Source code for tests.patch

########################################################################################
# Copyright 2019 Stephen McDowell                                                      #
#                                                                                      #
# Licensed under the Apache License, Version 2.0 (the "License");                      #
# you may not use this file except in compliance with the License.                     #
# You may obtain a copy of the License at                                              #
#                                                                                      #
#     http://www.apache.org/licenses/LICENSE-2.0                                       #
#                                                                                      #
# Unless required by applicable law or agreed to in writing, software                  #
# distributed under the License is distributed on an "AS IS" BASIS,                    #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.             #
# See the License for the specific language governing permissions and                  #
# limitations under the License.                                                       #
########################################################################################
"""Tests for the :mod:`ci_exec.patch` module."""

import textwrap
from pathlib import Path
from typing import Tuple

from ci_exec.colorize import Colors, Styles, colorize
from ci_exec.core import mkdir_p, rm_rf
from ci_exec.patch import filter_file, unified_diff

import pytest


_please_stop = textwrap.dedent('''
    cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
    project(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!")
''')
"""Dummy contents for testing replacements."""


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)


[docs]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)
[docs]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)