From 4499fe01ba0d9f200da4943ff9b56a84be5185b4 Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:35:05 -0700 Subject: [PATCH 1/8] add example of custom assertions --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 942f76f..4314b07 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,52 @@ # Codewars Test Framework for Python - -## Example +## Basic Example ```python -from solution import add import codewars_test as test +from solution import add @test.describe('Example Tests') def example_tests(): + @test.it('Example Test Case') def example_test_case(): test.assert_equals(add(1, 1), 2, 'Optional Message on Failure') ``` + +## Using Other Assertions + +Any function that raises an `AssertionError` upon a failed assertion can be used instead of `codewars_test` assertions: + +```python +import numpy as np +import pandas as pd +import codewars_test as test + +@test.describe('Example Tests') +def test_custom_assertions(): + + @test.it('Test something in numpy') + def test_numpy_assertion(): + actual = np.reshape(range(16), [4, 4]) + expected = np.reshape(range(16, 0, -1), [4, 4]) + np.testing.assert_equal(expected, actual) + + @test.it('Test something in pandas') + def test_pandas_assertion(): + actual = pd.DataFrame({'foo': [1, 2, 3]}) + expected = pd.DataFrame({'foo': [1, 42, 3]}) + pd.testing.assert_frame_equal(expected, actual) + + @test.it('Test something using a custom assertion') + def test_custom_assertion(): + def custom_assert_eq(actual, expected, msg=None): + if actual != expected: + default_msg = f"`{actual}` did not equal expected `{expected}`" + raise AssertionError(default_msg if msg is None else msg) + + actual = 2 + expected = 1 + custom_assert_eq(actual, expected) +``` + From 73eee3446319b041466024d136d73f055d547d00 Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:36:36 -0700 Subject: [PATCH 2/8] add .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58200d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ From 934bd53c704887f2f12cd6da1876c137b7b112fb Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:37:40 -0700 Subject: [PATCH 3/8] quote adjustment for consistency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4314b07..75f6c6b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ def test_custom_assertions(): def test_custom_assertion(): def custom_assert_eq(actual, expected, msg=None): if actual != expected: - default_msg = f"`{actual}` did not equal expected `{expected}`" + default_msg = f'`{actual}` did not equal expected `{expected}`' raise AssertionError(default_msg if msg is None else msg) actual = 2 From 9b77819598ae58abc2b38cbbdea7392b5ddd7a62 Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:43:52 -0700 Subject: [PATCH 4/8] h2 to h3 for headers --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 75f6c6b..735ff0a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Codewars Test Framework for Python -## Basic Example +### Basic Example ```python import codewars_test as test @@ -14,7 +14,7 @@ def example_tests(): test.assert_equals(add(1, 1), 2, 'Optional Message on Failure') ``` -## Using Other Assertions +### Using Other Assertions Any function that raises an `AssertionError` upon a failed assertion can be used instead of `codewars_test` assertions: From 5f266c79a54e5aa67d4f9f52fe9aa81b1539a1ba Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:44:42 -0700 Subject: [PATCH 5/8] simplify language --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 735ff0a..7fdd592 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ def example_tests(): ### Using Other Assertions -Any function that raises an `AssertionError` upon a failed assertion can be used instead of `codewars_test` assertions: +Any function that raises an `AssertionError` can be used instead of `codewars_test` assertions: ```python import numpy as np From ad23e52124ce53f75e5706f1493948fe6926a9e8 Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:24:53 -0700 Subject: [PATCH 6/8] add custom assertion wrapper --- README.md | 20 +++++++++++--------- codewars_test/test_framework.py | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7fdd592..b2fd640 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ def example_tests(): ### Using Other Assertions -Any function that raises an `AssertionError` can be used instead of `codewars_test` assertions: +Any function that raises an `AssertionError` can be used alongside `codewars_test` assertions. Decorate the function using `codewars_test.make_assertion` before calling it. This ensures that test output is correctly formatted as expected by the runner. In the following example, the tests are intended to fail in order to show the custom output prints. ```python import numpy as np @@ -25,28 +25,30 @@ import codewars_test as test @test.describe('Example Tests') def test_custom_assertions(): + np.testing.assert_equal = test.make_assertion(np.testing.assert_equal) + pd.testing.assert_frame_equal = test.make_assertion(pd.testing.assert_frame_equal) + + @test.make_assertion + def custom_assert_eq(actual, expected, msg=None): + if actual != expected: + default_msg = f'`{actual}` did not equal expected `{expected}`' + raise AssertionError(default_msg if msg is None else msg) @test.it('Test something in numpy') def test_numpy_assertion(): actual = np.reshape(range(16), [4, 4]) expected = np.reshape(range(16, 0, -1), [4, 4]) - np.testing.assert_equal(expected, actual) + np.testing.assert_equal(actual, expected) @test.it('Test something in pandas') def test_pandas_assertion(): actual = pd.DataFrame({'foo': [1, 2, 3]}) expected = pd.DataFrame({'foo': [1, 42, 3]}) - pd.testing.assert_frame_equal(expected, actual) + pd.testing.assert_frame_equal(actual, expected) @test.it('Test something using a custom assertion') def test_custom_assertion(): - def custom_assert_eq(actual, expected, msg=None): - if actual != expected: - default_msg = f'`{actual}` did not equal expected `{expected}`' - raise AssertionError(default_msg if msg is None else msg) - actual = 2 expected = 1 custom_assert_eq(actual, expected) ``` - diff --git a/codewars_test/test_framework.py b/codewars_test/test_framework.py index 50b9f85..76f9343 100644 --- a/codewars_test/test_framework.py +++ b/codewars_test/test_framework.py @@ -1,4 +1,5 @@ from __future__ import print_function +import functools class AssertException(Exception): @@ -85,6 +86,21 @@ def assert_approx_equals( expect(abs((actual - expected) / div) < margin, message, allow_raise) +def make_assertion(func): + ''' + Wraps an assertion function to emit pass/failure stdout prints. + The function should raise an AssertionError to cause a failure. + ''' + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + pass_() + except AssertionError as e: + fail(str(e)) + return wrapper + + ''' Usage: @describe('describe text') @@ -109,8 +125,6 @@ def wrapper(func): time = timer() try: func() - except AssertionError as e: - display('FAILED', str(e)) except Exception: fail('Unexpected exception raised') tb_str = ''.join(format_exception(*exc_info())) From cc912a028307ec4e797e51e17fa1e15d6de8f92b Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:39:05 -0700 Subject: [PATCH 7/8] add make assertion wrapper --- README.md | 3 ++- codewars_test/test_framework.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2fd640..bda90ce 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ def example_tests(): ### Using Other Assertions -Any function that raises an `AssertionError` can be used alongside `codewars_test` assertions. Decorate the function using `codewars_test.make_assertion` before calling it. This ensures that test output is correctly formatted as expected by the runner. In the following example, the tests are intended to fail in order to show the custom output prints. +Any function that raises an `AssertionError` can be used alongside built-in `codewars_test` assertions. Decorate the function using `codewars_test.make_assertion` before calling it. This ensures that test output is correctly formatted as expected by the runner. In the following example, the tests are intended to fail in order to show the custom output. ```python import numpy as np @@ -52,3 +52,4 @@ def test_custom_assertions(): expected = 1 custom_assert_eq(actual, expected) ``` + diff --git a/codewars_test/test_framework.py b/codewars_test/test_framework.py index 76f9343..1ece6d7 100644 --- a/codewars_test/test_framework.py +++ b/codewars_test/test_framework.py @@ -90,6 +90,15 @@ def make_assertion(func): ''' Wraps an assertion function to emit pass/failure stdout prints. The function should raise an AssertionError to cause a failure. + + @test.make_assertion + def custom_assert_eq(actual, expected, msg=None): + if actual != expected: + default_msg = f'`{actual}` did not equal expected `{expected}`' + raise AssertionError(default_msg if msg is None else msg) + + # or decorate with a normal function call: + custom_assert_eq = make_assertion(custom_assert_eq) ''' @functools.wraps(func) def wrapper(*args, **kwargs): @@ -165,3 +174,4 @@ def wrapped(): process.terminate() process.join() return wrapper + From b33e947bb3a105b5f9e99fe849b1a3ce56cae5f4 Mon Sep 17 00:00:00 2001 From: ggorlen <17895165+ggorlen@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:51:41 -0700 Subject: [PATCH 8/8] clean up imports and docstrings --- codewars_test/test_framework.py | 49 +++++++++++++++------------------ 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/codewars_test/test_framework.py b/codewars_test/test_framework.py index 1ece6d7..8eafd7a 100644 --- a/codewars_test/test_framework.py +++ b/codewars_test/test_framework.py @@ -1,5 +1,9 @@ from __future__ import print_function import functools +import sys +from multiprocessing import Process +from timeit import default_timer +from traceback import format_exception class AssertException(Exception): @@ -110,58 +114,49 @@ def wrapper(*args, **kwargs): return wrapper -''' -Usage: -@describe('describe text') -def describe1(): - @it('it text') - def it1(): - # some test cases... -''' - - def _timed_block_factory(opening_text): - from timeit import default_timer as timer - from traceback import format_exception - from sys import exc_info - def _timed_block_decorator(s, before=None, after=None): display(opening_text, s) def wrapper(func): if callable(before): before() - time = timer() + time = default_timer() try: func() except Exception: fail('Unexpected exception raised') - tb_str = ''.join(format_exception(*exc_info())) + tb_str = ''.join(format_exception(*sys.exc_info())) display('ERROR', tb_str) - display('COMPLETEDIN', '{:.2f}'.format((timer() - time) * 1000)) + display('COMPLETEDIN', '{:.2f}'.format((default_timer() - time) * 1000)) if callable(after): after() return wrapper return _timed_block_decorator -describe = _timed_block_factory('DESCRIBE') -it = _timed_block_factory('IT') - - ''' -Timeout utility Usage: -@timeout(sec) -def some_tests(): - any code block... -Note: Timeout value can be a float. +@describe('describe text') +def describe1(): + @it('it text') + def it1(): + # some test cases... ''' +describe = _timed_block_factory('DESCRIBE') +it = _timed_block_factory('IT') def timeout(sec): + ''' + Timeout utility + Usage: + @timeout(sec) + def some_tests(): + any code block... + Note: Timeout value can be a float. + ''' def wrapper(func): - from multiprocessing import Process msg = 'Should not throw any exceptions inside timeout' def wrapped():