ara-cli 0.1.9.77__py3-none-any.whl → 0.1.10.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ara-cli might be problematic. Click here for more details.
- ara_cli/__init__.py +18 -2
- ara_cli/__main__.py +245 -66
- ara_cli/ara_command_action.py +128 -63
- ara_cli/ara_config.py +201 -177
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch_templates.py +14 -0
- ara_cli/ara_subcommands/list.py +65 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +214 -28
- ara_cli/artefact_creator.py +5 -8
- ara_cli/artefact_deleter.py +2 -4
- ara_cli/artefact_fuzzy_search.py +13 -6
- ara_cli/artefact_lister.py +29 -55
- ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +23 -13
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +56 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_reader.py +4 -5
- ara_cli/artefact_renamer.py +6 -2
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +594 -219
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_communicator.py +62 -0
- ara_cli/chat_agent/agent_process_manager.py +211 -0
- ara_cli/chat_agent/agent_status_manager.py +73 -0
- ara_cli/chat_agent/agent_workspace_manager.py +76 -0
- ara_cli/commands/__init__.py +0 -0
- ara_cli/commands/command.py +7 -0
- ara_cli/commands/extract_command.py +15 -0
- ara_cli/commands/load_command.py +65 -0
- ara_cli/commands/load_image_command.py +34 -0
- ara_cli/commands/read_command.py +117 -0
- ara_cli/completers.py +144 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/error_handler.py +134 -0
- ara_cli/file_classifier.py +3 -2
- ara_cli/file_loaders/__init__.py +0 -0
- ara_cli/file_loaders/binary_file_loader.py +33 -0
- ara_cli/file_loaders/document_file_loader.py +34 -0
- ara_cli/file_loaders/document_reader.py +245 -0
- ara_cli/file_loaders/document_readers.py +233 -0
- ara_cli/file_loaders/file_loader.py +50 -0
- ara_cli/file_loaders/file_loaders.py +123 -0
- ara_cli/file_loaders/image_processor.py +89 -0
- ara_cli/file_loaders/markdown_reader.py +75 -0
- ara_cli/file_loaders/text_file_loader.py +187 -0
- ara_cli/global_file_lister.py +51 -0
- ara_cli/prompt_extractor.py +214 -87
- ara_cli/prompt_handler.py +508 -146
- ara_cli/tag_extractor.py +54 -24
- ara_cli/template_loader.py +245 -0
- ara_cli/template_manager.py +14 -4
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/update_config_prompt.py +7 -1
- ara_cli/version.py +1 -1
- ara_cli-0.1.10.8.dist-info/METADATA +241 -0
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/RECORD +104 -59
- tests/test_ara_command_action.py +66 -52
- tests/test_ara_config.py +200 -279
- tests/test_artefact_autofix.py +361 -5
- tests/test_artefact_lister.py +52 -132
- tests/test_artefact_scan.py +1 -1
- tests/test_chat.py +2009 -603
- tests/test_file_classifier.py +23 -0
- tests/test_file_creator.py +3 -5
- tests/test_global_file_lister.py +131 -0
- tests/test_prompt_handler.py +746 -0
- tests/test_tag_extractor.py +19 -13
- tests/test_template_loader.py +192 -0
- tests/test_template_manager.py +5 -4
- ara_cli/ara_command_parser.py +0 -536
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- ara_cli-0.1.9.77.dist-info/METADATA +0 -18
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
tests/test_tag_extractor.py
CHANGED
|
@@ -8,10 +8,11 @@ from ara_cli.list_filter import ListFilter
|
|
|
8
8
|
def artefact():
|
|
9
9
|
"""Fixture to create a mock artefact object."""
|
|
10
10
|
class Artefact:
|
|
11
|
-
def __init__(self, tags, status, users, path="dummy.md", content=""):
|
|
11
|
+
def __init__(self, tags, status, users, author="creator_unknown", path="dummy.md", content=""):
|
|
12
12
|
self.tags = tags
|
|
13
13
|
self.status = status
|
|
14
14
|
self.users = users
|
|
15
|
+
self.author = author
|
|
15
16
|
self.path = path
|
|
16
17
|
self.content = content
|
|
17
18
|
return Artefact
|
|
@@ -21,33 +22,33 @@ def artefact():
|
|
|
21
22
|
(
|
|
22
23
|
False, False, None,
|
|
23
24
|
{'artefacts': [
|
|
24
|
-
(['tag1', 'tag2'], 'in-progress', ['user1']),
|
|
25
|
-
(['tag3'], 'done', ['user2'])
|
|
25
|
+
(['tag1', 'tag2'], 'in-progress', ['user1'], "creator_unknown"),
|
|
26
|
+
(['tag3'], 'done', ['user2'], "creator_unknown")
|
|
26
27
|
]},
|
|
27
|
-
['done', 'in-progress', 'tag1', 'tag2', 'tag3', 'user_user1', 'user_user2']
|
|
28
|
+
['creator_unknown', 'done', 'in-progress', 'tag1', 'tag2', 'tag3', 'user_user1', 'user_user2']
|
|
28
29
|
),
|
|
29
30
|
(
|
|
30
31
|
False, True, None,
|
|
31
32
|
{'artefacts': [
|
|
32
|
-
(['project_a', 'priority_high'], None, ['user1']),
|
|
33
|
-
(['feature_x'], 'done', ['user2'])
|
|
33
|
+
(['project_a', 'priority_high'], None, ['user1'], "creator_unknown"),
|
|
34
|
+
(['feature_x'], 'done', ['user2'], "creator_unknown")
|
|
34
35
|
]},
|
|
35
36
|
['project_a']
|
|
36
37
|
),
|
|
37
38
|
(
|
|
38
|
-
False, False, ListFilter(include_tags=['
|
|
39
|
+
False, False, ListFilter(include_tags=['kritik']),
|
|
39
40
|
{'artefacts': [
|
|
40
|
-
(['release', 'kritik'], 'review', ['dev1']),
|
|
41
|
-
(['bugfix'], 'to-do', ['dev2'])
|
|
41
|
+
(['release', 'kritik'], 'review', ['dev1'], "creator_unknown"),
|
|
42
|
+
(['bugfix'], 'to-do', ['dev2'], "creator_unknown")
|
|
42
43
|
]},
|
|
43
|
-
['kritik', 'release', 'review', 'user_dev1']
|
|
44
|
+
['creator_unknown', 'kritik', 'release', 'review', 'user_dev1']
|
|
44
45
|
),
|
|
45
46
|
(
|
|
46
47
|
True, False, None,
|
|
47
48
|
{'artefacts': [
|
|
48
|
-
(['tag3'], 'status2', ['user3'])
|
|
49
|
+
(['tag3'], 'status2', ['user3'], "creator_unknown")
|
|
49
50
|
]},
|
|
50
|
-
['status2', 'tag3', 'user_user3']
|
|
51
|
+
['creator_unknown', 'status2', 'tag3', 'user_user3']
|
|
51
52
|
),
|
|
52
53
|
(
|
|
53
54
|
False, False, None,
|
|
@@ -80,4 +81,9 @@ def test_extract_tags(mock_directory_navigator, mock_artefact_reader, artefact,
|
|
|
80
81
|
|
|
81
82
|
mock_artefact_reader.read_artefacts.assert_called_once()
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
# Convert dictionary result to flat list for comparison
|
|
85
|
+
actual_tags = []
|
|
86
|
+
for group in result.values():
|
|
87
|
+
actual_tags.extend(group)
|
|
88
|
+
|
|
89
|
+
assert sorted(actual_tags) == sorted(expected_tags)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from unittest.mock import MagicMock, patch, mock_open
|
|
4
|
+
from ara_cli.template_loader import TemplateLoader
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def mock_chat_instance():
|
|
9
|
+
"""Fixture for a mocked chat instance."""
|
|
10
|
+
mock = MagicMock()
|
|
11
|
+
mock.choose_file_to_load.return_value = "chosen_file.md"
|
|
12
|
+
mock.load_file.return_value = True
|
|
13
|
+
return mock
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def template_loader_cli():
|
|
18
|
+
"""Fixture for a TemplateLoader in CLI mode."""
|
|
19
|
+
return TemplateLoader()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def template_loader_chat(mock_chat_instance):
|
|
24
|
+
"""Fixture for a TemplateLoader in chat mode."""
|
|
25
|
+
return TemplateLoader(chat_instance=mock_chat_instance)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_init(mock_chat_instance):
|
|
29
|
+
"""Test the constructor."""
|
|
30
|
+
loader_cli = TemplateLoader()
|
|
31
|
+
assert loader_cli.chat_instance is None
|
|
32
|
+
|
|
33
|
+
loader_chat = TemplateLoader(chat_instance=mock_chat_instance)
|
|
34
|
+
assert loader_chat.chat_instance == mock_chat_instance
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize("template_name, default_pattern, expected_method_to_call", [
|
|
38
|
+
("", "*.rules.md", "load_template_from_prompt_data"),
|
|
39
|
+
("my_rule", "*.rules.md", "load_template_from_global_or_local"),
|
|
40
|
+
])
|
|
41
|
+
def test_load_template_routing(template_loader_cli, template_name, default_pattern, expected_method_to_call):
|
|
42
|
+
"""Test that load_template calls the correct downstream method based on inputs."""
|
|
43
|
+
with patch.object(TemplateLoader, 'load_template_from_prompt_data') as mock_from_prompt, \
|
|
44
|
+
patch.object(TemplateLoader, 'load_template_from_global_or_local') as mock_from_global_local:
|
|
45
|
+
|
|
46
|
+
template_loader_cli.load_template(
|
|
47
|
+
template_name, "rules", "chat.md", default_pattern)
|
|
48
|
+
|
|
49
|
+
if expected_method_to_call == "load_template_from_prompt_data":
|
|
50
|
+
mock_from_prompt.assert_called_once()
|
|
51
|
+
mock_from_global_local.assert_not_called()
|
|
52
|
+
else:
|
|
53
|
+
mock_from_prompt.assert_not_called()
|
|
54
|
+
mock_from_global_local.assert_called_once()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_load_template_no_name_no_pattern(template_loader_cli, capsys):
|
|
58
|
+
"""Test load_template fails gracefully when no name or pattern is given."""
|
|
59
|
+
result = template_loader_cli.load_template("", "blueprint", "chat.md", None)
|
|
60
|
+
assert result is False
|
|
61
|
+
captured = capsys.readouterr()
|
|
62
|
+
assert "A template name is required for template type 'blueprint'" in captured.out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.parametrize("template_type, expected_plural", [
|
|
66
|
+
("rules", "rules"),
|
|
67
|
+
("commands", "commands"),
|
|
68
|
+
("intention", "intentions"),
|
|
69
|
+
("blueprint", "blueprints"),
|
|
70
|
+
("custom", "customs"),
|
|
71
|
+
])
|
|
72
|
+
def test_get_plural_template_type(template_loader_cli, template_type, expected_plural):
|
|
73
|
+
"""Test the pluralization of template types."""
|
|
74
|
+
assert template_loader_cli.get_plural_template_type(
|
|
75
|
+
template_type) == expected_plural
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize("template_name, expected_method_to_call", [
|
|
79
|
+
("global/my_rule", "_load_global_template"),
|
|
80
|
+
("my_rule", "_load_local_template"),
|
|
81
|
+
])
|
|
82
|
+
def test_load_template_from_global_or_local_routing(template_loader_cli, template_name, expected_method_to_call):
|
|
83
|
+
"""Test routing between global and local template loading."""
|
|
84
|
+
with patch.object(TemplateLoader, '_load_global_template') as mock_global, \
|
|
85
|
+
patch.object(TemplateLoader, '_load_local_template') as mock_local:
|
|
86
|
+
|
|
87
|
+
template_loader_cli.load_template_from_global_or_local(
|
|
88
|
+
template_name, "rules", "chat.md")
|
|
89
|
+
|
|
90
|
+
if expected_method_to_call == "_load_global_template":
|
|
91
|
+
mock_global.assert_called_once()
|
|
92
|
+
mock_local.assert_not_called()
|
|
93
|
+
else:
|
|
94
|
+
mock_global.assert_not_called()
|
|
95
|
+
mock_local.assert_called_once()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize("files, pattern, user_input, expected_return", [
|
|
99
|
+
(["one.md"], "*.md", "", "one.md"),
|
|
100
|
+
([], "*.md", "", None),
|
|
101
|
+
(["a.md", "b.md"], "*", "1", "a.md"),
|
|
102
|
+
(["a.md", "b.md"], "*", "2", "b.md"),
|
|
103
|
+
(["a.md", "b.md"], "*", "3", None),
|
|
104
|
+
(["a.md", "b.md"], "*", "invalid", None),
|
|
105
|
+
])
|
|
106
|
+
def test_choose_file_for_cli(template_loader_cli, files, pattern, user_input, expected_return):
|
|
107
|
+
"""Test the interactive file selection for the CLI."""
|
|
108
|
+
with patch('builtins.input', return_value=user_input):
|
|
109
|
+
result = template_loader_cli._choose_file_for_cli(files, pattern)
|
|
110
|
+
assert result == expected_return
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_load_file_to_chat_cli_context(tmp_path):
|
|
114
|
+
"""Test writing template content to a chat file in a CLI context."""
|
|
115
|
+
chat_file = tmp_path / "chat.md"
|
|
116
|
+
chat_file.write_text("# ara prompt:\n")
|
|
117
|
+
template_file = tmp_path / "template.md"
|
|
118
|
+
template_content = "This is the template content."
|
|
119
|
+
template_file.write_text(template_content)
|
|
120
|
+
|
|
121
|
+
loader = TemplateLoader()
|
|
122
|
+
result = loader._load_file_to_chat(
|
|
123
|
+
str(template_file), "rules", str(chat_file))
|
|
124
|
+
|
|
125
|
+
assert result is True
|
|
126
|
+
final_content = chat_file.read_text()
|
|
127
|
+
expected_content = f"# ara prompt:\n\n{template_content}\n"
|
|
128
|
+
assert final_content == expected_content
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_load_file_to_chat_chat_context(template_loader_chat, mock_chat_instance):
|
|
132
|
+
"""Test delegating file loading to the chat instance."""
|
|
133
|
+
result = template_loader_chat._load_file_to_chat(
|
|
134
|
+
"file.md", "rules", "chat.md")
|
|
135
|
+
|
|
136
|
+
assert result is True
|
|
137
|
+
mock_chat_instance.add_prompt_tag_if_needed.assert_called_once_with(
|
|
138
|
+
"chat.md")
|
|
139
|
+
mock_chat_instance.load_file.assert_called_once_with("file.md")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_find_project_root(tmp_path):
|
|
143
|
+
"""Test finding the project root directory."""
|
|
144
|
+
project_root = tmp_path / "project"
|
|
145
|
+
ara_dir = project_root / "ara"
|
|
146
|
+
nested_dir = project_root / "src" / "component"
|
|
147
|
+
ara_dir.mkdir(parents=True)
|
|
148
|
+
nested_dir.mkdir(parents=True)
|
|
149
|
+
|
|
150
|
+
no_ara_dir = tmp_path / "other"
|
|
151
|
+
no_ara_dir.mkdir()
|
|
152
|
+
|
|
153
|
+
loader = TemplateLoader()
|
|
154
|
+
|
|
155
|
+
# Test finding the root from a nested directory
|
|
156
|
+
assert loader._find_project_root(str(nested_dir)) == str(project_root)
|
|
157
|
+
# Test finding the root from the root itself
|
|
158
|
+
assert loader._find_project_root(str(project_root)) == str(project_root)
|
|
159
|
+
# Test not finding the root
|
|
160
|
+
assert loader._find_project_root(str(no_ara_dir)) is None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@patch('ara_cli.template_loader.TemplatePathManager')
|
|
164
|
+
@patch('ara_cli.template_loader.ConfigManager')
|
|
165
|
+
def test_get_available_templates(MockConfigManager, MockTemplatePathManager, tmp_path):
|
|
166
|
+
"""Test the discovery of global and local templates."""
|
|
167
|
+
# Setup mock paths and config
|
|
168
|
+
project_root = tmp_path / "project"
|
|
169
|
+
ara_dir = project_root / "ara"
|
|
170
|
+
araconfig_dir = project_root / ".araconfig"
|
|
171
|
+
custom_modules_dir = araconfig_dir / "custom-prompt-modules" / "rules"
|
|
172
|
+
global_modules_dir = tmp_path / "global_templates" / "prompt-modules" / "rules"
|
|
173
|
+
|
|
174
|
+
for d in [ara_dir, custom_modules_dir, global_modules_dir]:
|
|
175
|
+
d.mkdir(parents=True)
|
|
176
|
+
|
|
177
|
+
(custom_modules_dir / "local_rule.md").touch()
|
|
178
|
+
(global_modules_dir / "global_rule.md").touch()
|
|
179
|
+
|
|
180
|
+
mock_config = MagicMock()
|
|
181
|
+
mock_config.local_prompt_templates_dir = ".araconfig"
|
|
182
|
+
mock_config.custom_prompt_templates_subdir = "custom-prompt-modules"
|
|
183
|
+
MockConfigManager.get_config.return_value = mock_config
|
|
184
|
+
MockTemplatePathManager.get_template_base_path.return_value = str(
|
|
185
|
+
tmp_path / "global_templates")
|
|
186
|
+
|
|
187
|
+
loader = TemplateLoader()
|
|
188
|
+
templates = loader.get_available_templates(
|
|
189
|
+
"rules", context_path=str(project_root))
|
|
190
|
+
|
|
191
|
+
assert sorted(templates) == sorted(
|
|
192
|
+
["global/global_rule.md", "local_rule.md"])
|
tests/test_template_manager.py
CHANGED
|
@@ -9,10 +9,11 @@ import os
|
|
|
9
9
|
|
|
10
10
|
@pytest.fixture(autouse=True)
|
|
11
11
|
def navigate_to_ara_directory():
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
with patch('builtins.input', return_value='y'):
|
|
13
|
+
navigator = DirectoryNavigator("ara")
|
|
14
|
+
original_directory = navigator.navigate_to_target()
|
|
15
|
+
yield
|
|
16
|
+
os.chdir(original_directory)
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@pytest.mark.parametrize(
|