Package: pytest-expectdir
pytest-expectdirThis pytest plugin provides an easy way to test file generation and file-system transformation.Installpip install pytest-expectdirUsageHere is the workflow :Create a directory containing files and directories expected to be generated, and optionally one with your initial data so that it looks like this :my_pkg/
├ my_pkg/
│ └ ...
└ tests/
  ├ test_feature.py
  └ test_feature/
    ├ initial (optional)/
    │ └ ... your initial data
    └ expected/
      └ ... expected output treeThen you write your test as follow :test_feature.pydef test_feature(expectdir):
  with expectdir('test_feature') as output_dir:
    # Do whatever you want inside output_dir, which is a temporary directory copied from initial/
  # At the end of the with, output_dir gets compared with expected
  # ...And you get a fancy report of the difference if there are (as an AssertionError).Note that you can also pass manually the keyword argumentsinitialandexpectedtoexpectdir. If, for example, you have multiple tests ending up with the same expected result, or with the same initial one.The following is equivalent to the previous example :def test_feature(expectdir):
  with expectdir(initial='data_test_feature/initial', expected='data_test_feature/expected') as output_dir:
    # ...If your test data follows this schema :tests/
├ test_feature.py
└ test_feature/
  └ TestCaseClassName (if one)/
    └ test_method
      ├ initial (optional)/
      │ └ ...
      └ expected
        └ ...(like the first example), then you can even omit the parameters :def test_feature(expectdir):
  with expectdir() as output_dir:
    # ...API(pytest.fixture)expectdir(datapath=None, *, initial=None, expected=None, current_dir_replace_string=None) -> contextmanager as outputDir:PathThe main fixture. Its value is a function that returns a context manager. The context manager will return (when opened) a path to a temporary directory that will get compared to the Expected directory at closing. An AssertionError will then be raised if the two directory are not the same..gitkeepfiles, conventionally used to keep empty directories are ignored.You also may require to the content of some file containing the path where the test is executed. Just before executing what is in thewith, the string passed tocurrent_dir_replace_stringis replaced by temporary directory path in all files ininitial/. Also, after the with block, and before checking the temporary directory is equal to the expected one, all occurences of the temporary directory path is replaced bycurrent_dir_replace_string. IfNoneis passed, no replacement is done.The function chooses an optional initial directory and a required expected directory as follow :ExpectedIf theexpectedkeyword argument is provided, it's this directory that will be used.Else, if thedatapathpositional argument is provided, expected will bedatapath/"expected".Else, the test path will be used as fallback, i.e.currentModuleDirectory/TestCaseClassName/test_method/expectedif inside a testCase class, else,currentModuleDirectory/test_function/expectedif the test is a standalone function.If the selected path does not exist, raises a FileNotFoundError.InitialIf theinitialkeyword argument is provided and equal to__empty__, then the initial directory will be empty.If theinitialkeyword argument is provided and is a different string or aPathinstance, it's this directory that will be used.Else, if thedatapathpositional argument is provided, expected will bedatapath/"initial".Else, if theexpectedkeyword argument isnotprovided, the test path will be used as fallback, i.e.currentModuleDirectory/TestCaseClassName/test_method/initialif inside a TestCase class, else,currentModuleDirectory/test_function/initialif the test is a standalone function.Else, the initial directory will be empty.If the initial keyword argument is a Path, and this path does not exists, raises a FileNotFoundError.(pytest.fixture)expectdir(datapath=None, *, initial=None, expected=None, current_dir_replace_string="{{current_directory}}") -> contextmanager as outputDir:PathEquivalent to expectDir, but with"{{current_directory}}"as default value forcurrent_dir_replace_string.cmpdir(candidate:Path, expected:Path) -> Tuple[result:bool, Tuple[candidate_only:list[Path], expected_only:list[Path], different:list[Path]]]Compare two directories recursively, and list files only in the first, only on the second, and in both but different.The result isTrueif the directories are identical.When a subdirectory is present only in one of the compared directories, only the subdirectory itself is listed (not all its content).Files.gitkeepare ignored.formatDiff(file_output:TextIO, candidate:Path, expected:Path, diffRes:Tuple[candidate_only:list[Path], expected_only:list[Path], different:list[Path]]) -> NoneTakes the result ofcmpdir, and print tofile_outputthe diff summary.formatFileDiff(file_output:TextIO, lines_candidate:Iterable[str], lines_expected:Iterable[str], context=3, indent='  ') -> NoneFormat the diff of two files, and output tofile_output.contextis the number of identical to show before and after insertion / deletion for context.indentis the line prefix, so that the output is indented.How Fancy ?Here is a sample from the tests :In [1]: import sys
   ...: from pytest_expectdir.plugin import cmpdir, formatDiff
   ...: initial = './tests/data/test3/initial/'
   ...: expected = './tests/data/test3/expected/'
   ...: formatDiff(sys.stdout, initial, expected, cmpdir(initial, expected)[1])
Directory ./tests/data/test3/expected/ (expected) is different from ./tests/data/test3/initial/ (candidate).
Missing in candidate :
dir3/
f1
Extra in candidate :
dir2/
f4
In both directories but different content:

f3:
  - This line is removed
  - And this one too
    This is a complex test
  - Hello 3
  + Hello 3 And replaced ones
    With some lines
  + And added lines
    And otherlines
    common 1
    common 2
  [...] --- expected:11 / candidate:10 ---
    common 6
    common 7
    common 8
  - and diff 1
  + diff

dir4/f3:
  - This line is removed
  - And this one too
    This is a complex test
  - Hello 3
  + Hello 3 And replaced ones
    With some lines
  + And added lines
    And otherlines
    common 1
    common 2
  [...] --- expected:11 / candidate:10 ---
    common 6
    common 7
    common 8
  - and diff 1
  + diff1.2.0Added Replacement string for the current directory1.1.4Fix typo1.1.3Fix Coverage.io badge in README1.1.2Added unit test for full coverage1.1.1Fix Exception not forwarded1.1.0Fallback for expectdir tomodule_stem/class(if one)/functionif datadir is None1.0.0expectdir -> (datapath=None, *, initial=None, expected=None) -> contextmanager -> tmpdir:Pathfixture