diff --git a/tests/test_makepyfile.py b/tests/test_makepyfile.py new file mode 100644 index 0000000..f75e2fe --- /dev/null +++ b/tests/test_makepyfile.py @@ -0,0 +1,260 @@ +import os +from encodings.aliases import aliases +from pathlib import Path +from textwrap import dedent +from typing import Tuple + +import pytest + +pytest_plugins = ["pytester"] + + +def not_debug(): + """run tests only when not in CI and debug is enabled.""" + return os.getenv("CI", False) or not os.getenv("PYTEST_DEBUG_MAKEPYFILE", False) + + +def enc(enc): + try: + _ = "abc".encode(enc) + except LookupError: + return False + + return True + + +all_encodings = set(filter(enc, aliases.values())) + + +@pytest.fixture(params=all_encodings) +def encoding(request): + return request.param + + +@pytest.fixture( + params=[ + ("3", "4"), + ("A", "B"), + ("!", "?"), + ("☆", "☁"), + ("✅", "💥"), + ("é", "ü"), + ("Я", "ж"), # Cyrillic characters + ("Ф", "Щ"), # More Cyrillic + ("電腦", "电脑"), # Chinese: traditional vs simplified for "computer" + ("学校", "がっこう"), # Japanese: Kanji vs Hiragana for "school" + ], + ids=[ + "numbers", + "letters", + "punctuation", + "stars", + "check-explosion", + "accented_letters", + "cyrillic_basic", + "cyrillic_complex", + "chinese_computer", + "japanese_school", + ], +) +def any_charset(request): + return request.param + + +@pytest.fixture +def makepyfile_encoded(testdir, encoding, any_charset): + a, b = any_charset + + original_file = testdir.makepyfile( + f""" + def f(): + ''' + >>> print('{a}') + {b} + ''' + pass + """, + encoding=encoding, + ) + + expected_diff = dedent( + f""" + >>> print('{a}') + - {b} + + {a} + """ + ).strip("\n") + + yield original_file, expected_diff, encoding + + +def makebasicfile(tmp_path: Path, a, b, encoding: str) -> Tuple[Path, str]: + """alternative implementation without the use of `testdir.makepyfile`.""" + + original_file = Path(tmp_path).joinpath("test_basic.py") + code = dedent( + f""" + def f(): + ''' + >>> print('{a}') + {b} + ''' + pass + """ + ) + + original_file.write_text(code, encoding=encoding) + + expected_diff = dedent( + f""" + >>> print('{a}') + - {b} + + {a} + """ + ).strip("\n") + + return original_file, expected_diff + + +@pytest.fixture +def basic_encoded(tmp_path, encoding, any_charset): + + a, b = any_charset + + file, diff = makebasicfile(tmp_path, a, b, encoding) + + yield file, diff, encoding + + +@pytest.mark.skipif(not_debug(), reason="skipped in CI or debugging is not enabled.") +def test_makepyfile(makepyfile_encoded): + """ + Test is expected to fail because of UnicodeDecodeError. + + Just to compare visually with `test_basicfile` to see the difference + """ + + file, diff, enc = makepyfile_encoded + + text = Path(file).read_text(enc) + + print(text, diff) + + +@pytest.mark.skipif(not_debug(), reason="skipped in CI or debugging is not enabled.") +def test_basicfile(basic_encoded): + """ + Test is expected to fail because of UnicodeDecodeError. + + Just to compare visually with `test_makepyfile` to see the difference + """ + file, diff, enc = basic_encoded + + text = Path(file).read_text(enc) + + print(text, diff) + +@pytest.mark.skip(reason="makepyfile uses encoding only on bytes objects.") +def test_compare_make_basic_file(testdir, encoding, any_charset): + """ + Compare testdir.makepyfile with pathlib.Path.writetext to create python files. + + Expected behavior is when `testdir.makepyfile` created a file with given encoding, + `makebasicfile` also should be able to encode the charset. + + If a UnicodeEncodeError is raised, it means `testdir.makepyfile` screwed up the + encoding. + """ + + a, b = any_charset + + # create a python file with testdir.makepyfile in the old fashioned way + + make_file = testdir.makepyfile( + f""" + def f(): + ''' + >>> print('{a}') + {b} + ''' + pass + """, + encoding=encoding, + ) + + assert Path( + make_file + ).is_file(), f"`testdir.makepyfile` failed encode {repr((a,b))} with {encoding=}" + + make_diff = dedent( + f""" + >>> print('{a}') + - {b} + + {a} + """ + ).strip("\n") + + # try to check if makepyfile screwed up and create python file in a different way + + try: + basic_file, basic_diff = makebasicfile(str(testdir), a, b, encoding) + except UnicodeEncodeError: + pytest.fail( + f"testdir.makepyfile encoding {repr((a,b))} with {encoding=}, but it sould have failed." + ) + + assert Path( + basic_file + ).is_file(), f"`makebasicfile` failed encode {repr((a,b))} with {encoding=}" + assert ( + make_diff == basic_diff + ), "sanity check - diffs always should be the same, if not my test implementation is wrong." + + +def test_compare_write_bytes(testdir, encoding, any_charset): + """ + `makeypfile` ignores keyword arguments `encoding` if content is not bytes. + + 1. create test data and convert to bytes, if encoding failes the test is skipped. + 2. use both functions to create python file + + - if both functions raise the same error, everything is as expected + - if Path.write_bytes succeeds so should `testdir.makepyfile` and error should be None + - both files sould exist after the test + """ + + a, b = any_charset + + try: + bytes_content = dedent(f""" + def f(): + ''' + >>> print('{a}') + {b} + ''' + pass + """ + ).encode(encoding) + except UnicodeEncodeError: + # skip test do testdata could not prepared + pytest.xfail(f"{repr(any_charset)} cannot be encoded with {encoding=}") + + try: + make_file = testdir.makepyfile( + bytes_content, + encoding=encoding, + ) + except Exception as e: + error = e # if both function fail the same, everything is as expected + else: + error = None + + basic_file = Path(str(testdir)).joinpath("test_basic.py") + try: + basic_file.write_bytes(bytes_content) + except Exception as e: + assert type(error) is type(e), f"basically never should happen, but {e=} was raised." + else: + assert error is None, f"makepyfile screwed up {encoding=} and raised {error=}" + + assert Path(make_file).is_file() and Path(basic_file).is_file(), "files are missing."