commit:     f3b90592994c418286b10d883b4aee9a104f5e75
Author:     James Le Cuirot <chewi <AT> gentoo <DOT> org>
AuthorDate: Fri Jul 21 20:36:31 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Wed Aug  2 06:31:20 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=f3b90592

Drop custom test runner bits in favour of pytest

Signed-off-by: James Le Cuirot <chewi <AT> gentoo.org>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 .builds/lint.yml                                   |   2 +-
 .github/workflows/lint.yml                         |   2 +-
 DEVELOPING                                         |   2 +-
 TEST-NOTES                                         |   2 +-
 lib/portage/tests/__init__.py                      | 269 +--------------------
 lib/portage/tests/conftest.py                      |  27 ++-
 lib/portage/tests/dep/meson.build                  |  10 +-
 .../tests/dep/{testAtom.py => test_atom.py}        |   0
 ...ckRequiredUse.py => test_check_required_use.py} |   0
 ...endedAtomDict.py => test_extended_atom_dict.py} |   0
 ...fectingUSE.py => test_extract_affecting_use.py} |   0
 .../dep/{testStandalone.py => test_standalone.py}  |   0
 lib/portage/tests/glsa/test_security_set.py        |  16 +-
 lib/portage/tests/meson.build                      |   1 -
 lib/portage/tests/process/test_poll.py             |   1 -
 lib/portage/tests/process/test_unshare_net.py      |  13 -
 .../tests/resolver/test_autounmask_multilib_use.py |   2 -
 .../resolver/test_autounmask_use_slot_conflict.py  |   2 -
 lib/portage/tests/resolver/test_or_choices.py      |   2 -
 lib/portage/tests/runTests.py                      |  79 ------
 lib/portage/tests/sets/base/meson.build            |   4 +-
 ...lPackageSet.py => test_internal_package_set.py} |   0
 .../{testVariableSet.py => test_variable_set.py}   |   0
 lib/portage/tests/sets/files/meson.build           |   4 +-
 ...estConfigFileSet.py => test_config_file_set.py} |   0
 ...estStaticFileSet.py => test_static_file_set.py} |   0
 lib/portage/tests/sets/shell/meson.build           |   2 +-
 .../sets/shell/{testShell.py => test_shell.py}     |   0
 lib/portage/tests/sync/test_sync_local.py          |  13 +-
 lib/portage/tests/util/file_copy/test_copyfile.py  |  11 +-
 .../tests/util/futures/test_iter_completed.py      |  12 +-
 meson.build                                        |   4 +-
 runtests                                           | 184 --------------
 tox.ini                                            |   2 +-
 34 files changed, 49 insertions(+), 617 deletions(-)

diff --git a/.builds/lint.yml b/.builds/lint.yml
index 847552aac..e7aad2114 100644
--- a/.builds/lint.yml
+++ b/.builds/lint.yml
@@ -25,7 +25,7 @@ tasks:
   - black: |
       source .venv/bin/activate
       cd portage
-      STRAGGLERS="$(find bin runtests -type f -not -name '*.py' -not -name 
'*.sh' | \
+      STRAGGLERS="$(find bin -type f -not -name '*.py' -not -name '*.sh' | \
           xargs grep -l '#!/usr/bin/env python' | \
           tr '\n' ' ')"
       time black --check --diff --color . $STRAGGLERS

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index f2af40957..69b9578d4 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -11,7 +11,7 @@ jobs:
         id: stragglers
         run: |
           echo "::set-output name=missed::$(
-          find bin runtests -type f -not -name '*.py' -not -name '*.sh' | \
+          find bin -type f -not -name '*.py' -not -name '*.sh' | \
           xargs grep -l '#!/usr/bin/env python' | tr $'\n' ' ')"
       - uses: psf/black@stable
         with:

diff --git a/DEVELOPING b/DEVELOPING
index a49afff83..8302f9aa1 100644
--- a/DEVELOPING
+++ b/DEVELOPING
@@ -223,7 +223,7 @@ and commit.
      - meson setup -Dmodules-only=true build
      - meson test -C build --verbose
    Use meson setup's --native-file to override the Python version. See
-   PYTHON_SUPPORTED_VERSIONS in runtests.
+   Python versions listed in tox.ini.
 
 4. Version bump the ebuild locally (don't push) and verify it can re-install 
itself:
        emerge --oneshot sys-apps/portage

diff --git a/TEST-NOTES b/TEST-NOTES
index 8be5f9cf3..8a1b981bb 100644
--- a/TEST-NOTES
+++ b/TEST-NOTES
@@ -42,4 +42,4 @@ functionality.  You should raise portage.tests.SkipException 
in that case.
 emerge
 ------
 
-The emerge namespace currently has 0 tests (and no runner)
+The emerge namespace currently has 0 tests

diff --git a/lib/portage/tests/__init__.py b/lib/portage/tests/__init__.py
index d8e0cc78f..ef5985298 100644
--- a/lib/portage/tests/__init__.py
+++ b/lib/portage/tests/__init__.py
@@ -1,5 +1,5 @@
 # tests/__init__.py -- Portage Unit Test functionality
-# Copyright 2006-2021 Gentoo Authors
+# Copyright 2006-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import argparse
@@ -64,175 +64,6 @@ def cnf_sbindir():
     return os.path.join(portage.const.EPREFIX or "/", "usr", "sbin")
 
 
-def main():
-    suite = unittest.TestSuite()
-    basedir = Path(__file__).resolve().parent
-
-    argv0 = Path(sys.argv[0])
-
-    usage = f"usage: {argv0.name} [options] [tests to run]"
-    parser = argparse.ArgumentParser(usage=usage)
-    parser.add_argument(
-        "-l", "--list", help="list all tests", action="store_true", 
dest="list_tests"
-    )
-    parser.add_argument("tests", nargs="*", type=Path)
-    options = parser.parse_args(args=sys.argv)
-
-    if (
-        no_color(os.environ)
-        or os.environ.get("TERM") == "dumb"
-        or not sys.stdout.isatty()
-    ):
-        portage.output.nocolor()
-
-    if options.list_tests:
-        testdir = argv0.parent
-        for mydir in getTestDirs(basedir):
-            testsubdir = mydir.name
-            for name in getTestNames(mydir):
-                print(f"{testdir}/{testsubdir}/{name}.py")
-        return os.EX_OK
-
-    if len(options.tests) > 1:
-        suite.addTests(getTestFromCommandLine(options.tests[1:], basedir))
-    else:
-        for mydir in getTestDirs(basedir):
-            suite.addTests(getTests(mydir, basedir))
-
-    result = TextTestRunner(verbosity=2).run(suite)
-    if not result.wasSuccessful():
-        return 1
-    return os.EX_OK
-
-
-def my_import(name):
-    mod = __import__(name)
-    components = name.split(".")
-    for comp in components[1:]:
-        mod = getattr(mod, comp)
-    return mod
-
-
-def getTestFromCommandLine(args, base_path):
-    result = []
-    for arg in args:
-        realpath = arg.resolve()
-        path = realpath.parent
-        f = realpath.relative_to(path)
-
-        if not f.name.startswith("test") or not f.suffix == ".py":
-            raise Exception(f"Invalid argument: '{arg}'")
-
-        mymodule = f.stem
-        result.extend(getTestsFromFiles(path, base_path, [mymodule]))
-    return result
-
-
-def getTestDirs(base_path):
-    TEST_FILE = "__test__.py"
-    testDirs = []
-
-    # the os.walk help mentions relative paths as being quirky
-    # I was tired of adding dirs to the list, so now we add __test__.py
-    # to each dir we want tested.
-    for testFile in base_path.rglob(TEST_FILE):
-        testDirs.append(testFile.parent)
-
-    testDirs.sort()
-    return testDirs
-
-
-def getTestNames(path):
-    files = path.glob("*")
-    files = [f.stem for f in files if f.name.startswith("test") and f.suffix 
== ".py"]
-    files.sort()
-    return files
-
-
-def getTestsFromFiles(path, base_path, files):
-    parent_path = path.relative_to(base_path)
-    parent_module = ".".join(("portage", "tests") + parent_path.parts)
-    result = []
-    for mymodule in files:
-        # Make the trailing / a . for module importing
-        modname = ".".join((parent_module, mymodule))
-        mod = my_import(modname)
-        result.append(unittest.TestLoader().loadTestsFromModule(mod))
-    return result
-
-
-def getTests(path, base_path):
-    """
-
-    path is the path to a given subdir ( 'portage/' for example)
-    This does a simple filter on files in that dir to give us modules
-    to import
-
-    """
-    return getTestsFromFiles(path, base_path, getTestNames(path))
-
-
-class TextTestResult(_TextTestResult):
-    """
-    We need a subclass of unittest.runner.TextTestResult to handle tests with 
TODO
-
-    This just adds an addTodo method that can be used to add tests
-    that are marked TODO; these can be displayed later
-    by the test runner.
-    """
-
-    def __init__(self, stream, descriptions, verbosity):
-        super().__init__(stream, descriptions, verbosity)
-        self.todoed = []
-        self.portage_skipped = []
-
-    def addSuccess(self, test):
-        super(_TextTestResult, self).addSuccess(test)
-        if self.showAll:
-            self.stream.writeln(colorize("GOOD", "ok"))
-        elif self.dots:
-            self.stream.write(colorize("GOOD", "."))
-            self.stream.flush()
-
-    def addError(self, test, err):
-        super(_TextTestResult, self).addError(test, err)
-        if self.showAll:
-            self.stream.writeln(colorize("BAD", "ERROR"))
-        elif self.dots:
-            self.stream.write(colorize("HILITE", "E"))
-            self.stream.flush()
-
-    def addFailure(self, test, err):
-        super(_TextTestResult, self).addFailure(test, err)
-        if self.showAll:
-            self.stream.writeln(colorize("BAD", "FAIL"))
-        elif self.dots:
-            self.stream.write(colorize("BAD", "F"))
-            self.stream.flush()
-
-    def addTodo(self, test, info):
-        self.todoed.append((test, info))
-        if self.showAll:
-            self.stream.writeln(colorize("BRACKET", "TODO"))
-        elif self.dots:
-            self.stream.write(colorize("BRACKET", "."))
-
-    def addPortageSkip(self, test, info):
-        self.portage_skipped.append((test, info))
-        if self.showAll:
-            self.stream.writeln(colorize("WARN", "SKIP"))
-        elif self.dots:
-            self.stream.write(colorize("WARN", "."))
-
-    def printErrors(self):
-        if self.dots or self.showAll:
-            self.stream.writeln()
-            self.printErrorList("ERROR", self.errors)
-            self.printErrorList("FAIL", self.failures)
-            self.printErrorList("TODO", self.todoed)
-            self.printErrorList("SKIP", self.portage_skipped)
-
-
 class TestCase(unittest.TestCase):
     """
     We need a way to mark a unit test as "ok to fail"
@@ -243,69 +74,11 @@ class TestCase(unittest.TestCase):
 
     def __init__(self, *pargs, **kwargs):
         unittest.TestCase.__init__(self, *pargs, **kwargs)
-        self.todo = False
-        self.portage_skip = None
         self.cnf_path = cnf_path
         self.cnf_etc_path = cnf_etc_path
         self.bindir = cnf_bindir
         self.sbindir = cnf_sbindir
 
-    def defaultTestResult(self):
-        return TextTestResult()
-
-    def run(self, result=None):
-        if result is None:
-            result = self.defaultTestResult()
-        result.startTest(self)
-        testMethod = getattr(self, self._testMethodName)
-        try:
-            ok = False
-            try:
-                try:
-                    self.setUp()
-                except KeyboardInterrupt:
-                    raise
-                except unittest.SkipTest:
-                    raise
-                except Exception:
-                    result.addError(self, sys.exc_info())
-                    return
-
-                testMethod()
-                ok = True
-            except unittest.SkipTest as e:
-                result.addPortageSkip(self, f"{testMethod}: SKIP: {str(e)}")
-            except self.failureException:
-                if self.portage_skip is not None:
-                    if self.portage_skip is True:
-                        result.addPortageSkip(self, f"{testMethod}: SKIP")
-                    else:
-                        result.addPortageSkip(
-                            self, f"{testMethod}: SKIP: {self.portage_skip}"
-                        )
-                elif self.todo:
-                    result.addTodo(self, f"{testMethod}: TODO")
-                else:
-                    result.addFailure(self, sys.exc_info())
-            except (KeyboardInterrupt, SystemExit):
-                raise
-            except:
-                result.addError(self, sys.exc_info())
-
-            try:
-                self.tearDown()
-            except SystemExit:
-                raise
-            except KeyboardInterrupt:
-                raise
-            except:
-                result.addError(self, sys.exc_info())
-                ok = False
-            if ok:
-                result.addSuccess(self)
-        finally:
-            result.stopTest(self)
-
     def assertRaisesMsg(self, msg, excClass, callableObj, *args, **kwargs):
         """Fail unless an exception of class excClass is thrown
         by callableObj when invoked with arguments args and keyword
@@ -332,46 +105,6 @@ class TestCase(unittest.TestCase):
             raise self.failureException(f"path exists when it should not: 
{path}")
 
 
-class TextTestRunner(unittest.TextTestRunner):
-    """
-    We subclass unittest.TextTestRunner to output SKIP for tests that fail but 
are skippable
-    """
-
-    def _makeResult(self):
-        return TextTestResult(self.stream, self.descriptions, self.verbosity)
-
-    def run(self, test):
-        """
-        Run the given test case or test suite.
-        """
-        result = self._makeResult()
-        startTime = time.time()
-        test(result)
-        stopTime = time.time()
-        timeTaken = stopTime - startTime
-        result.printErrors()
-        self.stream.writeln(result.separator2)
-        run = result.testsRun
-        self.stream.writeln(
-            "Ran %d test%s in %.3fs" % (run, run != 1 and "s" or "", timeTaken)
-        )
-        self.stream.writeln()
-        if not result.wasSuccessful():
-            self.stream.write(colorize("BAD", "FAILED") + " (")
-            failed = len(result.failures)
-            errored = len(result.errors)
-            if failed:
-                self.stream.write("failures=%d" % failed)
-            if errored:
-                if failed:
-                    self.stream.write(", ")
-                self.stream.write("errors=%d" % errored)
-            self.stream.writeln(")")
-        else:
-            self.stream.writeln(colorize("GOOD", "OK"))
-        return result
-
-
 test_cps = ["sys-apps/portage", "virtual/portage"]
 test_versions = ["1.0", "1.0-r1", "2.3_p4", "1.0_alpha57"]
 test_slots = [None, "1", "gentoo-sources-2.6.17", "spankywashere"]

diff --git a/lib/portage/tests/conftest.py b/lib/portage/tests/conftest.py
index 88fc72b15..76bdaa381 100644
--- a/lib/portage/tests/conftest.py
+++ b/lib/portage/tests/conftest.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# runTests.py -- Portage Unit Test Functionality
 # Copyright 2006-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
@@ -64,22 +63,24 @@ def prepare_environment():
         path.insert(0, PORTAGE_BIN_PATH)
         os.environ["PATH"] = ":".join(path)
 
-    # Copy GPG test keys to temporary directory
-    gpg_path = tempfile.mkdtemp(prefix="gpg_")
+    try:
+        # Copy GPG test keys to temporary directory
+        gpg_path = tempfile.mkdtemp(prefix="gpg_")
 
-    shutil.copytree(
-        os.path.join(os.path.dirname(os.path.realpath(__file__)), ".gnupg"),
-        gpg_path,
-        dirs_exist_ok=True,
-    )
+        shutil.copytree(
+            os.path.join(os.path.dirname(os.path.realpath(__file__)), 
".gnupg"),
+            gpg_path,
+            dirs_exist_ok=True,
+        )
 
-    os.chmod(gpg_path, 0o700)
-    os.environ["PORTAGE_GNUPGHOME"] = gpg_path
+        os.chmod(gpg_path, 0o700)
+        os.environ["PORTAGE_GNUPGHOME"] = gpg_path
 
-    yield
+        yield
 
-    global_event_loop().close()
-    shutil.rmtree(gpg_path, ignore_errors=True)
+    finally:
+        global_event_loop().close()
+        shutil.rmtree(gpg_path, ignore_errors=True)
 
 
 # if __name__ == "__main__":

diff --git a/lib/portage/tests/dep/meson.build 
b/lib/portage/tests/dep/meson.build
index f96018917..848f5aade 100644
--- a/lib/portage/tests/dep/meson.build
+++ b/lib/portage/tests/dep/meson.build
@@ -1,10 +1,10 @@
 py.install_sources(
     [
-        'testAtom.py',
-        'testCheckRequiredUse.py',
-        'testExtendedAtomDict.py',
-        'testExtractAffectingUSE.py',
-        'testStandalone.py',
+        'test_atom.py',
+        'test_check_required_use.py',
+        'test_extended_atom_dict.py',
+        'test_extract_affecting_use.py',
+        'test_standalone.py',
         'test_best_match_to_list.py',
         'test_dep_getcpv.py',
         'test_dep_getrepo.py',

diff --git a/lib/portage/tests/dep/testAtom.py 
b/lib/portage/tests/dep/test_atom.py
similarity index 100%
rename from lib/portage/tests/dep/testAtom.py
rename to lib/portage/tests/dep/test_atom.py

diff --git a/lib/portage/tests/dep/testCheckRequiredUse.py 
b/lib/portage/tests/dep/test_check_required_use.py
similarity index 100%
rename from lib/portage/tests/dep/testCheckRequiredUse.py
rename to lib/portage/tests/dep/test_check_required_use.py

diff --git a/lib/portage/tests/dep/testExtendedAtomDict.py 
b/lib/portage/tests/dep/test_extended_atom_dict.py
similarity index 100%
rename from lib/portage/tests/dep/testExtendedAtomDict.py
rename to lib/portage/tests/dep/test_extended_atom_dict.py

diff --git a/lib/portage/tests/dep/testExtractAffectingUSE.py 
b/lib/portage/tests/dep/test_extract_affecting_use.py
similarity index 100%
rename from lib/portage/tests/dep/testExtractAffectingUSE.py
rename to lib/portage/tests/dep/test_extract_affecting_use.py

diff --git a/lib/portage/tests/dep/testStandalone.py 
b/lib/portage/tests/dep/test_standalone.py
similarity index 100%
rename from lib/portage/tests/dep/testStandalone.py
rename to lib/portage/tests/dep/test_standalone.py

diff --git a/lib/portage/tests/glsa/test_security_set.py 
b/lib/portage/tests/glsa/test_security_set.py
index 867a7cd4f..a0ba1e5b4 100644
--- a/lib/portage/tests/glsa/test_security_set.py
+++ b/lib/portage/tests/glsa/test_security_set.py
@@ -1,4 +1,4 @@
-# Copyright 2013-2022 Gentoo Authors
+# Copyright 2013-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 
@@ -64,7 +64,7 @@ class SecuritySetTestCase(TestCase):
             __import__("xml.etree.ElementTree")
             __import__("xml.parsers.expat").parsers.expat.ExpatError
         except (AttributeError, ImportError):
-            return "python is missing xml support"
+            self.skipTest("python is missing xml support")
 
     def write_glsa_test_case(self, glsa_dir, glsa):
         with open(
@@ -75,11 +75,7 @@ class SecuritySetTestCase(TestCase):
             f.write(self.glsa_template % glsa)
 
     def testSecuritySet(self):
-        skip_reason = self._must_skip()
-        if skip_reason:
-            self.portage_skip = skip_reason
-            self.assertFalse(True, skip_reason)
-            return
+        self._must_skip()
 
         ebuilds = {
             "cat/A-vulnerable-2.2": {"KEYWORDS": "x86"},
@@ -159,11 +155,7 @@ class SecuritySetTestCase(TestCase):
         # testing the format parsing with a bit more flexibility (no
         # need to keep inventing packages).
 
-        skip_reason = self._must_skip()
-        if skip_reason:
-            self.portage_skip = skip_reason
-            self.assertFalse(True, skip_reason)
-            return
+        self._must_skip()
 
         ebuilds = {
             "cat/A-vulnerable-2.2": {"KEYWORDS": "x86"},

diff --git a/lib/portage/tests/meson.build b/lib/portage/tests/meson.build
index 86e8e71ce..e8f204b51 100644
--- a/lib/portage/tests/meson.build
+++ b/lib/portage/tests/meson.build
@@ -1,7 +1,6 @@
 py.install_sources(
     [
         'conftest.py',
-        'runTests.py',
         '__init__.py',
     ],
     subdir : 'portage/tests',

diff --git a/lib/portage/tests/process/test_poll.py 
b/lib/portage/tests/process/test_poll.py
index 371dd1906..65a9ca1bf 100644
--- a/lib/portage/tests/process/test_poll.py
+++ b/lib/portage/tests/process/test_poll.py
@@ -121,4 +121,3 @@ class PipeReaderArrayTestCase(PipeReaderTestCase):
         super().__init__(*args, **kwargs)
         # https://bugs.python.org/issue5380
         # https://bugs.pypy.org/issue956
-        self.todo = True

diff --git a/lib/portage/tests/process/test_unshare_net.py 
b/lib/portage/tests/process/test_unshare_net.py
index e17357f4d..dabf15585 100644
--- a/lib/portage/tests/process/test_unshare_net.py
+++ b/lib/portage/tests/process/test_unshare_net.py
@@ -33,19 +33,6 @@ class UnshareNetTestCase(TestCase):
     )
     @pytest.mark.skipif(platform.system() != "Linux", reason="not Linux")
     def testUnshareNet(self):
-        AM_I_UNDER_PYTEST = "PYTEST_CURRENT_TEST" in os.environ
-        if not AM_I_UNDER_PYTEST:
-            if platform.system() != "Linux":
-                self.skipTest("not Linux")
-            if portage.process.find_binary("ping") is None:
-                self.skipTest("ping not found")
-
-            errno_value = portage.process._unshare_validate(CLONE_NEWNET)
-            if errno_value != 0:
-                self.skipTest(
-                    f"Unable to unshare: {errno.errorcode.get(errno_value, 
'?')}"
-                )
-
         env = os.environ.copy()
         env["IPV6"] = "1" if portage.process._has_ipv6() else ""
         self.assertEqual(

diff --git a/lib/portage/tests/resolver/test_autounmask_multilib_use.py 
b/lib/portage/tests/resolver/test_autounmask_multilib_use.py
index 3abbebbd5..2d3da85c5 100644
--- a/lib/portage/tests/resolver/test_autounmask_multilib_use.py
+++ b/lib/portage/tests/resolver/test_autounmask_multilib_use.py
@@ -13,8 +13,6 @@ from portage.tests.resolver.ResolverPlayground import (
 class AutounmaskMultilibUseTestCase(TestCase):
     @pytest.mark.xfail()
     def testAutounmaskMultilibUse(self):
-        self.todo = True
-
         ebuilds = {
             "x11-proto/xextproto-7.2.1-r1": {
                 "EAPI": "5",

diff --git a/lib/portage/tests/resolver/test_autounmask_use_slot_conflict.py 
b/lib/portage/tests/resolver/test_autounmask_use_slot_conflict.py
index 2db1396bc..a0b8c4ae7 100644
--- a/lib/portage/tests/resolver/test_autounmask_use_slot_conflict.py
+++ b/lib/portage/tests/resolver/test_autounmask_use_slot_conflict.py
@@ -13,8 +13,6 @@ from portage.tests.resolver.ResolverPlayground import (
 class AutounmaskUseSlotConflictTestCase(TestCase):
     @pytest.mark.xfail()
     def testAutounmaskUseSlotConflict(self):
-        self.todo = True
-
         ebuilds = {
             "sci-libs/K-1": {"IUSE": "+foo", "EAPI": 1},
             "sci-libs/L-1": {"DEPEND": "sci-libs/K[-foo]", "EAPI": 2},

diff --git a/lib/portage/tests/resolver/test_or_choices.py 
b/lib/portage/tests/resolver/test_or_choices.py
index 33707c71a..4258a1ab5 100644
--- a/lib/portage/tests/resolver/test_or_choices.py
+++ b/lib/portage/tests/resolver/test_or_choices.py
@@ -649,8 +649,6 @@ class OrChoicesLibpostprocTestCase(TestCase):
         # compatible with any available media-video/ffmpeg slot. In order to
         # solve this test case, some fancy backtracking (like for bug 382421)
         # will be required.
-        self.todo = True
-
         ebuilds = {
             "media-video/ffmpeg-0.10": {"EAPI": "5", "SLOT": "0.10"},
             "media-video/ffmpeg-1.2.2": {"EAPI": "5", "SLOT": "0"},

diff --git a/lib/portage/tests/runTests.py b/lib/portage/tests/runTests.py
deleted file mode 100644
index 36ea3a791..000000000
--- a/lib/portage/tests/runTests.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python
-# runTests.py -- Portage Unit Test Functionality
-# Copyright 2006-2022 Gentoo Authors
-# Distributed under the terms of the GNU General Public License v2
-
-import grp
-import os
-import os.path as osp
-import pwd
-import signal
-import tempfile
-import shutil
-import sys
-
-
-def debug_signal(signum, frame):
-    import pdb
-
-    pdb.set_trace()
-
-
-signal.signal(signal.SIGUSR1, debug_signal)
-
-# Pretend that the current user's uid/gid are the 'portage' uid/gid,
-# so things go smoothly regardless of the current user and global
-# user/group configuration.
-os.environ["PORTAGE_USERNAME"] = pwd.getpwuid(os.getuid()).pw_name
-os.environ["PORTAGE_GRPNAME"] = grp.getgrgid(os.getgid()).gr_name
-
-# Insert our parent dir so we can do shiny import "tests"
-# This line courtesy of Marienz and Pkgcore ;)
-sys.path.insert(0, 
osp.dirname(osp.dirname(osp.dirname(osp.realpath(__file__)))))
-
-import portage
-
-portage._internal_caller = True
-
-# Ensure that we don't instantiate portage.settings, so that tests should
-# work the same regardless of global configuration file state/existence.
-portage._disable_legacy_globals()
-
-if portage.util.no_color(os.environ):
-    portage.output.nocolor()
-
-import portage.tests as tests
-from portage.util._eventloop.global_event_loop import global_event_loop
-from portage.const import PORTAGE_BIN_PATH
-
-path = os.environ.get("PATH", "").split(":")
-path = [x for x in path if x]
-
-insert_bin_path = True
-try:
-    insert_bin_path = not path or not os.path.samefile(path[0], 
PORTAGE_BIN_PATH)
-except OSError:
-    pass
-
-if insert_bin_path:
-    path.insert(0, PORTAGE_BIN_PATH)
-    os.environ["PATH"] = ":".join(path)
-
-# Copy GPG test keys to temporary directory
-gpg_path = tempfile.mkdtemp(prefix="gpg_")
-
-shutil.copytree(
-    os.path.join(os.path.dirname(os.path.realpath(__file__)), ".gnupg"),
-    gpg_path,
-    dirs_exist_ok=True,
-)
-
-os.chmod(gpg_path, 0o700)
-os.environ["PORTAGE_GNUPGHOME"] = gpg_path
-
-if __name__ == "__main__":
-    try:
-        sys.exit(tests.main())
-    finally:
-        global_event_loop().close()
-        shutil.rmtree(gpg_path, ignore_errors=True)

diff --git a/lib/portage/tests/sets/base/meson.build 
b/lib/portage/tests/sets/base/meson.build
index ba15a8213..6c59b11ee 100644
--- a/lib/portage/tests/sets/base/meson.build
+++ b/lib/portage/tests/sets/base/meson.build
@@ -1,7 +1,7 @@
 py.install_sources(
     [
-        'testInternalPackageSet.py',
-        'testVariableSet.py',
+        'test_internal_package_set.py',
+        'test_variable_set.py',
         '__init__.py',
         '__test__.py',
     ],

diff --git a/lib/portage/tests/sets/base/testInternalPackageSet.py 
b/lib/portage/tests/sets/base/test_internal_package_set.py
similarity index 100%
rename from lib/portage/tests/sets/base/testInternalPackageSet.py
rename to lib/portage/tests/sets/base/test_internal_package_set.py

diff --git a/lib/portage/tests/sets/base/testVariableSet.py 
b/lib/portage/tests/sets/base/test_variable_set.py
similarity index 100%
rename from lib/portage/tests/sets/base/testVariableSet.py
rename to lib/portage/tests/sets/base/test_variable_set.py

diff --git a/lib/portage/tests/sets/files/meson.build 
b/lib/portage/tests/sets/files/meson.build
index 0ac7449db..d4550f4ee 100644
--- a/lib/portage/tests/sets/files/meson.build
+++ b/lib/portage/tests/sets/files/meson.build
@@ -1,7 +1,7 @@
 py.install_sources(
     [
-        'testConfigFileSet.py',
-        'testStaticFileSet.py',
+        'test_config_file_set.py',
+        'test_static_file_set.py',
         '__init__.py',
         '__test__.py',
     ],

diff --git a/lib/portage/tests/sets/files/testConfigFileSet.py 
b/lib/portage/tests/sets/files/test_config_file_set.py
similarity index 100%
rename from lib/portage/tests/sets/files/testConfigFileSet.py
rename to lib/portage/tests/sets/files/test_config_file_set.py

diff --git a/lib/portage/tests/sets/files/testStaticFileSet.py 
b/lib/portage/tests/sets/files/test_static_file_set.py
similarity index 100%
rename from lib/portage/tests/sets/files/testStaticFileSet.py
rename to lib/portage/tests/sets/files/test_static_file_set.py

diff --git a/lib/portage/tests/sets/shell/meson.build 
b/lib/portage/tests/sets/shell/meson.build
index 6044f3ebe..55a41a6e0 100644
--- a/lib/portage/tests/sets/shell/meson.build
+++ b/lib/portage/tests/sets/shell/meson.build
@@ -1,6 +1,6 @@
 py.install_sources(
     [
-        'testShell.py',
+        'test_shell.py',
         '__init__.py',
         '__test__.py',
     ],

diff --git a/lib/portage/tests/sets/shell/testShell.py 
b/lib/portage/tests/sets/shell/test_shell.py
similarity index 100%
rename from lib/portage/tests/sets/shell/testShell.py
rename to lib/portage/tests/sets/shell/test_shell.py

diff --git a/lib/portage/tests/sync/test_sync_local.py 
b/lib/portage/tests/sync/test_sync_local.py
index c400c9bba..a8a71cd4b 100644
--- a/lib/portage/tests/sync/test_sync_local.py
+++ b/lib/portage/tests/sync/test_sync_local.py
@@ -1,4 +1,4 @@
-# Copyright 2014-2021 Gentoo Authors
+# Copyright 2014-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import datetime
@@ -23,18 +23,13 @@ class SyncLocalTestCase(TestCase):
 
     def _must_skip(self):
         if find_binary("rsync") is None:
-            return "rsync: command not found"
+            self.skipTest("rsync: command not found")
         if find_binary("git") is None:
-            return "git: command not found"
+            self.skipTest("git: command not found")
 
     def testSyncLocal(self):
         debug = False
-
-        skip_reason = self._must_skip()
-        if skip_reason:
-            self.portage_skip = skip_reason
-            self.assertFalse(True, skip_reason)
-            return
+        self._must_skip()
 
         repos_conf = textwrap.dedent(
             """

diff --git a/lib/portage/tests/util/file_copy/test_copyfile.py 
b/lib/portage/tests/util/file_copy/test_copyfile.py
index 22c3982f0..e91a47bed 100644
--- a/lib/portage/tests/util/file_copy/test_copyfile.py
+++ b/lib/portage/tests/util/file_copy/test_copyfile.py
@@ -56,15 +56,8 @@ class CopyFileSparseTestCase(TestCase):
             self.assertEqual(perform_md5(src_path), perform_md5(dest_path))
 
             # This last part of the test is expected to fail when sparse
-            # copy is not implemented, so set the todo flag in order
-            # to tolerate failures. Or mark it xfail:
-
-            AM_I_UNDER_PYTEST = "PYTEST_CURRENT_TEST" in os.environ
-
-            if AM_I_UNDER_PYTEST:
-                pytest.xfail(reason="sparse copy is not implemented")
-            else:
-                self.todo = True
+            # copy is not implemented, so mark it xfail:
+            pytest.xfail(reason="sparse copy is not implemented")
 
             # If sparse blocks were preserved, then both files should
             # consume the same number of blocks.

diff --git a/lib/portage/tests/util/futures/test_iter_completed.py 
b/lib/portage/tests/util/futures/test_iter_completed.py
index 8955546ee..bda900505 100644
--- a/lib/portage/tests/util/futures/test_iter_completed.py
+++ b/lib/portage/tests/util/futures/test_iter_completed.py
@@ -1,7 +1,10 @@
-# Copyright 2018 Gentoo Foundation
+# Copyright 2023 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 import time
+
+import pytest
+
 from portage.tests import TestCase
 from portage.util._async.ForkProcess import ForkProcess
 from portage.util._eventloop.global_event_loop import global_event_loop
@@ -28,11 +31,10 @@ class SleepProcess(ForkProcess):
 
 
 class IterCompletedTestCase(TestCase):
+    # Mark this as todo, since we don't want to fail if heavy system load 
causes
+    # the tasks to finish in an unexpected order.
+    @pytest.mark.xfail(strict=False)
     def testIterCompleted(self):
-        # Mark this as todo, since we don't want to fail if heavy system
-        # load causes the tasks to finish in an unexpected order.
-        self.todo = True
-
         loop = global_event_loop()
         tasks = [
             SleepProcess(seconds=0.200),

diff --git a/meson.build b/meson.build
index db812eeaf..3d2a053b5 100644
--- a/meson.build
+++ b/meson.build
@@ -95,9 +95,9 @@ if get_option('native-extensions')
 endif
 
 test(
-    'python',
+    'pytest',
     py,
-    args : ['-bWd', meson.current_source_dir() / 'lib' / 'portage' / 'tests' / 
'runTests.py'],
+    args : ['-m', 'pytest', '--rootdir', meson.current_source_dir(), 
meson.current_source_dir()],
     timeout : 0
 )
 

diff --git a/runtests b/runtests
deleted file mode 100755
index 1701190b4..000000000
--- a/runtests
+++ /dev/null
@@ -1,184 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2010-2023 Gentoo Authors
-# Distributed under the terms of the GNU General Public License v2
-#
-# Note: We don't want to import portage modules directly because we do things
-# like run the testsuite through multiple versions of python.
-
-"""Helper script to run portage unittests against different python versions.
-
-Note: Any additional arguments will be passed down directly to the underlying
-unittest runner.  This lets you select specific tests to execute.
-"""
-
-import argparse
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-
-
-# These are the versions we fully support and require to pass tests.
-PYTHON_SUPPORTED_VERSIONS = ["3.9", "3.10", "3.11"]
-# The rest are just "nice to have".
-PYTHON_NICE_VERSIONS = ["pypy3", "3.12"]
-
-EPREFIX = os.environ.get("PORTAGE_OVERRIDE_EPREFIX", "/")
-
-
-class Colors:
-    """Simple object holding color constants."""
-
-    _COLORS_YES = ("y", "yes", "true")
-    _COLORS_NO = ("n", "no", "false")
-
-    WARN = GOOD = BAD = NORMAL = ""
-
-    def __init__(self, colorize=None):
-        if colorize is None:
-            nocolors = os.environ.get("NOCOLOR", "false")
-            # Ugh, look away, for here we invert the world!
-            if nocolors in self._COLORS_YES:
-                colorize = False
-            elif nocolors in self._COLORS_NO:
-                colorize = True
-            else:
-                raise ValueError(f"$NOCOLORS is invalid: {nocolors}")
-        else:
-            if colorize in self._COLORS_YES:
-                colorize = True
-            elif colorize in self._COLORS_NO:
-                colorize = False
-            else:
-                raise ValueError(f"--colors is invalid: {colorize}")
-
-        if colorize:
-            self.WARN = "\033[1;33m"
-            self.GOOD = "\033[1;32m"
-            self.BAD = "\033[1;31m"
-            self.NORMAL = "\033[0m"
-
-
-def get_python_executable(ver):
-    """Find the right python executable for |ver|"""
-    if ver in ("pypy", "pypy3"):
-        prog = ver
-    else:
-        prog = "python" + ver
-    return os.path.join(EPREFIX, "usr", "bin", prog)
-
-
-def get_parser():
-    """Return a argument parser for this module"""
-    epilog = """Examples:
-List all the available unittests.
-$ %(prog)s --list
-
-Run against specific versions of python.
-$ %(prog)s --python-versions '2.7 3.3'
-
-Run just one unittest.
-$ %(prog)s lib/portage/tests/xpak/test_decodeint.py
-"""
-    parser = argparse.ArgumentParser(
-        description=__doc__,
-        formatter_class=argparse.RawDescriptionHelpFormatter,
-        epilog=epilog,
-    )
-    parser.add_argument(
-        "--keep-temp",
-        default=False,
-        action="store_true",
-        help="Do not delete the temporary directory when exiting",
-    )
-    parser.add_argument(
-        "--color",
-        type=str,
-        default=None,
-        help="Whether to use colorized output (default is auto)",
-    )
-    parser.add_argument(
-        "--python-versions",
-        action="append",
-        help="Versions of python to test (default is test available)",
-    )
-    return parser
-
-
-def main(argv):
-    parser = get_parser()
-    opts, args = parser.parse_known_args(argv)
-    colors = Colors(colorize=opts.color)
-
-    # Figure out all the versions we want to test.
-    if opts.python_versions is None:
-        ignore_missing = True
-        pyversions = PYTHON_SUPPORTED_VERSIONS + PYTHON_NICE_VERSIONS
-    else:
-        ignore_missing = False
-        pyversions = []
-        for ver in opts.python_versions:
-            if ver == "supported":
-                pyversions.extend(PYTHON_SUPPORTED_VERSIONS)
-            else:
-                pyversions.extend(ver.split())
-
-    tempdir = None
-    try:
-        # Set up a single tempdir for all the tests to use.
-        # This way we know the tests won't leak things on us.
-        tempdir = tempfile.mkdtemp(prefix="portage.runtests.")
-        os.environ["TMPDIR"] = tempdir
-
-        # Actually test those versions now.
-        statuses = []
-        for ver in pyversions:
-            prog = get_python_executable(ver)
-            cmd = [prog, "-b", "-Wd", "lib/portage/tests/runTests.py"] + args
-            if os.access(prog, os.X_OK):
-                print(f"{colors.GOOD}Testing with Python 
{ver}...{colors.NORMAL}")
-                statuses.append((ver, subprocess.call(cmd)))
-            elif not ignore_missing:
-                print(
-                    f"{colors.BAD}Could not find requested Python 
{ver}{colors.NORMAL}"
-                )
-                statuses.append((ver, 1))
-            else:
-                print(f"{colors.WARN}Skip Python {ver}...{colors.NORMAL}")
-            print()
-    finally:
-        if tempdir is not None:
-            if opts.keep_temp:
-                print(f"Temporary directory left behind:\n{tempdir}")
-            else:
-                # Nuke our tempdir and anything that might be under it.
-                shutil.rmtree(tempdir, True)
-
-    # Then summarize it all.
-    print("\nSummary:\n")
-    width = 10
-    header = "| %-*s | %s" % (width, "Version", "Status")
-    print(f"{header}\n|{'-' * (len(header) - 1)}")
-    exit_status = 0
-    for ver, status in statuses:
-        exit_status += status
-        if status:
-            color = colors.BAD
-            msg = "FAIL"
-        else:
-            color = colors.GOOD
-            msg = "PASS"
-        print(
-            "| %s%-*s%s | %s%s%s"
-            % (color, width, ver, colors.NORMAL, color, msg, colors.NORMAL)
-        )
-    exit(exit_status)
-
-
-if __name__ == "__main__":
-    try:
-        main(sys.argv[1:])
-    except KeyboardInterrupt:
-        print("interrupted ...", file=sys.stderr)
-        exit(1)

diff --git a/tox.ini b/tox.ini
index 6a52a8a0a..6778dd00f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,4 +28,4 @@ allowlist_externals =
        ./run-pylint
 commands =
        pylint: ./run-pylint
-       test: python -bWd lib/portage/tests/runTests.py
+       test: pytest


Reply via email to