diff --git a/stestr/commands/run.py b/stestr/commands/run.py index 8c02b24..2bf4b32 100644 --- a/stestr/commands/run.py +++ b/stestr/commands/run.py @@ -16,6 +16,7 @@ import functools import io import os +import os.path import subprocess import sys @@ -521,11 +522,15 @@ def run_command( if no_discover: ids = no_discover + klass = None if "::" in ids: - ids = ids.replace("::", ".") - if ids.find("/") != -1: - root = ids.replace(".py", "") - ids = root.replace("/", ".") + ids, klass = ids.split("::", 1) + klass = ".".join(klass.split("::")) + if ids.find(os.path.sep) != -1: + root, _ = os.path.splitext(os.path.normpath(ids)) + ids = ".".join(root.split(os.path.sep)) + if klass: + ids = f"{ids}.{klass}" stestr_python = sys.executable if os.environ.get("PYTHON"): python_bin = os.environ.get("PYTHON") @@ -584,11 +589,15 @@ def run_tests(): if pdb: ids = pdb + klass = None if "::" in ids: - ids = ids.replace("::", ".") - if ids.find("/") != -1: - root = ids.replace(".py", "") - ids = root.replace("/", ".") + ids, klass = ids.split("::", 1) + klass = ".".join(klass.split("::")) + if ids.find(os.path.sep) != -1: + root, _ = os.path.splitext(os.path.normpath(ids)) + ids = ".".join(root.split(os.path.sep)) + if klass: + ids = f"{ids}.{klass}" runner = subunit_run.SubunitTestRunner stream = io.BytesIO() program.TestProgram( diff --git a/stestr/tests/test_return_codes.py b/stestr/tests/test_return_codes.py index 99c1e3c..dcd8ce5 100644 --- a/stestr/tests/test_return_codes.py +++ b/stestr/tests/test_return_codes.py @@ -97,15 +97,17 @@ def assertRunExit(self, cmd, expected, subunit=False, stdin=None): if not subunit: self.assertEqual( - p.returncode, expected, "Stdout: {}; Stderr: {}".format(out, err) + p.returncode, + expected, + "Command: {}; Stdout: {}; Stderr: {}".format(cmd, out, err), ) return (out, err) else: self.assertEqual( p.returncode, expected, - "Expected return code: %s doesn't match actual " - "return code of: %s" % (expected, p.returncode), + "Expected return code: {} doesn't match actual " + "return code of: {}. Command: {}".format(expected, p.returncode, cmd), ) output_stream = io.BytesIO(out) stream = subunit_lib.ByteStreamToStreamResult(output_stream) @@ -471,21 +473,33 @@ def test_list_from_func(self): self.assertEqual(0, list_cmd.list_command(stdout=stdout.stream)) def test_run_no_discover_pytest_path(self): - passing_string = "tests/test_passing.py::FakeTestClass::test_pass_list" + passing_string = "::".join( + [ + os.path.join("tests", "test_passing.py"), + "FakeTestClass", + "test_pass_list", + ] + ) out, err = self.assertRunExit("stestr run -n %s" % passing_string, 0) lines = out.decode("utf8").splitlines() self.assertIn(" - Passed: 1", lines) self.assertIn(" - Failed: 0", lines) def test_run_no_discover_pytest_path_failing(self): - passing_string = "tests/test_failing.py::FakeTestClass::test_pass_list" - out, err = self.assertRunExit("stestr run -n %s" % passing_string, 1) + failing_string = "::".join( + [ + os.path.join("tests", "test_failing.py"), + "FakeTestClass", + "test_pass_list", + ] + ) + out, err = self.assertRunExit("stestr run -n %s" % failing_string, 1) lines = out.decode("utf8").splitlines() self.assertIn(" - Passed: 0", lines) self.assertIn(" - Failed: 1", lines) def test_run_no_discover_file_path(self): - passing_string = "tests/test_passing.py" + passing_string = os.path.join("tests", "test_passing.py") out, err = self.assertRunExit("stestr run -n %s" % passing_string, 0) lines = out.decode("utf8").splitlines() self.assertIn(" - Passed: 2", lines) @@ -493,8 +507,26 @@ def test_run_no_discover_file_path(self): self.assertIn(" - Expected Fail: 1", lines) def test_run_no_discover_file_path_failing(self): - passing_string = "tests/test_failing.py" - out, err = self.assertRunExit("stestr run -n %s" % passing_string, 1) + failing_string = os.path.join("tests", "test_failing.py") + out, err = self.assertRunExit("stestr run -n %s" % failing_string, 1) + lines = out.decode("utf8").splitlines() + self.assertIn(" - Passed: 0", lines) + self.assertIn(" - Failed: 2", lines) + self.assertIn(" - Unexpected Success: 1", lines) + + def test_run_no_discover_unnormalized_file_path(self): + # we don't use os.path.join since we want an unnormalized path + passing_string = f"tests{os.path.sep}{os.path.sep}test_passing.py" + out, err = self.assertRunExit("stestr run -n %s" % passing_string, 0) + lines = out.decode("utf8").splitlines() + self.assertIn(" - Passed: 2", lines) + self.assertIn(" - Failed: 0", lines) + self.assertIn(" - Expected Fail: 1", lines) + + def test_run_no_discover_unnormalized_file_path_failing(self): + # we don't use os.path.join since we want an unnormalized path + failing_string = f"tests{os.path.sep}{os.path.sep}test_failing.py" + out, err = self.assertRunExit("stestr run -n %s" % failing_string, 1) lines = out.decode("utf8").splitlines() self.assertIn(" - Passed: 0", lines) self.assertIn(" - Failed: 2", lines) diff --git a/tox.ini b/tox.ini index 4730fba..e24bbc8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,10 @@ [tox] -minversion = 1.6 +minversion = 3.18.0 envlist = py312,py311,py310,py39,py38,pep8 -skipsdist = True [testenv] usedevelop = True install_command = pip install -U --force-reinstall {opts} {packages} -setenv = VIRTUAL_ENV={envdir} allowlist_externals = find stestr @@ -23,18 +21,14 @@ commands = black --check {posargs} stestr tools doc setup.py [testenv:black] -envdir = .tox/pep8 commands = black {posargs} stestr tools doc setup.py - - [testenv:venv] commands = {posargs} [testenv:cover] setenv = - VIRTUAL_ENV={envdir} PYTHON=coverage run --source stestr commands = coverage run stestr/cli.py run {posargs} @@ -43,7 +37,6 @@ commands = [testenv:coverxml] setenv = - VIRTUAL_ENV={envdir} PYTHON=coverage run --source stestr commands = coverage run stestr/cli.py run {posargs}