Given following log snippets, their explanation, and nothing else, explain what failure, if any, occurred during build of this package.

  Snippets are in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.

  Snippets are delimited with '================'.

  Drawing on information from all snippets, provide a concise explanation of the issue and recommend a solution.

  Explanation of the issue, and recommended solution, should take a handful of sentences.

  Snippets:

  [=================================== FAILURES ===================================
________ TestSanityCheckRepodata.test_correct_yum_repo_with_xz_compress ________
self = <tests.test_util.TestSanityCheckRepodata object at 0x3ff9de481d0>
    def test_correct_yum_repo_with_xz_compress(self):
        """No Exception should be raised if the repo is normal.
    
        This is using default XZ compression.
        """
        base.mkmetadatadir(self.tempdir, compress_type='xz')
    
        # No exception should be raised here.
>       util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=True)
tests/test_util.py:467: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpvozqkuy7bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmpvozqkuy7bodhi/\nFailed to load expired repos cache: [Errno 13] Permission denied: \'...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-d0a54114aaa31648": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpvozqkuy7bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
________ TestSanityCheckRepodata.test_correct_yum_repo_with_gz_compress ________
self = <tests.test_util.TestSanityCheckRepodata object at 0x3ff9de488f0>
    def test_correct_yum_repo_with_gz_compress(self):
        """No Exception should be raised if the repo is normal.
    
        This is using GZ compression.
        """
        base.mkmetadatadir(self.tempdir, compress_type='gz')
    
        # No exception should be raised here.
>       util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=True)
tests/test_util.py:488: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp1jbwuc11bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmp1jbwuc11bodhi/\nFailed to load expired repos cache: [Errno 13] Permission denied: \'...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-6dd1032be3afbb0e": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp1jbwuc11bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
_______ TestSanityCheckRepodata.test_correct_yum_repo_with_bz2_compress ________
self = <tests.test_util.TestSanityCheckRepodata object at 0x3ff9de4bda0>
    def test_correct_yum_repo_with_bz2_compress(self):
        """No Exception should be raised if the repo is normal.
    
        This is using BZ2 compression.
        """
        base.mkmetadatadir(self.tempdir, compress_type='bz2')
    
        # No exception should be raised here.
>       util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=True)
tests/test_util.py:498: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpc59mrdm1bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmpc59mrdm1bodhi/\nFailed to load expired repos cache: [Errno 13] Permission denied: \'...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-a53211f8ca7c9316": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpc59mrdm1bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
_______ TestSanityCheckRepodata.test_correct_yum_repo_with_zstd_compress _______
self = <tests.test_util.TestSanityCheckRepodata object at 0x3ff9de4be00>
    @pytest.mark.skipif(
        packaging.version.parse(createrepo_c.VERSION) < packaging.version.parse('1.0.0'),
        reason='ZSTD compression requires createrepo_c 1.0.0 or higher'
    )
    def test_correct_yum_repo_with_zstd_compress(self):
        """No Exception should be raised if the repo is normal.
    
        This is using ZSTD compression.
        """
        base.mkmetadatadir(self.tempdir, compress_type='zstd')
    
        # No exception should be raised here.
>       util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=True)
tests/test_util.py:512: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpmes6k1s1bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmpmes6k1s1bodhi/\nFailed to load expired repos cache: [Errno 13] Permission denied: \'...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-8eaf2deda4538a56": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpmes6k1s1bodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
______________ TestSanityCheckRepodata.test_repomd_drpms_disabled ______________
self = <tests.test_util.TestSanityCheckRepodata object at 0x3ff9de4a780>
    def test_repomd_drpms_disabled(self):
        """If the prestodelta data tag is missing in a repo without DRPMs is fine."""
        base.mkmetadatadir(self.tempdir)
        repomd_path = os.path.join(self.tempdir, 'repodata', 'repomd.xml')
        repomd = ElementTree.parse(repomd_path)
        ElementTree.register_namespace('', 'http://linux.duke.edu/metadata/repo')
        root = repomd.getroot()
        for data in root.findall('{http://linux.duke.edu/metadata/repo}data'):
            if data.attrib['type'] == 'prestodelta':
                root.remove(data)
        repomd.write(repomd_path, encoding='UTF-8', xml_declaration=True)
    
        # No exception should be raised.
>       util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=False)
tests/test_util.py:655: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpsqgz5wkkbodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmpsqgz5wkkbodhi/\nFailed to load expired repos cache: [Errno 13] Permission denied: \'...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-04e93ffbf13728c3": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpsqgz5wkkbodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
___________________ TestSanityCheckRepodata.test_source_true ___________________
self = <tests.test_util.TestSanityCheckRepodata object at 0x3ff9de4acf0>
    def test_source_true(self):
        """It should not fail source repos for missing prestodelta or comps."""
        base.mkmetadatadir(self.tempdir)
        repomd_path = os.path.join(self.tempdir, 'repodata', 'repomd.xml')
        repomd = ElementTree.parse(repomd_path)
        ElementTree.register_namespace('', 'http://linux.duke.edu/metadata/repo')
        root = repomd.getroot()
        for data in root.findall('{http://linux.duke.edu/metadata/repo}data'):
            # Source repos don't have drpms or comps.
            if data.attrib['type'] in ('group', 'prestodelta'):
                root.remove(data)
        repomd.write(repomd_path, encoding='UTF-8', xml_declaration=True)
    
        # No exception should be raised.
>       util.sanity_check_repodata(self.tempdir, repo_type='source', drpms=True)
tests/test_util.py:671: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpotw7x87dbodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmpotw7x87dbodhi/\nFailed to load expired repos cache: [Errno 13] Permission denied: \'...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-2e88aeae7acd6877": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmpotw7x87dbodhi/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
_____________________ TestComposer.test_sanity_check_valid _____________________
self = <tests.tasks.test_composer.TestComposer object at 0x3ff9d189040>
save_state = <MagicMock name='save_state' id='4396222416080'>
    @mock.patch('bodhi.server.tasks.composer.ComposerThread.save_state')
    def test_sanity_check_valid(self, save_state):
        task = self._make_task()
        t = RPMComposerThread(self.semmock, task['composes'][0],
                              'ralph', self.db_factory, self.tempdir)
        t.devnull = mock.MagicMock()
        t.id = 'f17-updates-testing'
        with self.db_factory() as session:
            t.db = session
            t.compose = session.query(Compose).one()
            t._checkpoints = {}
            t._startyear = datetime.now(timezone.utc).year
            t._wait_for_pungi(self._generate_fake_pungi(t, 'testing_tag', t.compose.release)())
            t.db = None
    
        # test with valid repodata
        for arch in ('i386', 'x86_64', 'armhfp'):
            repo = os.path.join(t.path, 'compose', 'Everything', arch, 'os')
            base.mkmetadatadir(repo)
            os.makedirs(os.path.join(repo, 'Packages', 'a'))
            name = 'test.rpm'
            if arch == 'armhfp':
                name = 'test.notrpm'
            with open(os.path.join(repo, 'Packages', 'a', name), 'w') as tf:
                tf.write('foo')
    
        base.mkmetadatadir(os.path.join(t.path, 'compose', 'Everything', 'source', 'tree'),
                           source=True)
        os.makedirs(os.path.join(t.path, 'compose', 'Everything', 'source', 'tree', 'Packages',
                                 'a'))
        with open(os.path.join(t.path, 'compose', 'Everything', 'source', 'tree', 'Packages', 'a',
                               'test.src.rpm'), 'w') as tf:
            tf.write('bar')
    
        assert 'completed_repo' in t._checkpoints
        save_state.reset_mock()
>       t._sanity_check_repo()
tests/tasks/test_composer.py:814: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/tasks/composer.py:1174: in _sanity_check_repo
    sanity_check_repodata(repodata, repo_type=repo_type, drpms=drpms)
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp57ggvjfabodhi/Fedora-17-updates-testing-20250414.0/compo...i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmp57ggvjfabodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/\nFail...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-5d1a8af71e3a4245": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp57ggvjfabodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
------------------------------ Captured log call -------------------------------
ERROR    bodhi:composer.py:1176 Repodata sanity check failed, compose thrown out
Traceback (most recent call last):
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/tasks/composer.py", line 1174, in _sanity_check_repo
    sanity_check_repodata(repodata, repo_type=repo_type, drpms=drpms)
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/util.py", line 385, in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/util.py", line 418, in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 468, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 573, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp57ggvjfabodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
____________________ TestComposer.test_sanity_check_symlink ____________________
self = <tests.tasks.test_composer.TestComposer object at 0x3ff9d189340>
save_state = <MagicMock name='save_state' id='4396219407040'>
    @mock.patch('bodhi.server.tasks.composer.ComposerThread.save_state')
    def test_sanity_check_symlink(self, save_state):
        task = self._make_task()
        t = RPMComposerThread(self.semmock, task['composes'][0],
                              'ralph', self.db_factory, self.tempdir)
        t.devnull = mock.MagicMock()
        t.id = 'f17-updates-testing'
        with self.db_factory() as session:
            t.db = session
            t.compose = session.query(Compose).one()
            t._checkpoints = {}
            t._startyear = datetime.now(timezone.utc).year
            t._wait_for_pungi(self._generate_fake_pungi(t, 'testing_tag', t.compose.release)())
            t.db = None
    
        # test with valid repodata
        for arch in ('i386', 'x86_64', 'armhfp'):
            repo = os.path.join(t.path, 'compose', 'Everything', arch, 'os')
            base.mkmetadatadir(repo)
            os.makedirs(os.path.join(repo, 'Packages', 'a'))
            os.symlink('/dev/null', os.path.join(repo, 'Packages', 'a', 'test.notrpm'))
    
        base.mkmetadatadir(os.path.join(t.path, 'compose', 'Everything', 'source', 'tree'),
                           source=True)
        os.makedirs(os.path.join(t.path, 'compose', 'Everything', 'source', 'tree', 'Packages',
                                 'a'))
        os.symlink('/dev/null', os.path.join(t.path, 'compose', 'Everything', 'source', 'tree',
                                             'Packages', 'a', 'test.src.rpm'))
    
        assert 'completed_repo' in t._checkpoints
        save_state.reset_mock()
        with pytest.raises(Exception) as exc:
            t._sanity_check_repo()
>       assert "Symlinks found" in str(exc.value)
E       assert 'Symlinks found' in "Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp5j92y4zibodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1."
E        +  where "Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp5j92y4zibodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1." = str(CalledProcessError(1, ['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp5j92y4zibodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']))
E        +    where CalledProcessError(1, ['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp5j92y4zibodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']) = <ExceptionInfo CalledProcessError(1, ['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp5j92y4zibodhi/Fedora-17-updates-tes...skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']) tblen=6>.value
tests/tasks/test_composer.py:887: AssertionError
------------------------------ Captured log call -------------------------------
ERROR    bodhi:composer.py:1176 Repodata sanity check failed, compose thrown out
Traceback (most recent call last):
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/tasks/composer.py", line 1174, in _sanity_check_repo
    sanity_check_repodata(repodata, repo_type=repo_type, drpms=drpms)
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/util.py", line 385, in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/util.py", line 418, in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 468, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 573, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp5j92y4zibodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
______________ TestComposer.test_sanity_check_directories_missing ______________
self = <tests.tasks.test_composer.TestComposer object at 0x3ff9d1894f0>
save_state = <MagicMock name='save_state' id='4396222420016'>
    @mock.patch('bodhi.server.tasks.composer.ComposerThread.save_state')
    def test_sanity_check_directories_missing(self, save_state):
        task = self._make_task()
        t = RPMComposerThread(self.semmock, task['composes'][0],
                              'ralph', self.db_factory, self.tempdir)
        t.devnull = mock.MagicMock()
        t.id = 'f17-updates-testing'
        with self.db_factory() as session:
            t.db = session
            t.compose = session.query(Compose).one()
            t._checkpoints = {}
            t._startyear = datetime.now(timezone.utc).year
            t._wait_for_pungi(self._generate_fake_pungi(t, 'testing_tag', t.compose.release)())
            t.db = None
    
        # test with valid repodata
        for arch in ('i386', 'x86_64', 'armhfp'):
            repo = os.path.join(t.path, 'compose', 'Everything', arch, 'os')
            base.mkmetadatadir(repo)
            shutil.rmtree(os.path.join(t.path, 'compose', 'Everything', arch, 'os', 'Packages'))
    
        base.mkmetadatadir(os.path.join(t.path, 'compose', 'Everything', 'source', 'tree'),
                           source=True)
    
        assert 'completed_repo' in t._checkpoints
        save_state.reset_mock()
        with pytest.raises(OSError) as exc:
>           t._sanity_check_repo()
tests/tasks/test_composer.py:918: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bodhi/server/tasks/composer.py:1174: in _sanity_check_repo
    sanity_check_repodata(repodata, repo_type=repo_type, drpms=drpms)
bodhi/server/util.py:385: in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
bodhi/server/util.py:418: in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
/usr/lib64/python3.12/subprocess.py:468: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
input = None, capture_output = False, timeout = None, check = True
popenargs = (['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp9uwose1xbodhi/Fedora-17-updates-testing-20250414.0/compo...i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', ...],)
kwargs = {'encoding': 'utf-8', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: 1 args: ['dnf4', '--disablerepo=*', '--repofrompath=test...>
stdout = 'Added testrepo repo from /tmp/tmp9uwose1xbodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/\nFail...son\'\nError: Cannot create repo destination directory "/var/cache/yum/testrepo-56498c56cc29698f": Permission denied\n'
stderr = None, retcode = 1
    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp9uwose1xbodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.
/usr/lib64/python3.12/subprocess.py:573: CalledProcessError
------------------------------ Captured log call -------------------------------
ERROR    bodhi:composer.py:1176 Repodata sanity check failed, compose thrown out
Traceback (most recent call last):
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/tasks/composer.py", line 1174, in _sanity_check_repo
    sanity_check_repodata(repodata, repo_type=repo_type, drpms=drpms)
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/util.py", line 385, in sanity_check_repodata
    output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/bodhi_server-8.3.0/bodhi/server/util.py", line 418, in sanity_check_repodata_dnf
    return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 468, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 573, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['dnf4', '--disablerepo=*', '--repofrompath=testrepo,/tmp/tmp9uwose1xbodhi/Fedora-17-updates-testing-20250414.0/compose/Everything/i386/os/', '--enablerepo=testrepo', '--setopt=skip_if_unavailable=0', '--setopt=testrepo.skip_if_unavailable=0', '--refresh', '--nogpgcheck', 'list', '--available']' returned non-zero exit status 1.]: [Details of failed tests.]
=============
[=========================== short test summary info ============================
FAILED tests/test_util.py::TestSanityCheckRepodata::test_correct_yum_repo_with_xz_compress
FAILED tests/test_util.py::TestSanityCheckRepodata::test_correct_yum_repo_with_gz_compress
FAILED tests/test_util.py::TestSanityCheckRepodata::test_correct_yum_repo_with_bz2_compress
FAILED tests/test_util.py::TestSanityCheckRepodata::test_correct_yum_repo_with_zstd_compress
FAILED tests/test_util.py::TestSanityCheckRepodata::test_repomd_drpms_disabled
FAILED tests/test_util.py::TestSanityCheckRepodata::test_source_true - subpro...
FAILED tests/tasks/test_composer.py::TestComposer::test_sanity_check_valid - ...
FAILED tests/tasks/test_composer.py::TestComposer::test_sanity_check_symlink
FAILED tests/tasks/test_composer.py::TestComposer::test_sanity_check_directories_missing
===== 9 failed, 1711 passed, 1 skipped, 1558 warnings in 208.79s (0:03:28) =====
RPM build errors:
error: Bad exit status from /var/tmp/rpm-tmp.XzGcft (%check)
    Bad exit status from /var/tmp/rpm-tmp.XzGcft (%check)]: [Summary of failed tests.]

Issue: The build failed in the %check phase due to failing tests.
Resolution: Review the code and test for possible regressions.