pax_global_header00006660000000000000000000000064144617556770014540gustar00rootroot0000000000000052 comment=5a9dc770ebd9bba6c58aface5f0106844d356e45 expecttest-0.1.5/000077500000000000000000000000001446175567700137335ustar00rootroot00000000000000expecttest-0.1.5/.flake8000066400000000000000000000014531446175567700151110ustar00rootroot00000000000000[flake8] select = B,C,E,F,P,T4,W,B9 max-line-length = 150 ### DEFAULT IGNORES FOR 4-space INDENTED PROJECTS ### # E127, E128 are hard to silence in certain nested formatting situations. # E203 doesn't work for slicing # E265, E266 talk about comment formatting which is too opinionated. # E402 warns on imports coming after statements. There are important use cases # like demandimport (https://fburl.com/demandimport) that require statements # before imports. # E501 is not flexible enough, we're using B950 instead. # E722 is a duplicate of B001. # P207 is a duplicate of B003. # P208 is a duplicate of C403. # W503 talks about operator formatting which is too opinionated. ignore = E127, E128, E203, E265, E266, E402, E501, E722, P207, P208, W503 exclude = .git, .hg, __pycache__, max-complexity = 12 expecttest-0.1.5/.github/000077500000000000000000000000001446175567700152735ustar00rootroot00000000000000expecttest-0.1.5/.github/workflows/000077500000000000000000000000001446175567700173305ustar00rootroot00000000000000expecttest-0.1.5/.github/workflows/ci.yml000066400000000000000000000013531446175567700204500ustar00rootroot00000000000000name: CI on: pull_request: push: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: ci: strategy: matrix: python-version: - 3.8 - 3.9 - "3.10" - 3.11 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Install Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install Poetry uses: snok/install-poetry@v1 - name: Install dependencies run: poetry install - name: Run tests run: poetry run ./run_tests.sh expecttest-0.1.5/.gitignore000066400000000000000000000000541446175567700157220ustar00rootroot00000000000000__pycache__/ /.hypothesis/ /.vscode/ /dist/ expecttest-0.1.5/LICENSE000066400000000000000000000020561446175567700147430ustar00rootroot00000000000000Copyright (c) Edward Z. Yang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. expecttest-0.1.5/README.md000066400000000000000000000034501446175567700152140ustar00rootroot00000000000000# expecttest [![PyPI version](https://badge.fury.io/py/expecttest.svg)](https://badge.fury.io/py/expecttest) This library implements expect tests (also known as "golden" tests). Expect tests are a method of writing tests where instead of hard-coding the expected output of a test, you run the test to get the output, and the test framework automatically populates the expected output. If the output of the test changes, you can rerun the test with the environment variable `EXPECTTEST_ACCEPT=1` to automatically update the expected output. Somewhat unusually, this library implements *inline* expect tests: that is to say, the expected output isn't saved to an external file, it is saved directly in the Python file (and we modify your Python file when updating the expect test.) The general recipe for how to use this is as follows: 1. Write your test and use `assertExpectedInline()` instead of a normal `assertEqual`. Leave the expected argument blank with an empty string: ```py self.assertExpectedInline(some_func(), "") ``` 2. Run your test. It should fail, and you get an error message about accepting the output with `EXPECTTEST_ACCEPT=1` 3. Rerun the test with `EXPECTTEST_ACCEPT=1`. Now the previously blank string literal will contain the expected value of the test. ```py self.assertExpectedInline(some_func(), "my_value") ``` Some tips and tricks: - Often, you will want to expect test on a multiline string. This framework understands triple-quoted strings, so you can just write `"""my_value"""` and it will turn into triple-quoted strings. - Take some time thinking about how exactly you want to design the output format of the expect test. It is often profitable to design an output representation specifically for expect tests. expecttest-0.1.5/expecttest/000077500000000000000000000000001446175567700161235ustar00rootroot00000000000000expecttest-0.1.5/expecttest/__init__.py000066400000000000000000000247561446175567700202520ustar00rootroot00000000000000import ast import os import re import string import sys import traceback import unittest from typing import Any, Callable, Dict, List, Match, Tuple, Set ACCEPT = os.getenv('EXPECTTEST_ACCEPT') LINENO_AT_START = sys.version_info >= (3, 8) def nth_line(src: str, lineno: int) -> int: """ Compute the starting index of the n-th line (where n is 1-indexed) >>> nth_line("aaa\\nbb\\nc", 2) 4 """ assert lineno >= 1 pos = 0 for _ in range(lineno - 1): pos = src.find('\n', pos) + 1 return pos def nth_eol(src: str, lineno: int) -> int: """ Compute the ending index of the n-th line (before the newline, where n is 1-indexed) >>> nth_eol("aaa\\nbb\\nc", 2) 6 """ assert lineno >= 1 pos = -1 for _ in range(lineno): pos = src.find('\n', pos + 1) if pos == -1: return len(src) return pos def normalize_nl(t: str) -> str: return t.replace('\r\n', '\n').replace('\r', '\n') def escape_trailing_quote(s: str, quote: str) -> str: if s and s[-1] == quote: return s[:-1] + '\\' + quote else: return s class EditHistory: state: Dict[str, List[Tuple[int, int]]] seen: Set[Tuple[str, int]] def __init__(self) -> None: self.state = {} self.seen = set() def adjust_lineno(self, fn: str, lineno: int) -> int: if fn not in self.state: return lineno for edit_loc, edit_diff in self.state[fn]: if lineno > edit_loc: lineno += edit_diff return lineno def seen_file(self, fn: str) -> bool: return fn in self.state def seen_edit(self, fn: str, lineno: int) -> bool: return (fn, lineno) in self.seen def record_edit(self, fn: str, lineno: int, delta: int) -> None: self.state.setdefault(fn, []).append((lineno, delta)) self.seen.add((fn, lineno)) EDIT_HISTORY = EditHistory() def ok_for_raw_triple_quoted_string(s: str, quote: str) -> bool: """ Is this string representable inside a raw triple-quoted string? Due to the fact that backslashes are always treated literally, some strings are not representable. >>> ok_for_raw_triple_quoted_string("blah", quote="'") True >>> ok_for_raw_triple_quoted_string("'", quote="'") False >>> ok_for_raw_triple_quoted_string("a ''' b", quote="'") False """ return quote * 3 not in s and (not s or s[-1] not in [quote, '\\']) RE_EXPECT = re.compile( ( r"(?Pr?)" r"(?P'''|" r'""")' r"(?P.*?)" r"(?P=quote)" ), re.DOTALL ) def replace_string_literal(src : str, start_lineno : int, end_lineno : int, new_string : str) -> Tuple[str, int]: r""" Replace a triple quoted string literal with new contents. Only handles printable ASCII correctly at the moment. This will preserve the quote style of the original string, and makes a best effort to preserve raw-ness (unless it is impossible to do so.) Returns a tuple of the replaced string, as well as a delta of number of lines added/removed. >>> replace_string_literal("'''arf'''", 1, 1, "barf") ("'''barf'''", 0) >>> r = replace_string_literal(" moo = '''arf'''", 1, 1, "'a'\n\\b\n") >>> print(r[0]) moo = '''\ 'a' \\b ''' >>> r[1] 3 >>> replace_string_literal(" moo = '''\\\narf'''", 1, 2, "'a'\n\\b\n")[1] 2 >>> print(replace_string_literal(" f('''\"\"\"''')", 1, 1, "a ''' b")[0]) f('''a \'\'\' b''') """ # Haven't implemented correct escaping for non-printable characters assert all(c in string.printable for c in new_string), repr(new_string) new_string = normalize_nl(new_string) delta = [new_string.count("\n")] if delta[0] > 0: delta[0] += 1 # handle the extra \\\n assert start_lineno <= end_lineno start = nth_line(src, start_lineno) end = nth_eol(src, end_lineno) assert start <= end def replace(m: Match[str]) -> str: s = new_string raw = m.group('raw') == 'r' if not raw or not ok_for_raw_triple_quoted_string(s, quote=m.group('quote')[0]): raw = False s = s.replace('\\', '\\\\') if m.group('quote') == "'''": s = escape_trailing_quote(s, "'").replace("'''", r"\'\'\'") else: s = escape_trailing_quote(s, '"').replace('"""', r'\"\"\"') new_body = "\\\n" + s if "\n" in s and not raw else s delta[0] -= m.group('body').count("\n") return ''.join(['r' if raw else '', m.group('quote'), new_body, m.group('quote'), ]) return (src[:start] + RE_EXPECT.sub(replace, src[start:end], count=1) + src[end:], delta[0]) def replace_many(rep: Dict[str, str], text: str) -> str: rep = {re.escape(k): v for k, v in rep.items()} pattern = re.compile("|".join(rep.keys())) return pattern.sub(lambda m: rep[re.escape(m.group(0))], text) class TestCase(unittest.TestCase): longMessage = True _expect_filters: Dict[str, str] def substituteExpected(self, pattern: str, replacement: str) -> None: if not hasattr(self, '_expect_filters'): self._expect_filters = {} def expect_filters_cleanup() -> None: del self._expect_filters self.addCleanup(expect_filters_cleanup) if pattern in self._expect_filters: raise RuntimeError( "Cannot remap {} to {} (existing mapping is {})" .format(pattern, replacement, self._expect_filters[pattern])) self._expect_filters[pattern] = replacement def assertExpectedInline(self, actual: str, expect: str, skip: int = 0) -> None: """ Assert that actual is equal to expect. The expect argument MUST be a string literal (triple-quoted strings OK), and will get updated directly in source when you run the test suite with EXPECTTEST_ACCEPT=1. If you want to write a helper function that makes use of assertExpectedInline (e.g., expect is not a string literal), set the skip argument to how many function calls we should skip to find the string literal to update. """ if hasattr(self, '_expect_filters'): actual = replace_many(self._expect_filters, actual) if ACCEPT: if actual != expect: # current frame and parent frame, plus any requested skip tb = traceback.extract_stack(limit=2 + skip) fn, lineno, _, _ = tb[0] if EDIT_HISTORY.seen_edit(fn, lineno): print("Skipping already accepted output for {} at {}:{}".format(self.id(), fn, lineno)) return print("Accepting new output for {} at {}:{}".format(self.id(), fn, lineno)) with open(fn, 'r+') as f: old = f.read() old_ast = ast.parse(old) # NB: it's only the traceback line numbers that are wrong; # we reread the file every time we write to it, so AST's # line numbers are correct lineno = EDIT_HISTORY.adjust_lineno(fn, lineno) # Conservative assumption to start start_lineno = lineno end_lineno = lineno # Try to give a more accurate bounds based on AST # NB: this walk is in no specified order (in practice it's # breadth first) for n in ast.walk(old_ast): if isinstance(n, ast.Expr): if hasattr(n, 'end_lineno'): assert LINENO_AT_START if n.lineno == start_lineno: end_lineno = n.end_lineno # type: ignore[attr-defined] else: if n.lineno == end_lineno: start_lineno = n.lineno new, delta = replace_string_literal(old, start_lineno, end_lineno, actual) assert old != new, f"Failed to substitute string at {fn}:{lineno}; did you use triple quotes? " \ "If this is unexpected, please file a bug report at " \ "https://github.com/ezyang/expecttest/issues/new " \ f"with the contents of the source file near {fn}:{lineno}" # Only write the backup file the first time we hit the # file if not EDIT_HISTORY.seen_file(fn): with open(fn + ".bak", 'w') as f_bak: f_bak.write(old) f.seek(0) f.truncate(0) f.write(new) EDIT_HISTORY.record_edit(fn, lineno, delta) else: help_text = ("To accept the new output, re-run test with " "envvar EXPECTTEST_ACCEPT=1 (we recommend " "staging/committing your changes before doing this)") self.assertMultiLineEqualMaybeCppStack(expect, actual, msg=help_text) def assertExpectedRaisesInline(self, exc_type: Any, callable: Callable[..., Any], expect: str, *args: Any, **kwargs: Any) -> None: """ Like assertExpectedInline, but tests the str() representation of the raised exception from callable. The raised exeption must be exc_type. """ try: callable(*args, **kwargs) except exc_type as e: self.assertExpectedInline(str(e), expect, skip=1) return # Don't put this in the try block; the AssertionError will catch it self.fail(msg="Did not raise when expected to") def assertMultiLineEqualMaybeCppStack(self, expect: str, actual: str, *args: Any, **kwargs: Any) -> None: if hasattr(self, "assertMultiLineEqual"): self.assertMultiLineEqual(expect, actual[:len(expect)], *args, **kwargs) else: self.assertEqual(expect, actual[:len(expect)], *args, **kwargs) if len(actual) > len(expect): cpp_stacktrace_header = "\nException raised from" end_header = len(expect) + len(cpp_stacktrace_header) self.assertEqual(actual[len(expect): end_header], cpp_stacktrace_header) if __name__ == "__main__": import doctest doctest.testmod() expecttest-0.1.5/expecttest/py.typed000066400000000000000000000000001446175567700176100ustar00rootroot00000000000000expecttest-0.1.5/poetry.lock000066400000000000000000000400421446175567700161270ustar00rootroot00000000000000[[package]] name = "attrs" version = "21.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "flake8" version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "hypothesis" version = "6.14.0" description = "A library for property-based testing" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] attrs = ">=19.2.0" sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-resources (>=3.3.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"] cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] dateutil = ["python-dateutil (>=1.4)"] django = ["pytz (>=2014.1)", "django (>=2.2)"] dpcontracts = ["dpcontracts (>=0.4)"] ghostwriter = ["black (>=19.10b0)"] lark = ["lark-parser (>=0.6.5)"] numpy = ["numpy (>=1.9.0)"] pandas = ["pandas (>=0.25)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] zoneinfo = ["importlib-resources (>=3.3.0)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"] [[package]] name = "importlib-metadata" version = "4.5.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = "*" [[package]] name = "mypy" version = "0.910" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.5" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" toml = "*" typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} typing-extensions = ">=3.7.4" [package.extras] dmypy = ["psutil (>=4.0)"] python2 = ["typed-ast (>=1.4.0,<1.5.0)"] [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" optional = false python-versions = "*" [[package]] name = "pycodestyle" version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" category = "dev" optional = false python-versions = "*" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typed-ast" version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = "*" [[package]] name = "typing-extensions" version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" optional = false python-versions = "*" [[package]] name = "zipp" version = "3.4.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.6" content-hash = "ce24e340ca7487ae5b160600fbf359007c27f0e2002ee66f156c7b3f79f9fefc" [metadata.files] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] hypothesis = [ {file = "hypothesis-6.14.0-py3-none-any.whl", hash = "sha256:27aa2af763af06b8b61ce65c09626cf1da6d3a6ff155900f3c581837b453313a"}, {file = "hypothesis-6.14.0.tar.gz", hash = "sha256:9bdee01ae260329b16117e9b0229a839b4a77747a985922653f595bd2a6a541a"}, ] importlib-metadata = [ {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"}, {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mypy = [ {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] zipp = [ {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, ] expecttest-0.1.5/pyproject.toml000066400000000000000000000022551446175567700166530ustar00rootroot00000000000000[tool.poetry] name = "expecttest" version = "0.1.5" description = "" readme = "README.md" repository = "https://github.com/ezyang/expecttest" authors = ["Edward Z. Yang "] include = ["expecttest/py.typed"] license = "MIT" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] [tool.poetry.dependencies] python = "^3.6" [tool.poetry.dev-dependencies] flake8 = "^3" hypothesis = "^6" mypy = "^0.910" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] python_version = "3.7" strict_optional = true show_column_numbers = true show_error_codes = true warn_no_return = true disallow_any_unimported = true # --strict warn_unused_configs = true disallow_any_generics = true disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true disallow_untyped_decorators = true no_implicit_optional = true warn_redundant_casts = true warn_unused_ignores = false # purposely disabled warn_return_any = true no_implicit_reexport = true strict_equality = true expecttest-0.1.5/run_tests.sh000077500000000000000000000001171446175567700163170ustar00rootroot00000000000000#!/bin/sh set -ex flake8 mypy --exclude=smoketests . python test_expecttest.py expecttest-0.1.5/smoketests/000077500000000000000000000000001446175567700161345ustar00rootroot00000000000000expecttest-0.1.5/smoketests/accept_twice.py000066400000000000000000000005341446175567700211420ustar00rootroot00000000000000import expecttest import unittest S1 = "a\nb" S2 = "c\nd" class Test(expecttest.TestCase): def bar(self): self.assertExpectedInline(S1, """\ w""") def test_a(self): self.bar() self.bar() def test_b(self): self.assertExpectedInline(S2, """\ x y z""") if __name__ == '__main__': unittest.main() expecttest-0.1.5/test_expecttest.py000066400000000000000000000121551446175567700175400ustar00rootroot00000000000000import doctest import sys import string import shutil import subprocess import os import textwrap import traceback import unittest import tempfile from typing import Any, Dict, Tuple import hypothesis from hypothesis.strategies import (booleans, composite, integers, sampled_from, text) import expecttest def sh(file: str, accept: bool = False) -> subprocess.CompletedProcess: # type: ignore[type-arg] env = '' if accept: env = 'EXPECTTEST_ACCEPT=1 ' print(f' $ {env}python {file}') r = subprocess.run( [sys.executable, file], capture_output=True, # NB: Always OVERRIDE EXPECTTEST_ACCEPT variable, so outer usage doesn't # get confused! env={**os.environ, 'EXPECTTEST_ACCEPT': '1' if accept else ''}, text=True, ) if r.stderr: print(textwrap.indent(r.stderr, ' ')) return r @composite def text_lineno(draw: Any) -> Tuple[str, int]: t = draw(text("a\n")) lineno = draw(integers(min_value=1, max_value=t.count("\n") + 1)) return (t, lineno) class TestExpectTest(expecttest.TestCase): @hypothesis.given(text_lineno()) def test_nth_line_ref(self, t_lineno: Tuple[str, int]) -> None: t, lineno = t_lineno hypothesis.event("lineno = {}".format(lineno)) def nth_line_ref(src: str, lineno: int) -> int: xs = src.split("\n")[:lineno] xs[-1] = '' return len("\n".join(xs)) self.assertEqual(expecttest.nth_line(t, lineno), nth_line_ref(t, lineno)) @hypothesis.given(text(string.printable), booleans(), sampled_from(['"', "'"])) def test_replace_string_literal_roundtrip(self, t: str, raw: bool, quote: str) -> None: if raw: hypothesis.assume(expecttest.ok_for_raw_triple_quoted_string(t, quote=quote)) prog = """\ r = {r}{quote}placeholder{quote} r2 = {r}{quote}placeholder2{quote} r3 = {r}{quote}placeholder3{quote} """.format(r='r' if raw else '', quote=quote * 3) new_prog = expecttest.replace_string_literal( textwrap.dedent(prog), 2, 2, t)[0] ns : Dict[str, Any] = {} exec(new_prog, ns) msg = "program was:\n{}".format(new_prog) self.assertEqual(ns['r'], 'placeholder', msg=msg) # noqa: F821 self.assertEqual(ns['r2'], expecttest.normalize_nl(t), msg=msg) # noqa: F821 self.assertEqual(ns['r3'], 'placeholder3', msg=msg) # noqa: F821 def test_sample_lineno(self) -> None: prog = r""" single_single('''0''') single_multi('''1''') multi_single('''\ 2 ''') multi_multi_less('''\ 3 4 ''') multi_multi_same('''\ 5 ''') multi_multi_more('''\ 6 ''') different_indent( RuntimeError, '''7''' ) """ edits = [(2, 2, "a"), (3, 3, "b\n"), (4, 6, "c"), (7, 10, "d\n"), (11, 13, "e\n"), (14, 16, "f\ng\n"), (17, 20, "h")] history = expecttest.EditHistory() fn = 'not_a_real_file.py' for start_lineno, end_lineno, actual in edits: start_lineno = history.adjust_lineno(fn, start_lineno) end_lineno = history.adjust_lineno(fn, end_lineno) prog, delta = expecttest.replace_string_literal( prog, start_lineno, end_lineno, actual) # NB: it doesn't really matter start/end you record edit at history.record_edit(fn, start_lineno, delta) self.assertExpectedInline(prog, r""" single_single('''a''') single_multi('''\ b ''') multi_single('''c''') multi_multi_less('''\ d ''') multi_multi_same('''\ e ''') multi_multi_more('''\ f g ''') different_indent( RuntimeError, '''h''' ) """) def test_lineno_assumptions(self) -> None: def get_tb(s: str) -> traceback.StackSummary: return traceback.extract_stack(limit=2) tb1 = get_tb("") tb2 = get_tb("""a b c""") assert isinstance(tb1[0].lineno, int) if expecttest.LINENO_AT_START: # tb2's stack starts on the next line self.assertEqual(tb1[0].lineno + 1, tb2[0].lineno) else: # starts at the end here self.assertEqual(tb1[0].lineno + 1 + 2, tb2[0].lineno) def test_smoketest_accept_twice(self) -> None: with tempfile.TemporaryDirectory() as d: dst = os.path.join(d, 'test.py') shutil.copy(os.path.join(os.path.dirname(__file__), 'smoketests/accept_twice.py'), dst) r = sh(dst) self.assertNotEqual(r.returncode, 0) r = sh(dst, accept=True) self.assertExpectedInline(r.stdout.replace(dst, 'test.py'), '''\ Accepting new output for __main__.Test.test_a at test.py:10 Skipping already accepted output for __main__.Test.test_a at test.py:10 Accepting new output for __main__.Test.test_b at test.py:18 ''') r = sh(dst) self.assertEqual(r.returncode, 0) def load_tests(loader: unittest.TestLoader, tests: unittest.TestSuite, ignore: Any) -> unittest.TestSuite: tests.addTests(doctest.DocTestSuite(expecttest)) return tests if __name__ == '__main__': unittest.main()