Skip to content

Commit

Permalink
added feature only fetch the tests
Browse files Browse the repository at this point in the history
  • Loading branch information
omkarkhatavkar committed Nov 19, 2020
1 parent a959b8c commit 90587c1
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Modified
- Add fixture for git user configuration
- Skip "Publish to Test PyPi" workflow when there is no change on setup.py
- Added support to pick the only changed/modified and newly added tests

## [0.4.4] - 2020-03-21
### Fixed
Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Usage

$ pytest --picked --mode=unstaged # default

$ pytest --picked --mode=onlychanged

Features
--------
Expand Down
73 changes: 72 additions & 1 deletion pytest_picked/modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ def __init__(self, test_file_convention):

def affected_tests(self):
raw_output = self.git_output()

re_list = [
item.replace(".", r"\.").replace("*", ".*")
for item in self.test_file_convention
Expand All @@ -26,6 +25,45 @@ def affected_tests(self):
files.append(file_or_folder)
return files, folders

def only_tests(self):
raw_output = self.git_output()
re_list = [
item.replace(".", r"\.").replace("*", ".*")
for item in self.test_file_convention
]
test_file_regex = r"(\/|^)" + r"|".join(re_list)
test_class_regex = r"(?<=class\s).*Test.*.[\w](?=\(|\:)"
test_regex = r"(?<=def\s)test.*(?=\(|/:)"

tests = []
last_file_name = None
file_dict = {}
class_name = ""
for candidate in raw_output.splitlines():
file_or_test = self.parser(candidate)
if file_or_test:
if re.search(test_file_regex, file_or_test):
last_file_name = file_or_test
if last_file_name not in file_dict.keys():
file_dict[last_file_name] = []
if re.search(test_class_regex, file_or_test):
match = re.findall(test_class_regex, file_or_test)
class_name = match[0]
file_dict[last_file_name].append(class_name)

elif re.search(test_regex, file_or_test):
match = re.findall(test_regex, file_or_test)
test_name = match[0]
file_dict[last_file_name].append(class_name + "::" + test_name)
class_name = ""
for file_name in file_dict.keys():
for test_name in file_dict[file_name]:
tests.append(
file_name + "::" + test_name
) if "::" not in test_name else tests.append(file_name + test_name)

return list(dict.fromkeys(tests)), []

def git_output(self):
output = subprocess.run(self.command(), stdout=subprocess.PIPE) # nosec
return output.stdout.decode("utf-8").expandtabs()
Expand Down Expand Up @@ -127,3 +165,36 @@ def parser(self, candidate):
indicator_index = candidate.find(rename_indicator)
start_path_index = indicator_index + len(rename_indicator)
return candidate[start_path_index:]


class OnlyChanged(Mode):
def command(self):
return ["git", "diff"]

def parser(self, candidate):
"""
Discard the first 6 characters in case of file name.
Parse the modified file using the file indicator '+++ b/'
Parse affected tests from using regex 'def' and '@@'
The command output would look like this:
diff --git a/tests/migrations/auto.py b/tests/migrations/auto.py
index 2ce07c4..ebd406e 100644
--- a/tests/migrations/auto.py
+++ b/tests/migrations/auto.py
@@ -181,6 +181,7 @@ def test_positive_migration(session, ad_data):
Reference:
https://git-scm.com/docs/git-diff#git-diff
"""
start_path_index = 6
file_indicator = "+++ b/"
modified_test_indicator = "@@ "
new_test_indicator = "+"

if candidate.startswith(file_indicator):
return candidate[start_path_index:]
if candidate.startswith(modified_test_indicator):
return candidate
if candidate.startswith(new_test_indicator):
return candidate
11 changes: 7 additions & 4 deletions pytest_picked/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import _pytest

from .modes import Branch, Unstaged
from .modes import Branch, OnlyChanged, Unstaged


def pytest_addoption(parser):
Expand All @@ -25,7 +25,7 @@ def pytest_addoption(parser):
dest="picked_mode",
default="unstaged",
required=False,
help="Options: unstaged, branch",
help="Options: unstaged, branch, onlychanged",
)


Expand All @@ -36,6 +36,7 @@ def _get_affected_paths(config):
modes = {
"branch": Branch(test_file_convention),
"unstaged": Unstaged(test_file_convention),
"onlychanged": OnlyChanged(test_file_convention),
}
try:
mode = modes[picked_mode]
Expand All @@ -45,7 +46,10 @@ def _get_affected_paths(config):
config.args = []
raise ValueError(error)
else:
return mode.affected_tests()
if picked_mode == "onlychanged":
return mode.only_tests()
else:
return mode.affected_tests()


def pytest_configure(config):
Expand All @@ -62,7 +66,6 @@ def pytest_collection_modifyitems(session, config, items):
picked_type = config.getoption("picked")
if not picked_type or picked_type != "first":
return

affected_files, affected_folders = _get_affected_paths(config)
match_paths = affected_files + affected_folders
# only reorder if there was anything matched
Expand Down
68 changes: 67 additions & 1 deletion tests/test_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from pytest_picked.modes import Branch, Unstaged
from pytest_picked.modes import Branch, OnlyChanged, Unstaged


@pytest.fixture
Expand Down Expand Up @@ -164,3 +164,69 @@ def test_should_only_list_pytest_root_changed_files(self, git_repository, testdi
pytestroot.chdir()
output = set(Branch([]).git_output().splitlines())
assert output == {"A test_pytestroot.py"}


class TestOnlyChanged:
def test_should_return_git_diff_command(self):
mode = OnlyChanged([])
command = mode.command()

assert isinstance(command, list)
assert mode.command() == ["git", "diff"]

@pytest.mark.parametrize(
"line,expected_line",
[
(
"@@ -191,29 +191,8 @@ class TestPytestPicked:",
"@@ -191,29 +191,8 @@ class TestPytestPicked:",
),
("- def test_positive_update_limit(self):", None),
("--- a/tests/foreman/cli/test_host_collection.py", None),
(
"+++ b/tests/pytestpicked/test_positive",
"tests/pytestpicked/test_positive",
),
("+ @pytest.mark.tier2", "+ @pytest.mark.tier2"),
("+ def test", "+ def test"),
("-def test_positive", None),
],
)
def test_parser_should_ignore_no_paths_characters(self, line, expected_line):
mode = OnlyChanged([])
parsed_line = mode.parser(line)

assert parsed_line == expected_line

def test_should_list_changed_tests(self):
raw_output = (
b"\n+++ b/tests/pytestpicked/test_modes1.py"
b"\n@@ -191,29 +191,8 @@ class TestPytestPicked(CLITestCase):"
b"\n+ def test_with_class(self):"
b"\n+++ b/tests/pytestpicked/test_modes2.py"
b"\n@@ -191,29 +191,8 @@ class TestPytestPicked:"
b"\n+ def test_with_class(self):"
b"\n+++ b/tests/pytestpicked/test_modes3.py"
b"\n+ def test_without_class(self):"
b"\n+++ b/tests/pytestpicked/test_modes4.py"
b"\n+ def invalid_function(self):"
b"\n+++ b/tests/pytestpicked/test_modes5.py"
b"\n+ def invalid_test(self):"
b"\n+++ b/tests/pytestpicked/test_modes6.py"
b"\n@@ -191,29 +191,8 @@ class InvalidClass:"
)
test_file_convention = ["test_*.py", "*_test.py"]

with patch("pytest_picked.modes.subprocess.run") as subprocess_mock:
subprocess_mock.return_value.stdout = raw_output
mode = OnlyChanged(test_file_convention)
tests, folders = mode.only_tests()
expected_tests = [
"tests/pytestpicked/test_modes1.py::TestPytestPicked",
"tests/pytestpicked/test_modes1.py::test_with_class",
"tests/pytestpicked/test_modes2.py::TestPytestPicked",
"tests/pytestpicked/test_modes2.py::test_with_class",
"tests/pytestpicked/test_modes3.py::test_without_class",
]

assert tests == expected_tests

0 comments on commit 90587c1

Please sign in to comment.