From f4c09d21fe3e31e867ac5a5a787f1f860214f6c7 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Tue, 21 May 2024 03:19:53 +0200 Subject: [PATCH] init --- .gitignore | 7 +++ .vscode/settings.json | 3 + LICENCE | 21 +++++++ README.md | 36 +++++++++++ assets/output.png | Bin 0 -> 54909 bytes build.sh | 14 +++++ clean.sh | 9 +++ example/inlines.py | 15 +++++ example/tests.py | 73 ++++++++++++++++++++++ publish.sh | 6 ++ pyproject.toml | 30 +++++++++ setup.sh | 9 +++ src/okipy/__init__.py | 3 + src/okipy/colors.py | 10 +++ src/okipy/lib.py | 137 ++++++++++++++++++++++++++++++++++++++++++ src/okipy/main.py | 49 +++++++++++++++ src/okipy/py.typed | 1 + src/okipy/utils.py | 20 ++++++ 18 files changed, 443 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 LICENCE create mode 100644 README.md create mode 100644 assets/output.png create mode 100755 build.sh create mode 100755 clean.sh create mode 100644 example/inlines.py create mode 100755 example/tests.py create mode 100755 publish.sh create mode 100644 pyproject.toml create mode 100755 setup.sh create mode 100644 src/okipy/__init__.py create mode 100644 src/okipy/colors.py create mode 100644 src/okipy/lib.py create mode 100755 src/okipy/main.py create mode 100644 src/okipy/py.typed create mode 100644 src/okipy/utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5a6d32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/venv + +/dist +/src/okipy.egg-info + +__pycache__ +.mypy_cache diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..beb906a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "standard", +} diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a9dd0e8 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Oki.py + +Minimal, typed, functional and dynamic test library. + +## Usage + +```py +def main(): + suite = Suite() + + @suite.test() + def adds_correctly(ctx): + assert 2 + 2 == 4 + + @suite.test() + def adds_incorrectly(ctx): + print("this must work") + assert 2 + 2 == 5 + + @suite.test() + def it_doesnt_fail(ctx): + print("hope this doesnt fail ...") + print("oh, this will fail", file=sys.stderr) + raise Exception("Expected failure.") + + @suite.test() + def it_fails(ctx): + with ctx.assert_raises(): + raise Exception("Expected failure.") + + suite.run() +``` + +Results in this output when run : + +![test output screenshot](./assets/output.png) diff --git a/assets/output.png b/assets/output.png new file mode 100644 index 0000000000000000000000000000000000000000..e2ac425dc91d25b55f59d5fe50b87331b6f192f3 GIT binary patch literal 54909 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 ML{_*=NYDTO1Kcf 0: + print(f" {yellow('Stdout')} {bold(str(len(self.stdout)))} {yellow('lines')}") + for line in self.stdout: + print(line) + + if len(self.stderr) > 0: + print(f" {yellow('Stderr')} {bold(str(len(self.stderr)))} {yellow('lines')}") + for line in self.stderr: + print(line) + print() + + +class Test: + name: str + procedure: Callable[[Ctx], Any] + + def __init__(self, name: str, procedure: Callable[[Ctx], Any]) -> None: + self.name = name + self.procedure = procedure + + def run(self): + with CaptureOutput() as capture: + try: + self.procedure(Ctx(self)) + except BaseException as ex: + return TestFailure(self, capture.stdout, capture.stderr, ex) + + +class Suite: + """ + A suite of tests. + + Append test with the `Suite.test` decorator : + ```py + suite = Suite("Feur") + + @suite.test() + def it_works(ctx): + assert 1 + 1 == 2 + + suite.run() + ``` + """ + name: Union[str, None] + tests: list[Test] + + def __init__(self, name: Union[str, None] = None) -> None: + self.name = name + self.tests = [] + + def test(self): + def decorate(procedure: Callable[[Ctx], Any]) -> Callable[[Ctx], Any]: + name = procedure.__name__ + self.tests.append(Test(name, procedure)) + return procedure + return decorate + + def run(self, filters: list[str] = []): + if self.name is not None: + print(" ", green("Suite"), bold(self.name)) + to_run = [*self.filter_tests(filters)] + print(yellow('Running'), bold(str(len(to_run))), yellow('/'), bold(str(len(self.tests))), yellow('tests')) + print() + failures = list[TestFailure]() + for test in to_run: + failure = test.run() + if failure is None: + print(f" {green('Ok')} {bold(test.name)}") + else: + print(f" {red('Err')} {bold(test.name)}") + failures.append(failure) + print() + print("", yellow('Failed'), bold(str(len(failures))), yellow('/'), bold(str(len(to_run))), yellow('tests')) + for (index, failure) in enumerate(failures): + failure.print(index) + return failures + + def filter_tests(self, filters: list[str]): + for test in self.tests: + oki = True + for filter in filters: + if filter not in test.name: + oki = False + if oki: + yield test + + +def get_inline_suite() -> Suite: + existing: Optional[Suite] = globals().get('_okipy_inline_suite') + if existing is None: + globals()['_okipy_inline_suite'] = existing = Suite() + return existing + + +def test(): + def decorate(procedure: Callable[[Ctx], Any]) -> Callable[[Ctx], Any]: + return get_inline_suite().test()(procedure) + return decorate diff --git a/src/okipy/main.py b/src/okipy/main.py new file mode 100755 index 0000000..7142364 --- /dev/null +++ b/src/okipy/main.py @@ -0,0 +1,49 @@ +#!/usr/bin/env -S python + +from argparse import ArgumentParser +from os.path import realpath, dirname +import subprocess + + +def main(): + (source, filters) = parse_args() + path = realpath(source) + directory = dirname(path) + input = script_from_file(path, filters) + subprocess.run(["python"], text=True, cwd=directory, input=input) + + +def parse_args(): + parser = ArgumentParser( + prog='okipy', + description='Okipy test runner.' + ) + parser.add_argument('source') + parser.add_argument('filters', nargs='*') + parsed = parser.parse_args() + dict = vars(parsed) + source = dict['source'] + filters = dict['filters'] + assert type(source) is str + assert type(filters) is list + return (source, filters) + + +def script_from_file(path: str, filters: list[str]): + content = read_text_file(path) + filters_strs = [f'"{filter}"' for filter in filters] + filters_str = f'[{",".join(filters_strs)}]' + return f""" +{content} + +from okipy import get_inline_suite +get_inline_suite().run({filters_str}) +""" + + +def read_text_file(path: str): + with open(path, "r") as file: + return "\n".join(file.readlines()) + + +if __name__ == '__main__': main() diff --git a/src/okipy/py.typed b/src/okipy/py.typed new file mode 100644 index 0000000..2c85c45 --- /dev/null +++ b/src/okipy/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. The mypy package uses inline types. \ No newline at end of file diff --git a/src/okipy/utils.py b/src/okipy/utils.py new file mode 100644 index 0000000..82d9e0d --- /dev/null +++ b/src/okipy/utils.py @@ -0,0 +1,20 @@ + +from io import StringIO +import sys + + +class CaptureOutput(): + def __enter__(self): + self.stdout = list[str]() + self._stdout = sys.stdout + sys.stdout = self._str_stdout = StringIO() + self.stderr = list[str]() + self._stderr = sys.stderr + sys.stderr = self._str_stderr = StringIO() + return self + + def __exit__(self, *_args): + self.stdout.extend(self._str_stdout.getvalue().splitlines()) + self.stderr.extend(self._str_stderr.getvalue().splitlines()) + sys.stdout = self._stdout + sys.stderr = self._stderr