Skip to content

Commit

Permalink
Merge pull request #82 from pydsigner/GH-76_core_functionality_tests
Browse files Browse the repository at this point in the history
GH-76: Core functionality tests
  • Loading branch information
pydsigner authored Feb 4, 2024
2 parents 4726aa6 + a2e86b8 commit 389f6aa
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 6 deletions.
22 changes: 16 additions & 6 deletions src/anchovy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,11 @@ def find_inputs(self, path: Path):
else:
yield candidate

def process(self, input_paths: list[Path] | None = None):
def match_paths(self, input_paths: list[Path]):
"""
Process a set of files using the Context's defined rules. If
@input_paths is empty or None, `self.find_inputs()` will be used to get
a tree of files to process. If intermediate files are produced,
`self.process()` will be called recursively with them.
Match a set of input paths against the Context's defined Rules, and
associate them with the Steps of those Rules.
"""
input_paths = input_paths or list(self.find_inputs(self.settings['input_dir']))
# We want to handle tasks in the order they're defined!
tasks: dict[Step, list[tuple[Path, list[Path]]]]
tasks = {r.step: [] for r in self.rules if r.step}
Expand Down Expand Up @@ -153,6 +150,19 @@ def process(self, input_paths: list[Path] | None = None):
# surrounding for loop.
break

return tasks

def process(self, input_paths: list[Path] | None = None):
"""
Process a set of files using the Context's defined rules. If
@input_paths is empty or None, `self.find_inputs()` will be used to get
a tree of files to process. If intermediate files are produced,
`self.process()` will be called recursively with them.
"""
input_paths = input_paths or list(self.find_inputs(self.settings['input_dir']))

tasks = self.match_paths(input_paths)

flattened: list[tuple[Step, Path, list[Path]]] = []
for step, paths in tasks.items():
flattened.extend((step, p, ops) for p, ops in paths)
Expand Down
92 changes: 92 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from pathlib import Path

import pytest

from anchovy.core import BuildSettings, Context, Matcher, PathCalc, Rule, Step


class DummyStep(Step):
def __call__(self, path: Path, output_paths: list[Path]):
pass


class AllMatcher(Matcher[Path]):
def __call__(self, context: Context, path: Path):
return path


class BMatcher(Matcher[Path]):
def __call__(self, context: Context, path: Path):
if path.name.startswith('b'):
return path


class DummyPathCalc(PathCalc[Path]):
def __call__(self, context: Context, path: Path, match: Path) -> Path:
return context['output_dir'] / path.relative_to(context['input_dir'])


@pytest.fixture
def build_settings(tmp_path):
return BuildSettings(
input_dir=tmp_path / 'input',
output_dir=tmp_path / 'output',
working_dir=tmp_path / 'working',
custody_cache=tmp_path / 'custody.json',
purge_dirs=False
)


def test_context_match_paths(build_settings: BuildSettings):
i_a = build_settings['input_dir'] / 'a'
i_b = build_settings['input_dir'] / 'b'
i_c = build_settings['input_dir'] / 'c'
o_a = build_settings['output_dir'] / 'a'
o_b = build_settings['output_dir'] / 'b'
o_c = build_settings['output_dir'] / 'c'

paths = [i_a, i_b, i_c]
b_step = DummyStep()
all_step = DummyStep()
context = Context(build_settings, [
Rule(BMatcher(), DummyPathCalc(), b_step),
Rule(AllMatcher(), DummyPathCalc(), all_step),
])
tasks = context.match_paths(paths)
assert tasks == {
b_step: [
(i_b, [o_b]),
],
all_step: [
(i_a, [o_a]),
(i_b, [o_b]),
(i_c, [o_c]),
],
}

def test_context_match_paths_stop_matching(build_settings: BuildSettings):
i_a = build_settings['input_dir'] / 'a'
i_b = build_settings['input_dir'] / 'b'
i_c = build_settings['input_dir'] / 'c'
o_a = build_settings['output_dir'] / 'a'
o_b = build_settings['output_dir'] / 'b'
o_c = build_settings['output_dir'] / 'c'

paths = [i_a, i_b, i_c]

b_step = DummyStep()
all_step = DummyStep()
context = Context(build_settings, [
Rule(BMatcher(), [DummyPathCalc(), None], b_step),
Rule(AllMatcher(), DummyPathCalc(), all_step),
])
tasks = context.match_paths(paths)
assert tasks == {
b_step: [
(i_b, [o_b]),
],
all_step: [
(i_a, [o_a]),
(i_c, [o_c]),
],
}
113 changes: 113 additions & 0 deletions test/test_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from __future__ import annotations

import re
import typing as t
from pathlib import Path

import pytest

from anchovy.core import BuildSettings, Context, Matcher, PathCalc, Rule, Step
from anchovy.paths import DirPathCalc, OutputDirPathCalc, REMatcher, WebIndexPathCalc, WorkingDirPathCalc


INPUT_PATH = Path('input')
WORKING_PATH = Path('working')
OUTPUT_PATH = Path('output')
EXTERNAL_PATH = Path('external')


@pytest.fixture
def dummy_context():
return Context(
BuildSettings(
input_dir=INPUT_PATH,
output_dir=OUTPUT_PATH,
working_dir=WORKING_PATH,
custody_cache=EXTERNAL_PATH / 'custody.json',
purge_dirs=False
),
[]
)


@pytest.mark.parametrize('config,input,expected', [
(('output_dir',), INPUT_PATH / 'foo.txt', OUTPUT_PATH / 'foo.txt'),
((OUTPUT_PATH,), INPUT_PATH / 'foo.txt', OUTPUT_PATH / 'foo.txt'),
((EXTERNAL_PATH,), WORKING_PATH / 'foo.txt', EXTERNAL_PATH / 'foo.txt'),
(('working_dir',), INPUT_PATH / 'foo.txt', WORKING_PATH / 'foo.txt'),
(('working_dir',), WORKING_PATH / 'foo.txt', WORKING_PATH / 'foo.txt'),
(('working_dir', '.html'), WORKING_PATH / 'foo.txt', WORKING_PATH / 'foo.html'),
(('working_dir', '.html'), WORKING_PATH / 'foo.j.txt', WORKING_PATH / 'foo.j.html'),
])
def test_dir_path_calc(config: tuple, input: Path, expected: Path, dummy_context: Context):
calc = DirPathCalc(*config)
assert calc(dummy_context, input, None) == expected


@pytest.mark.parametrize('config,input,regex,expected', [
(('output_dir',), INPUT_PATH / 'foo.j.html', r'.*(?P<ext>\.j\.html)', OUTPUT_PATH / 'foo.j.html'),
(('output_dir', '.zip'), INPUT_PATH / 'foo.j.html', r'.*(?P<ext>\.j\.html)', OUTPUT_PATH / 'foo.zip'),
])
def test_dir_path_calc_regex(config: tuple, input: Path, regex: str, expected: Path, dummy_context: Context):
calc = DirPathCalc(*config)
match = re.match(regex, input.as_posix())
assert calc(dummy_context, input, match) == expected


@pytest.mark.parametrize('config,input,regex,expected', [
(('output_dir', None, lambda p: p), INPUT_PATH / 'foo.j.html', r'.*(?P<ext>\.j\.html)', OUTPUT_PATH / 'foo.j.html'),
(('output_dir', '.zip', lambda p: (p.with_suffix('') / 'index').with_suffix(p.suffix)), INPUT_PATH / 'foo.j.html', r'.*(?P<ext>\.j\.html)', OUTPUT_PATH / 'foo' / 'index.zip'),
])
def test_dir_path_calc_transform(config: tuple, input: Path, regex: str, expected: Path, dummy_context: Context):
calc = DirPathCalc(*config)
match = re.match(regex, input.as_posix())
assert calc(dummy_context, input, match) == expected


@pytest.mark.parametrize('config,input,expected', [
((), INPUT_PATH / 'foo.txt', OUTPUT_PATH / 'foo.txt'),
((), WORKING_PATH / 'foo.txt', OUTPUT_PATH / 'foo.txt'),
(('.html',), WORKING_PATH / 'foo.txt', OUTPUT_PATH / 'foo.html'),
(('.html',), WORKING_PATH / 'foo.j.txt', OUTPUT_PATH / 'foo.j.html'),
])
def test_output_dir_path_calc(config: tuple, input: Path, expected: Path, dummy_context: Context):
calc = OutputDirPathCalc(*config)
assert calc(dummy_context, input, None) == expected


@pytest.mark.parametrize('config,input,expected', [
(('output_dir', None), INPUT_PATH / 'foo.html', OUTPUT_PATH / 'foo' / 'index.html'),
(('output_dir', '.zip', lambda p: p.with_stem(p.stem * 2)), INPUT_PATH / 'foo.html', OUTPUT_PATH / 'foofoo' / 'index.zip'),
])
def test_web_index_path_calc(config: tuple, input: Path, expected: Path, dummy_context: Context):
calc = WebIndexPathCalc(*config)
assert calc(dummy_context, input, None) == expected


@pytest.mark.parametrize('config,input,expected', [
((), (INPUT_PATH / 'foo.txt', None), WORKING_PATH / 'foo.txt'),
((), (WORKING_PATH / 'foo.txt', None), WORKING_PATH / 'foo.txt'),
(('.html',), (WORKING_PATH / 'foo.txt', None), WORKING_PATH / 'foo.html'),
(('.html',), (WORKING_PATH / 'foo.j.txt', None), WORKING_PATH / 'foo.j.html'),
])
def test_working_dir_path_calc(config: tuple, input: tuple[Path, t.Any], expected: Path, dummy_context: Context):
calc = WorkingDirPathCalc(*config)
assert calc(dummy_context, *input) == expected


@pytest.mark.parametrize('config,input,expected', [
((r'.*\.html',), INPUT_PATH / 'foo.html', {}),
((r'f.*\.html',), INPUT_PATH / 'foo.html', None),
((r'f.*\.html', 0, 'input_dir'), INPUT_PATH / 'foo.html', {}),
((r'.*(?P<ext>\.j\.html)', 0, 'input_dir'), INPUT_PATH / 'foo.j.html', {'ext': '.j.html'}),
((r'.*(?P<ext>\.j\.html)', 0, 'input_dir'), INPUT_PATH / 'foo.html', None),
((r'.*(?P<ext>\.j\.html)', 0, 'working_dir'), INPUT_PATH / 'foo.j.html', None),
((r'.*(?P<ext>\.j\.html)', 0, 'working_dir'), WORKING_PATH / 'foo.j.html', {'ext': '.j.html'}),
])
def test_re_matcher(config: tuple, input: Path, expected: dict | None, dummy_context: Context):
matcher = REMatcher(*config)
result = matcher(dummy_context, input)
if result:
assert result.groupdict() == expected
else:
assert result is expected

0 comments on commit 389f6aa

Please sign in to comment.