prepare-assignment 0.4.2__tar.gz → 0.4.4__tar.gz
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.
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/PKG-INFO +1 -1
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/preparer.py +11 -1
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/task_handler.py +2 -2
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/validator.py +24 -1
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/task_properties.py +22 -13
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/tasks.py +2 -2
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/pyproject.toml +1 -1
- prepare_assignment-0.4.4/tests/conftest.py +25 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_preparer.py +34 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_task_handler.py +11 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_validator.py +32 -1
- prepare_assignment-0.4.4/tests/data/test_task_properties.py +45 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/dependabot.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/workflows/pr-title.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/workflows/pypi-publish.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/workflows/release.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/workflows/semantic-release.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/workflows/test.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.gitignore +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.releaserc.json +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/LICENSE +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/README.md +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/__main__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/cli/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/cli/main.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/cli/task.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/cli/tasks.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/check.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/command.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/expression.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/main.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/runner.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/shell.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/subsituter.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/versions.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/config.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/constants.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/errors.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/job_environment.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/prepare.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/task_definition.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/types.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/schemas/config.schema.json +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/schemas/prepare.schema.json +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/schemas/task.schema.json +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/config.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/default_validator.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/dependency.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/files.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/logger.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/paths.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/resources.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/virtual_env.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/yml_loader.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_check.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_command.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_expression.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_resolve_version.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_runner.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_runner_process.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_shell.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/core/test_substituter.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/data/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/data/test_prepare.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/data/test_types.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/test_dependencies.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/test_main.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/prepare.yml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/solution/pom.xml +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/solution/src/main/java/gradelist/AssessmentResult.java +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/solution/src/main/java/gradelist/GradeList.java +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/solution/src/main/java/gradelist/Test.java +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/solution/src/test/java/gradelist/AssessmentResultTest.java +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/testproject/solution/src/test/java/gradelist/GradeListTest.java +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/__init__.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/test_config.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/test_default_validator.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/test_files.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/test_logger.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/test_paths.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/tests/utils/test_resources.py +0 -0
- {prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/uv.lock +0 -0
|
@@ -12,7 +12,7 @@ from virtualenv import cli_run
|
|
|
12
12
|
|
|
13
13
|
from prepare_assignment.core.versions import COMMIT_HASH_RE, get_git_url, resolve_version
|
|
14
14
|
from prepare_assignment.core.validator import validate_task_definition, validate_tasks, load_yaml, \
|
|
15
|
-
validate_default_values
|
|
15
|
+
validate_default_values, validate_unique_ids
|
|
16
16
|
from prepare_assignment.data.errors import DependencyError, ValidationError, PrepareTaskError
|
|
17
17
|
from prepare_assignment.data.task_definition import TaskDefinition, CompositeTaskDefinition, \
|
|
18
18
|
PythonTaskDefinition, ValidableTask
|
|
@@ -154,10 +154,19 @@ def __sub_tasks(task: TaskDefinition) -> List[Any]:
|
|
|
154
154
|
return [step for step in task.tasks if step.get("uses", None) is not None]
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
def __validate_sub_task_ids(props: TaskProperties, task: TaskDefinition) -> None:
|
|
158
|
+
if isinstance(task, CompositeTaskDefinition):
|
|
159
|
+
validate_unique_ids(str(props.definition_path), f"task '{task.name}'", task.tasks)
|
|
160
|
+
|
|
161
|
+
|
|
157
162
|
def __load_task_from_disk(props: TaskProperties, parsed: Dict[str, ValidableTask]) -> None:
|
|
158
163
|
logger.debug(f"Task '{props}' is already available, loading from disk")
|
|
159
164
|
task_yaml = load_yaml(props.definition_path)
|
|
160
165
|
task = TaskDefinition.of(task_yaml, props.task_path)
|
|
166
|
+
try:
|
|
167
|
+
__validate_sub_task_ids(props, task)
|
|
168
|
+
except ValidationError as e:
|
|
169
|
+
raise PrepareTaskError(f"Unable to prepare task '{props}'", e) from e
|
|
161
170
|
# Always rebuild the schema, so schemas generated by older versions of prepare are updated
|
|
162
171
|
json_schema = __build_json_schema(props, task)
|
|
163
172
|
with open(__schema_path(props), "r", encoding="utf-8") as handle:
|
|
@@ -181,6 +190,7 @@ def __prepare_task(props: TaskProperties) -> ValidableTask:
|
|
|
181
190
|
task_yaml = validate_task_definition(props.definition_path)
|
|
182
191
|
task: TaskDefinition = TaskDefinition.of(task_yaml, props.task_path)
|
|
183
192
|
validate_default_values(task)
|
|
193
|
+
__validate_sub_task_ids(props, task)
|
|
184
194
|
if isinstance(task, PythonTaskDefinition):
|
|
185
195
|
main_path = os.path.join(props.repo_path, Path(task.main)) # type: ignore
|
|
186
196
|
if not Path(main_path).resolve().is_relative_to(props.repo_path.resolve()):
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/core/task_handler.py
RENAMED
|
@@ -9,7 +9,7 @@ from treelib import Tree
|
|
|
9
9
|
from prepare_assignment.core.preparer import __prepare_tasks
|
|
10
10
|
from prepare_assignment.core.versions import get_git_url, is_fixed_version
|
|
11
11
|
from prepare_assignment.data.task_definition import TaskDefinition
|
|
12
|
-
from prepare_assignment.data.task_properties import TaskProperties
|
|
12
|
+
from prepare_assignment.data.task_properties import TaskProperties, directory_to_version
|
|
13
13
|
from prepare_assignment.utils.files import remove_tree
|
|
14
14
|
from prepare_assignment.utils.dependency import get_dependencies
|
|
15
15
|
from prepare_assignment.utils.paths import get_tasks_path
|
|
@@ -88,7 +88,7 @@ def ls() -> None:
|
|
|
88
88
|
version_path = os.path.join(org_path, task)
|
|
89
89
|
versions = sorted(os.listdir(version_path))
|
|
90
90
|
for version in versions:
|
|
91
|
-
tree.create_node(version, parent=task)
|
|
91
|
+
tree.create_node(directory_to_version(version), parent=task)
|
|
92
92
|
t = tree.show(stdout=False)
|
|
93
93
|
print(t)
|
|
94
94
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Dict, Any
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
7
|
|
|
8
8
|
from jsonschema.exceptions import ValidationError
|
|
9
9
|
from jsonschema.validators import validate
|
|
@@ -37,9 +37,32 @@ def validate_prepare(prepare_file: str, prepare: Dict[str, Any]) -> None:
|
|
|
37
37
|
except ValidationError as ve:
|
|
38
38
|
message = f"Error in: {prepare_file}, unable to verify '{ve.json_path}'\n\t -> {ve.message}"
|
|
39
39
|
raise VE(message)
|
|
40
|
+
for job, steps in prepare["jobs"].items():
|
|
41
|
+
validate_unique_ids(prepare_file, f"job '{job}'", steps)
|
|
40
42
|
logger.debug("✓ Prepare file is valid")
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
def validate_unique_ids(file: str, where: str, steps: List[Dict[str, Any]]) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Validate that the ids of the steps are unique, the outputs of a step are stored under its id
|
|
48
|
+
NOTE: steps without an id are not checked
|
|
49
|
+
:param file: path/name of the file that contains the steps
|
|
50
|
+
:param where: the job or composite task that contains the steps, used in the error message
|
|
51
|
+
:param steps: the steps of the job or composite task
|
|
52
|
+
:return: None
|
|
53
|
+
:raises: ValidationError: if multiple steps have the same id
|
|
54
|
+
"""
|
|
55
|
+
names: Dict[str, List[str]] = {}
|
|
56
|
+
for step in steps:
|
|
57
|
+
step_id = step.get("id", None)
|
|
58
|
+
if step_id is not None:
|
|
59
|
+
names.setdefault(step_id, []).append(step.get("name", ""))
|
|
60
|
+
for step_id, step_names in names.items():
|
|
61
|
+
if len(step_names) > 1:
|
|
62
|
+
quoted = ", ".join(f"'{name}'" for name in step_names)
|
|
63
|
+
raise VE(f"Error in: {file}, {where} has multiple steps with id '{step_id}': {quoted}")
|
|
64
|
+
|
|
65
|
+
|
|
43
66
|
def validate_tasks(file: str, task: Dict[str, Any], json_schema: Any) -> None:
|
|
44
67
|
"""
|
|
45
68
|
Validate all tasks based on their respective json schemas
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/task_properties.py
RENAMED
|
@@ -18,7 +18,14 @@ class TaskProperties:
|
|
|
18
18
|
|
|
19
19
|
@cached_property
|
|
20
20
|
def task_path(self) -> Path:
|
|
21
|
-
return Path(os.path.join(tasks_path, self.organization, self.name, self.
|
|
21
|
+
return Path(os.path.join(tasks_path, self.organization, self.name, self.version_directory))
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def version_directory(self) -> str:
|
|
25
|
+
"""
|
|
26
|
+
The version as a single directory name, a branch can contain a slash (e.g. fix/something)
|
|
27
|
+
"""
|
|
28
|
+
return version_to_directory(self.version)
|
|
22
29
|
|
|
23
30
|
@cached_property
|
|
24
31
|
def repo_path(self) -> Path:
|
|
@@ -38,19 +45,21 @@ class TaskProperties:
|
|
|
38
45
|
|
|
39
46
|
@classmethod
|
|
40
47
|
def of(cls, task: str) -> TaskProperties:
|
|
41
|
-
|
|
48
|
+
# Everything after the first '@' is the version, a branch can contain a slash
|
|
49
|
+
name, separator, version = task.partition("@")
|
|
50
|
+
if not separator:
|
|
51
|
+
version = "latest"
|
|
52
|
+
parts = name.split("/")
|
|
42
53
|
if len(parts) > 2:
|
|
43
54
|
raise ValueError("Tasks cannot have more than one slash")
|
|
44
55
|
elif len(parts) == 1:
|
|
45
56
|
parts.insert(0, "prepare-assignment")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
version = split[1]
|
|
56
|
-
return cls(organization, task_name, version)
|
|
57
|
+
return cls(parts[0], parts[1], version)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def version_to_directory(version: str) -> str:
|
|
61
|
+
return version.replace("%", "%25").replace("/", "%2F")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def directory_to_version(directory: str) -> str:
|
|
65
|
+
return directory.replace("%2F", "/").replace("%25", "%")
|
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
from typing import List
|
|
3
3
|
|
|
4
4
|
from prepare_assignment.data.task_definition import TaskDefinition
|
|
5
|
-
from prepare_assignment.data.task_properties import TaskProperties
|
|
5
|
+
from prepare_assignment.data.task_properties import TaskProperties, directory_to_version
|
|
6
6
|
from prepare_assignment.utils.paths import get_tasks_path
|
|
7
7
|
from prepare_assignment.utils.yml_loader import YAML_LOADER
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ def get_all_tasks() -> List[TaskProperties]:
|
|
|
20
20
|
version_path = os.path.join(org_path, task)
|
|
21
21
|
versions = os.listdir(version_path)
|
|
22
22
|
for version in versions:
|
|
23
|
-
tasks.append(TaskProperties
|
|
23
|
+
tasks.append(TaskProperties(org, task, directory_to_version(version)))
|
|
24
24
|
return tasks
|
|
25
25
|
|
|
26
26
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
# Loggers that prepare() configures (level, handlers), see prepare_assignment/core/main.py
|
|
7
|
+
_LOGGERS = ("prepare_assignment", "tasks")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def reset_loggers() -> Iterator[None]:
|
|
12
|
+
"""
|
|
13
|
+
Restore the prepare loggers after every test. prepare() sets their level (e.g. ERROR for verbosity 0), which made
|
|
14
|
+
tests that check log output with caplog fail when they ran after a test that calls prepare().
|
|
15
|
+
"""
|
|
16
|
+
saved = {}
|
|
17
|
+
for name in _LOGGERS:
|
|
18
|
+
logger = logging.getLogger(name)
|
|
19
|
+
saved[name] = (logger.level, list(logger.handlers), logger.propagate)
|
|
20
|
+
yield
|
|
21
|
+
for name, (level, handlers, propagate) in saved.items():
|
|
22
|
+
logger = logging.getLogger(name)
|
|
23
|
+
logger.setLevel(level)
|
|
24
|
+
logger.handlers[:] = handlers
|
|
25
|
+
logger.propagate = propagate
|
|
@@ -283,3 +283,37 @@ def test_outdated_schema_is_updated_on_load(mocker: MockerFixture) -> None:
|
|
|
283
283
|
spy.assert_not_called()
|
|
284
284
|
with open(schema_file) as handle:
|
|
285
285
|
assert "if" in json.load(handle)["properties"]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
DUPLICATE_ID_COMPOSITE: Final[Dict[str, Any]] = {
|
|
289
|
+
'id': 'duplicate',
|
|
290
|
+
'name': 'duplicate',
|
|
291
|
+
'description': 'Composite task with a duplicate step id',
|
|
292
|
+
'runs': {
|
|
293
|
+
'using': 'composite',
|
|
294
|
+
'tasks': [{'name': 'first', 'id': 'same', 'run': 'echo 1'},
|
|
295
|
+
{'name': 'second', 'id': 'same', 'run': 'echo 2'}]
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_composite_duplicate_id_is_rejected(mocker: MockerFixture) -> None:
|
|
301
|
+
__clean_cache()
|
|
302
|
+
mocker.patch("prepare_assignment.core.preparer.__download_task")
|
|
303
|
+
mocker.patch("prepare_assignment.core.preparer.validate_task_definition", return_value=DUPLICATE_ID_COMPOSITE)
|
|
304
|
+
remove = mocker.patch("prepare_assignment.core.preparer.remove_tree")
|
|
305
|
+
prepare = {'prepare': [{'name': 'duplicate', 'uses': 'duplicate', 'with': {}}]}
|
|
306
|
+
with pytest.raises(PrepareTaskError) as exc:
|
|
307
|
+
prepare_tasks("prepare.yml", prepare)
|
|
308
|
+
assert "multiple steps with id 'same'" in str(exc.value.cause)
|
|
309
|
+
remove.assert_called()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_cached_composite_duplicate_id_is_rejected(mocker: MockerFixture) -> None:
|
|
313
|
+
__clean_cache()
|
|
314
|
+
mocker.patch("prepare_assignment.core.preparer.__is_installed", return_value=True)
|
|
315
|
+
mocker.patch("prepare_assignment.core.preparer.load_yaml", return_value=DUPLICATE_ID_COMPOSITE)
|
|
316
|
+
prepare = {'prepare': [{'name': 'duplicate', 'uses': 'duplicate', 'with': {}}]}
|
|
317
|
+
with pytest.raises(PrepareTaskError) as exc:
|
|
318
|
+
prepare_tasks("prepare.yml", prepare)
|
|
319
|
+
assert "multiple steps with id 'same'" in str(exc.value.cause)
|
|
@@ -93,3 +93,14 @@ def test_load_task_uses_task_path(tasks_dir: Path) -> None:
|
|
|
93
93
|
props = TaskProperties.of("remove")
|
|
94
94
|
_install_fake(props, "x")
|
|
95
95
|
assert load_task(props).path == props.task_path
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_version_with_slash(tasks_dir: Path, mocker: MockerFixture, capsys: pytest.CaptureFixture) -> None:
|
|
99
|
+
from prepare_assignment.utils.tasks import get_all_tasks
|
|
100
|
+
mocker.patch("prepare_assignment.utils.tasks.get_tasks_path", return_value=tasks_dir)
|
|
101
|
+
mocker.patch("prepare_assignment.core.task_handler.tasks_path", tasks_dir)
|
|
102
|
+
props = TaskProperties.of("remove@fix/something")
|
|
103
|
+
_install_fake(props, "branch")
|
|
104
|
+
assert get_all_tasks() == [props]
|
|
105
|
+
task_handler.ls()
|
|
106
|
+
assert "fix/something" in capsys.readouterr().out
|
|
@@ -5,7 +5,8 @@ import pytest
|
|
|
5
5
|
from pytest_mock import MockerFixture
|
|
6
6
|
|
|
7
7
|
from prepare_assignment.core.validator import (validate_prepare, validate_tasks, load_yaml,
|
|
8
|
-
validate_task_definition, validate_default_values
|
|
8
|
+
validate_task_definition, validate_default_values,
|
|
9
|
+
validate_unique_ids)
|
|
9
10
|
from prepare_assignment.data.task_definition import PythonTaskDefinition
|
|
10
11
|
from prepare_assignment.data.errors import ValidationError
|
|
11
12
|
|
|
@@ -179,3 +180,33 @@ def test_validate_default_values_bool_for_integer_invalid() -> None:
|
|
|
179
180
|
task_copy["inputs"]["num"] = {'description': 'n', 'type': 'integer', 'default': True, 'required': False}
|
|
180
181
|
with pytest.raises(ValidationError):
|
|
181
182
|
validate_default_values(PythonTaskDefinition.of(task_copy, "test.yml"))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_validate_unique_ids_valid() -> None:
|
|
186
|
+
steps = [{'name': 'a', 'id': 'a', 'run': 'echo'}, {'name': 'b', 'id': 'b', 'run': 'echo'},
|
|
187
|
+
{'name': 'no id', 'run': 'echo'}, {'name': 'no id', 'run': 'echo'}]
|
|
188
|
+
validate_unique_ids("prepare.yml", "job 'build'", steps)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_validate_unique_ids_duplicate() -> None:
|
|
192
|
+
steps = [{'name': 'Remove old artefacts', 'id': 'remove', 'uses': 'remove'},
|
|
193
|
+
{'name': 'other', 'id': 'other', 'uses': 'remove'},
|
|
194
|
+
{'name': 'Remove old temp directory', 'id': 'remove', 'uses': 'remove'}]
|
|
195
|
+
with pytest.raises(ValidationError) as exc:
|
|
196
|
+
validate_unique_ids("prepare.yml", "job 'build'", steps)
|
|
197
|
+
assert exc.value.message == ("Error in: prepare.yml, job 'build' has multiple steps with id 'remove': "
|
|
198
|
+
"'Remove old artefacts', 'Remove old temp directory'")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_validate_prepare_duplicate_id() -> None:
|
|
202
|
+
prepare = {'name': 'Duplicate', 'jobs': {'build': [{'name': 'a', 'id': 'same', 'run': 'echo'},
|
|
203
|
+
{'name': 'b', 'id': 'same', 'run': 'echo'}]}}
|
|
204
|
+
with pytest.raises(ValidationError) as exc:
|
|
205
|
+
validate_prepare("prepare.yml", prepare)
|
|
206
|
+
assert "job 'build'" in exc.value.message
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_validate_prepare_same_id_in_different_jobs() -> None:
|
|
210
|
+
prepare = {'name': 'Different jobs', 'jobs': {'build': [{'name': 'a', 'id': 'same', 'run': 'echo'}],
|
|
211
|
+
'test': [{'name': 'b', 'id': 'same', 'run': 'echo'}]}}
|
|
212
|
+
validate_prepare("prepare.yml", prepare)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from prepare_assignment.data.task_properties import TaskProperties, directory_to_version, version_to_directory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize("task, expected", [
|
|
9
|
+
("remove", TaskProperties("prepare-assignment", "remove", "latest")),
|
|
10
|
+
("remove@v1", TaskProperties("prepare-assignment", "remove", "v1")),
|
|
11
|
+
("prepare-assignment/remove", TaskProperties("prepare-assignment", "remove", "latest")),
|
|
12
|
+
("org/remove@v1.0.0", TaskProperties("org", "remove", "v1.0.0")),
|
|
13
|
+
("prc2-assignment@fix/tasks-and-shell", TaskProperties("prepare-assignment", "prc2-assignment",
|
|
14
|
+
"fix/tasks-and-shell")),
|
|
15
|
+
("prepare-assignment/remove@fix/something", TaskProperties("prepare-assignment", "remove", "fix/something")),
|
|
16
|
+
("remove@feat/a@b", TaskProperties("prepare-assignment", "remove", "feat/a@b")),
|
|
17
|
+
])
|
|
18
|
+
def test_of(task: str, expected: TaskProperties) -> None:
|
|
19
|
+
assert TaskProperties.of(task) == expected
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_of_more_than_one_slash_in_name() -> None:
|
|
23
|
+
with pytest.raises(ValueError):
|
|
24
|
+
TaskProperties.of("a/b/c@v1")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_str_keeps_slash_in_version() -> None:
|
|
28
|
+
assert str(TaskProperties.of("remove@fix/something")) == "prepare-assignment/remove@fix/something"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_task_path_is_single_directory_for_version() -> None:
|
|
32
|
+
props = TaskProperties.of("remove@fix/something")
|
|
33
|
+
assert props.task_path.name == "fix%2Fsomething"
|
|
34
|
+
assert props.task_path.parent.name == "remove"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_task_path_unchanged_for_plain_version() -> None:
|
|
38
|
+
assert TaskProperties.of("remove@v1.0.0").task_path.name == "v1.0.0"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("version", ["v1", "fix/something", "a/b/c", "100%", "%2F", "a%2F/b%25"])
|
|
42
|
+
def test_version_directory_round_trip(version: str) -> None:
|
|
43
|
+
directory = version_to_directory(version)
|
|
44
|
+
assert os.sep not in directory
|
|
45
|
+
assert directory_to_version(directory) == version
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/.github/workflows/semantic-release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/job_environment.py
RENAMED
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/data/task_definition.py
RENAMED
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/schemas/config.schema.json
RENAMED
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/schemas/prepare.schema.json
RENAMED
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/schemas/task.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/default_validator.py
RENAMED
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/dependency.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/virtual_env.py
RENAMED
|
File without changes
|
{prepare_assignment-0.4.2 → prepare_assignment-0.4.4}/prepare_assignment/utils/yml_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|