def _make_dummy() -> Tuple[Path, Path]: """Create filter_town and CMakeLists.txt, return the paths.""" filter_town = Path(".").resolve() / "filter_town" rm_rf(filter_town) mkdir_p(filter_town) cmake_lists_txt = filter_town / "CMakeLists.txt" with cmake_lists_txt.open("w") as cml: cml.write(_please_stop) return (filter_town, cmake_lists_txt)
def test_executable_relative(): """Validate |Executable| accepts relative paths.""" if platform.system() != "Windows": scripty_path = "./scripty.sh" with open(scripty_path, "w") as scripty: scripty.write("#!/bin/sh\necho 'hi, my name is scripty :)'\n") chmod = which("chmod", log_calls=False) chmod("+x", scripty_path) scripty = Executable(scripty_path, log_calls=False) proc = scripty(stdout=PIPE, stderr=PIPE) assert proc.returncode == 0 assert proc.stderr == b"" assert proc.stdout.decode("utf-8") == "hi, my name is scripty :)\n" rm_rf(scripty_path)
def test_mkdir_p(capsys): """Validate that |mkdir_p| creates directories as expected.""" # Relative paths should be ok. hello = Path("hello") rm_rf(hello) # in case previous tests failed, start clean mkdir_p(hello) assert hello.is_dir() # Already exists, but this is ok (real test is that it doesn't fail). hello = hello.resolve() mkdir_p(hello) assert hello.is_dir() # Strings are allowed. mkdir_p("hello") assert hello.is_dir() # Long chains should be allowed. hello_there = hello / "there" hello_there_beautiful = hello_there / "beautiful" hello_there_beautiful_world = hello_there_beautiful / "world" def repeat(): mkdir_p(hello_there_beautiful_world) assert hello.is_dir() assert hello_there.is_dir() assert hello_there_beautiful.is_dir() assert hello_there_beautiful_world.is_dir() repeat() # because repeat() # why repeat() # not? xD # Cleanup hello/there/beautiful/world and create file hello/there to test errors. rm_rf(hello_there) assert hello.is_dir() with hello_there.open("w") as f: f.write("beautiful world\n") assert hello_there.is_file() with pytest.raises(SystemExit): mkdir_p(hello_there) captured = capsys.readouterr() assert captured.out == "" assert "Unable to mkdir_p" in captured.err if platform.system() == "Windows": assert "file already exists" in captured.err else: assert "File exists:" in captured.err # TODO: how to safely engineer permission access errors on all platforms? # Concern: don't eff people over if they ran tests as `root` # Cleanup rm_rf(hello) assert not hello.is_dir()
def wrap_cd(*, src: Union[Path, str], dest: Union[Path, str], create: bool, err_endswith: Optional[str] = None, err_has: Optional[list] = None): """ Test both versions of cd (decorator and context manager). Parameters ---------- src : Path or str The directory to start in. Assumed to be a valid place to cd to. dest : Path or str The directory to test changing to. create : bool Pass-through argument to ``cd``: whether or not to create ``dest``. err_endswith : str or None If this string is provided, then it is assumed that ``cd`` to ``dest`` will cause an error and we should validate that the captured ``stderr`` ends with the value supplied here. err_has : list or None If provided, this list contains strings to check ``x in`` captured ``stderr`` for each ``x in err_has``. """ # NOTE: see cd.__init__ for why we are avoiding resolve() src_path = Path(os.path.abspath(str(Path(src).expanduser()))) dest_path = Path(os.path.abspath(str(Path(dest).expanduser()))) # Double-whammy testing. with cd(src): # Make sure we ended up in src assert str(Path.cwd()) == str(src_path) # Version 1: context manager approach. def context_cd(): with cd(dest, create=create): assert str(Path.cwd()) == str(dest_path) # Version 2: decorator approach. @cd(dest, create=create) def decorated_cd(): assert str(Path.cwd()) == str(dest_path) # Convenience wrapper for checking both succeed or error. def assert_cd(func): if any([err_endswith, err_has]): with pytest.raises(SystemExit): func() captured = capsys.readouterr() assert captured.out == "" if err_endswith: assert captured.err.strip().endswith(err_endswith) if err_has: for err in err_has: assert err in captured.err else: func() # Start clean with each test. Allow existing file tests to fail. if create and dest_path.is_dir(): rm_rf(dest) assert_cd(context_cd) assert str(Path.cwd()) == str( src_path) # Make sure we end up back in src. # Start clean with each test. Allow existing file tests to fail. if create and dest_path.is_dir(): rm_rf(dest) assert_cd(decorated_cd) assert str(Path.cwd()) == str( src_path) # Make sure we end up back in src.
def test_cd(capsys): """Validate |cd| behaves as expected.""" def wrap_cd(*, src: Union[Path, str], dest: Union[Path, str], create: bool, err_endswith: Optional[str] = None, err_has: Optional[list] = None): """ Test both versions of cd (decorator and context manager). Parameters ---------- src : Path or str The directory to start in. Assumed to be a valid place to cd to. dest : Path or str The directory to test changing to. create : bool Pass-through argument to ``cd``: whether or not to create ``dest``. err_endswith : str or None If this string is provided, then it is assumed that ``cd`` to ``dest`` will cause an error and we should validate that the captured ``stderr`` ends with the value supplied here. err_has : list or None If provided, this list contains strings to check ``x in`` captured ``stderr`` for each ``x in err_has``. """ # NOTE: see cd.__init__ for why we are avoiding resolve() src_path = Path(os.path.abspath(str(Path(src).expanduser()))) dest_path = Path(os.path.abspath(str(Path(dest).expanduser()))) # Double-whammy testing. with cd(src): # Make sure we ended up in src assert str(Path.cwd()) == str(src_path) # Version 1: context manager approach. def context_cd(): with cd(dest, create=create): assert str(Path.cwd()) == str(dest_path) # Version 2: decorator approach. @cd(dest, create=create) def decorated_cd(): assert str(Path.cwd()) == str(dest_path) # Convenience wrapper for checking both succeed or error. def assert_cd(func): if any([err_endswith, err_has]): with pytest.raises(SystemExit): func() captured = capsys.readouterr() assert captured.out == "" if err_endswith: assert captured.err.strip().endswith(err_endswith) if err_has: for err in err_has: assert err in captured.err else: func() # Start clean with each test. Allow existing file tests to fail. if create and dest_path.is_dir(): rm_rf(dest) assert_cd(context_cd) assert str(Path.cwd()) == str( src_path) # Make sure we end up back in src. # Start clean with each test. Allow existing file tests to fail. if create and dest_path.is_dir(): rm_rf(dest) assert_cd(decorated_cd) assert str(Path.cwd()) == str( src_path) # Make sure we end up back in src. starting_cwd = str(Path.cwd()) # Make sure we can navigate to the same place. Because why not? with cd(starting_cwd): assert str(Path.cwd()) == starting_cwd with cd(starting_cwd): assert str(Path.cwd()) == starting_cwd with cd(starting_cwd): assert str(Path.cwd()) == starting_cwd assert str(Path.cwd()) == starting_cwd assert str(Path.cwd()) == starting_cwd assert str(Path.cwd()) == starting_cwd # Make sure we can get to directories that currently exist. wrap_cd(src=".", dest="..", create=False) assert str(Path.cwd()) == starting_cwd wrap_cd(src=".", dest="~", create=False) assert str(Path.cwd()) == starting_cwd # With create=True on something that is a file, error ripples from mkdir_p. supertest = Path(".").resolve() / "supertest" with supertest.open("w") as f: f.write("supertest!\n") err_has = ["Unable to mkdir_p"] if platform.system() == "Windows": err_has.append("file already exists") else: err_has.append("File exists:") wrap_cd(src=".", dest=supertest, create=True, err_has=err_has) rm_rf(supertest) assert str(Path.cwd()) == starting_cwd # When create=False with directory that does not exist we expect a failure. not_a_directory = "not_a_directory" rm_rf(not_a_directory) wrap_cd( src=".", dest=not_a_directory, create=False, err_endswith="not_a_directory' is not a directory, but create=False.") assert not Path(not_a_directory).is_dir() assert str(Path.cwd()) == starting_cwd # Make sure that we can create it as expected. mkdir_p(not_a_directory ) # only here for coverage in wrap_cd (first rm_rf(dest)) wrap_cd(src=".", dest=not_a_directory, create=True) assert Path(not_a_directory).is_dir() rm_rf(not_a_directory) assert str(Path.cwd()) == starting_cwd # v2: multiple directories that don't exist with create=False expects failure. not_a_directory = Path("not") / "a" / "directory" rm_rf("not") wrap_cd(src=".", dest=not_a_directory, create=False, err_endswith="directory' is not a directory, but create=False.") assert not Path("not").is_dir() assert not (Path("not") / "a").is_dir() assert not not_a_directory.is_dir() assert str(Path.cwd()) == starting_cwd # Make sure we can create multiple directories at once. rm_rf("not") wrap_cd(src=".", dest=not_a_directory, create=True) assert Path("not").is_dir() assert (Path("not") / "a").is_dir() assert not_a_directory.is_dir() rm_rf("not") assert str(Path.cwd()) == starting_cwd # Test first failure case in cd.__enter__ where cwd() cannot be found. Maybe there # is an easier way to test this? def uh_uh_uh(*args, **kwargs): raise RuntimeError("You didn't say the magic word!") first = Path(".").resolve() / "first" second = first / "second" third = second / "third" path_cwd = Path.cwd Path.cwd = uh_uh_uh with pytest.raises(SystemExit): with cd(third, create=True): pass # pragma: nocover captured = capsys.readouterr() assert captured.out == "" assert captured.err.strip().endswith( "cd: could not get current working directory: You didn't say the magic word!" ) Path.cwd = path_cwd assert str(Path.cwd()) == starting_cwd # Test second failure case in cd.__enter__ where os.chdir does not succeed. rm_rf(first) os_chdir = os.chdir os.chdir = uh_uh_uh with pytest.raises(SystemExit): with cd(third, create=True): pass # pragma: nocover captured = capsys.readouterr() assert captured.out == "" assert captured.err.strip().endswith( "cd: could not change directories to '" + str(third) + "': You didn't say the magic word!") os.chdir = os_chdir rm_rf(first) assert str(Path.cwd()) == starting_cwd # Test failure case in cd.__exit__ where we cannot return. with pytest.raises(SystemExit): with cd(third, create=True): with cd(second): rm_rf(first) captured = capsys.readouterr() assert captured.out == "" assert "cd: could not return to " + str(third) in captured.err assert str(Path.cwd()) == starting_cwd
def test_filter_file(capsys): """Validate that |filter_file| patches / errors as expected.""" # Non-existent files cannot be patched. with pytest.raises(SystemExit): filter_file("i_dont_exist", "boom", "blam") red_x = colorize("[X] ", color=Colors.Red, style=Styles.Bold) captured = capsys.readouterr() assert captured.out == "" err = "{red_x}Cannot filter 'i_dont_exist', no such file!".format( red_x=red_x) assert captured.err.strip() == err # Backup extension must not be empty string. with pytest.raises(SystemExit): filter_file("tox.ini", "boom", "blam", backup_extension="") captured = capsys.readouterr() assert captured.out == "" err = "{red_x}filter_file: 'backup_extension' may not be the empty string.".format( red_x=red_x) assert captured.err.strip() == err def read_both(cml: Path, bku: Path) -> Tuple[str, str]: """Open and read both files, returning the results.""" with cml.open() as cml_f: cml_contents = cml_f.read() with bku.open() as bku_f: bku_contents = bku_f.read() return (cml_contents, bku_contents) for line_based in (True, False): # Filtering nothing should error. filter_town, cmake_lists_txt = _make_dummy() with pytest.raises(SystemExit): filter_file(cmake_lists_txt, "", "", line_based=line_based) captured = capsys.readouterr() assert captured.out == "" assert "filter_file: no changes made to '" in captured.err assert "CMakeLists.txt'" in captured.err # Invalid replacement should trigger failure. filter_town, cmake_lists_txt = _make_dummy() with pytest.raises(SystemExit): filter_file(cmake_lists_txt, "export", lambda x: 11, line_based=line_based) captured = capsys.readouterr() assert captured.out == "" assert captured.err.startswith( "{red_x}Unable to filter".format(red_x=red_x)) assert "expected str instance, int found" in captured.err # No filtering with demand_different=False should not error. filter_town, cmake_lists_txt = _make_dummy() backup = filter_file(cmake_lists_txt, "", "", demand_different=False, line_based=line_based) cml, bku = read_both(cmake_lists_txt, backup) assert cml == bku # Test an actual patch. filter_town, cmake_lists_txt = _make_dummy() backup = filter_file(cmake_lists_txt, "super_project", "SUPER_PROJECT", line_based=line_based) cml, bku = read_both(cmake_lists_txt, backup) assert cml != bku assert bku == _please_stop assert cml == _please_stop.replace("super_project", "SUPER_PROJECT") # Cleanup rm_rf(filter_town)
def test_unified_diff(capsys): """Validate that |unified_diff| diffs / errors as expected.""" # Invalid from_file should exit. with pytest.raises(SystemExit): unified_diff("i_am_not_here", "tox.ini") captured = capsys.readouterr() assert captured.out == "" assert "unified_diff: from_path 'i_am_not_here' does not exist!" in captured.err # Invalid to_file should exit. with pytest.raises(SystemExit): unified_diff("tox.ini", "i_am_not_here") captured = capsys.readouterr() assert captured.out == "" assert "unified_diff: to_path 'i_am_not_here' does not exist!" in captured.err # Diff between a file and itself should result in the empty string. empty = unified_diff("tox.ini", "tox.ini") assert empty == "" # Do some diffing. expected_diff_template = textwrap.dedent('''\ --- {backup} +++ {cmake_lists_txt} @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14 FATAL_ERROR) project(super_project) -export(PACKAGE super_project) +# export(PACKAGE super_project) message(STATUS "PLEASE STOP DOING export(PACKAGE)!") message(STATUS "At the very least, give us an option() to stop it!") ''').replace("@@\n\n cmake", "@@\n \n cmake") # NOTE: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this took a while to figure out xD # looking at diff of diff in tox output was very confusing hehehe. def filter_diff(no_pygments): filter_town, cmake_lists_txt = _make_dummy() backup = filter_file(cmake_lists_txt, r"^(\s*export\s*\(PACKAGE.*\).*)$", r"# \1", line_based=True) diff = unified_diff(backup, cmake_lists_txt, no_pygments=no_pygments) expected_diff = expected_diff_template.format( backup=str(backup), cmake_lists_txt=str(cmake_lists_txt)) return (diff, expected_diff) for no_pygments in (True, False): diff, expected_diff = filter_diff(no_pygments) if no_pygments: assert diff == expected_diff else: import pygments from pygments import lexers, formatters lex = lexers.find_lexer_class_by_name("diff") fmt = formatters.get_formatter_by_name("console") assert diff == pygments.highlight(expected_diff, lex(), fmt) # Force in an error just for shiggles (and because we can). def superfail(*args, **kwargs): raise ValueError("superfail") lexers.find_lexer_class_by_name = superfail # Attempt to call pygments code now that this raises. Result: original text. diff, expected_diff = filter_diff(False) assert diff == expected_diff # Lastly, make sure the catch-all exception prints the expected message. import difflib difflib.unified_diff = superfail with pytest.raises(SystemExit): unified_diff("tox.ini", "tox.ini") captured = capsys.readouterr() assert captured.out == "" expected = "unified_diff: unable to diff 'tox.ini' with 'tox.ini': superfail" assert captured.err.strip().endswith(expected) # Cleanup. filter_town, cmake_lists_txt = _make_dummy() rm_rf(filter_town)
def test_rm_rf(capsys): """Validate |rm_rf| deletes files / directories as expected.""" def stage(spec: dict): """ Create the stage to (selectively) delete. Parameters ---------- spec : dict All keys must be strings. Values may either be strings (indicating a file is to be written), or a dictionary (nested directory) with string keys and values being strings or dict as well. Return ------ tuple(List[Path], List[Path]) The created ``(files, directories)`` in that order. """ all_files = [] all_directories = [] def make_children(parent, next_spec): for key, item in next_spec.items(): this_kid = parent / key if isinstance(item, str): with this_kid.open("w") as f: f.write(item) all_files.append(this_kid) else: # assumed to be dict! mkdir_p(this_kid) all_directories.append(this_kid) make_children(this_kid, item) make_children(Path(".").resolve(), spec) return (all_files, all_directories) spec = { "hi": { "there.txt": "a file with some text\n", "there": { "beautiful": { "file.ya": "how interesting, another file ya?\n", "world": { "FILEZ": "don't need extensions :p\n" } } }, "another": { "directory": { "goes": { "all": { "the": { "way": { "down": { "here": "!!!\n" } } } } } } } } } # Create the stage. files, directories = stage(spec) # May as well check (?) for f in files: assert f.is_file() for d in directories: assert d.is_dir() # Deleting hi means they are all gone. rm_rf("hi") for fd in itertools.chain(files, directories): assert not fd.exists() # Recreate stage and selectively delete some things. files, directories = stage(spec) hi_there_txt = Path("hi") / "there.txt" assert hi_there_txt.is_file() rm_rf(hi_there_txt) assert not hi_there_txt.exists() # Creating a symbolic link is the only way I know of to raise an exception here. # rmtree does not allow removal of symlinks. # NOTE: see https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to # Can only test directory links on windows. hi_there = (Path("hi") / "there").resolve() # make sure target is absolute assert hi_there.is_dir() hi_you = Path("hi") / "you" hi_you.symlink_to(hi_there, target_is_directory=True) with pytest.raises(SystemExit): rm_rf(hi_you) captured = capsys.readouterr() assert captured.out == "" assert "Cannot call rmtree on a symbolic link" in captured.err # Cleanup: remove hi completely rm_rf("hi")