diff --git a/examples/python.yml b/examples/python.yml index 0287ecc2..8b7abef6 100644 --- a/examples/python.yml +++ b/examples/python.yml @@ -27,20 +27,20 @@ cw-2: # NOTE: You can use Test or test, whichever you prefer. # Use "describe" to label your test suite. - Test.describe("two_oldest_ages:") - - # Use "it" to identify the conditions you are testing for - Test.it("should return the second oldest age first") - # using assert_equals will report the invalid values to the user - Test.assert_equals(results1[0], 45) - # using expect will just give a user a generic error message, unless you provide a message - Test.expect(results2[0] == 18, "Number is not the second oldest") - - # its best practice to test for multiple groups of tests, using it calls. - Test.it("should return the oldest age last") - - Test.assert_equals(results1[1], 87) - Test.expect(results2[1] == 83, "Number is not the oldest") + @Test.describe("Two Oldest Ages") + def describe1(): + # Use "it" to identify the conditions you are testing for + @Test.it("should return the second oldest age first") + def it1(): + # using assert_equals will report the invalid values to the user + Test.assert_equals(results1[0], 45) + # using expect will just give a user a generic error message, unless you provide a message + Test.expect(results2[0] == 18, "Number is not the second oldest") + # its best practice to test for multiple groups of tests, using it calls. + @Test.it("should return the oldest age last") + def it2(): + Test.assert_equals(results1[1], 87) + Test.expect(results2[1] == 83, "Number is not the oldest") bug fixes: initial: |- @@ -53,21 +53,20 @@ cw-2: fixture: |- # Use "describe" to define the test suite - test.describe('add method') - - # Use "it" to indicate a condition you are testing for - test.it('should add both arguments and return') - - # "assert_equals" will return information about what values were - # expect if the assertion fails. This can be very useful to other - # users trying to pass the kata. - test.assert_equals(add(1,2), 3) - - # "expect" is a lower level assertion that will allow you to test - # anything. It just needs a boolean result. You should pass a message - # as the second parameter so that if the assertion fails the user - # will be giving some useful information. - test.expect(add(1,1) == 2, "add(1,1) should == 2") + @test.describe('add method') + def describe1(): + # Use "it" to indicate a condition you are testing for + @test.it('should add both arguments and return') + def it1(): + # "assert_equals" will return information about what values were + # expect if the assertion fails. This can be very useful to other + # users trying to pass the kata. + test.assert_equals(add(1,2), 3) + # "expect" is a lower level assertion that will allow you to test + # anything. It just needs a boolean result. You should pass a message + # as the second parameter so that if the assertion fails the user + # will be giving some useful information. + test.expect(add(1,1) == 2, "add(1,1) should == 2") refactoring: initial: |- @@ -85,30 +84,29 @@ cw-2: fixture: |- # Use "describe" to define the test suite - test.describe('Person') - - jack = Person('Jack') - - # Use "it" to indicate a condition you are testing for - test.it('should have a name') - - # "assert_equals" will return information about what values were - # expect if the assertion fails. This can be very useful to other - # users trying to pass the kata. - test.assert_equals(jack.name, "Jack") - - - test.it("should greet Jill") - - test.assert_equals(jack.greet("Jill"), "Hello Jill, my name is Jack") - - test.it("should greet other people as well") - - # unlike "assert_equals", "expect" is a lower level assertion that - # takes a boolean to determine if it passes. If it fails it will - # output the message that you give it, or a generic one. It is a good - # idea to provide a custom error message to help users pass the kata - test.expect(jack.greet("Jane") == "Hello Jane, my name is Jack", "Jack apparently is only able to greet Jane") + @test.describe('Person') + def describe1(): + jack = Person('Jack') + + # Use "it" to indicate a condition you are testing for + @test.it('should have a name') + def it1(): + # "assert_equals" will return information about what values were + # expect if the assertion fails. This can be very useful to other + # users trying to pass the kata. + test.assert_equals(jack.name, "Jack") + + @test.it("should greet Jill") + def it2(): + test.assert_equals(jack.greet("Jill"), "Hello Jill, my name is Jack") + + @test.it("should greet other people as well") + def it3(): + # unlike "assert_equals", "expect" is a lower level assertion that + # takes a boolean to determine if it passes. If it fails it will + # output the message that you give it, or a generic one. It is a good + # idea to provide a custom error message to help users pass the kata + test.expect(jack.greet("Jane") == "Hello Jane, my name is Jack", "Jack apparently is only able to greet Jane") reference: initial: |- @@ -119,17 +117,16 @@ cw-2: fixture: |- # Use test.describe (or Test.describe) to describe your test suite - test.describe("websites") - - # Use "it" calls to describe the specific test case - test.it("should have the value 'codewars' inside of it") - - # assert equals will pass if both items equal each other (using ==). If - # the test fails, assert_equals will output a descriptive message indicating - # what the values were expected to be. - test.assert_equals(['codewars'], websites) - - # you can also use the lower level test.expect. If you use test.expect directly then - # you should provide a custom error message, as the default one will be pretty useless - # to users trying to pass the kata. - test.expect(['codewars'] == websites, 'Array does not have correct value') \ No newline at end of file + @test.describe("websites") + def describe1(): + # Use "it" calls to describe the specific test case + @test.it("should have the value 'codewars' inside of it") + def it1(): + # assert equals will pass if both items equal each other (using ==). If + # the test fails, assert_equals will output a descriptive message indicating + # what the values were expected to be. + test.assert_equals(['codewars'], websites) + # you can also use the lower level test.expect. If you use test.expect directly then + # you should provide a custom error message, as the default one will be pretty useless + # to users trying to pass the kata. + test.expect(['codewars'] == websites, 'Array does not have correct value') \ No newline at end of file diff --git a/examples/python3.yml b/examples/python3.yml index 02392cbb..8b7abef6 100644 --- a/examples/python3.yml +++ b/examples/python3.yml @@ -27,20 +27,20 @@ cw-2: # NOTE: You can use Test or test, whichever you prefer. # Use "describe" to label your test suite. - Test.describe("two_oldest_ages:") - - # Use "it" to identify the conditions you are testing for - Test.it("should return the second oldest age first") - # using assert_equals will report the invalid values to the user - Test.assert_equals(results1[0], 45) - # using expect will just give a user a generic error message, unless you provide a message - Test.expect(results2[0] == 18, "Number is not the second oldest") - - # its best practice to test for multiple groups of tests, using it calls. - Test.it("should return the oldest age last") - - Test.assert_equals(results1[1], 87) - Test.expect(results2[1] == 83, "Number is not the oldest") + @Test.describe("Two Oldest Ages") + def describe1(): + # Use "it" to identify the conditions you are testing for + @Test.it("should return the second oldest age first") + def it1(): + # using assert_equals will report the invalid values to the user + Test.assert_equals(results1[0], 45) + # using expect will just give a user a generic error message, unless you provide a message + Test.expect(results2[0] == 18, "Number is not the second oldest") + # its best practice to test for multiple groups of tests, using it calls. + @Test.it("should return the oldest age last") + def it2(): + Test.assert_equals(results1[1], 87) + Test.expect(results2[1] == 83, "Number is not the oldest") bug fixes: initial: |- @@ -53,21 +53,20 @@ cw-2: fixture: |- # Use "describe" to define the test suite - test.describe('add method') - - # Use "it" to indicate a condition you are testing for - test.it('should add both arguments and return') - - # "assert_equals" will return information about what values were - # expect if the assertion fails. This can be very useful to other - # users trying to pass the kata. - test.assert_equals(add(1,2), 3) - - # "expect" is a lower level assertion that will allow you to test - # anything. It just needs a boolean result. You should pass a message - # as the second parameter so that if the assertion fails the user - # will be giving some useful information. - test.expect(add(1,1) == 2, "add(1,1) should == 2") + @test.describe('add method') + def describe1(): + # Use "it" to indicate a condition you are testing for + @test.it('should add both arguments and return') + def it1(): + # "assert_equals" will return information about what values were + # expect if the assertion fails. This can be very useful to other + # users trying to pass the kata. + test.assert_equals(add(1,2), 3) + # "expect" is a lower level assertion that will allow you to test + # anything. It just needs a boolean result. You should pass a message + # as the second parameter so that if the assertion fails the user + # will be giving some useful information. + test.expect(add(1,1) == 2, "add(1,1) should == 2") refactoring: initial: |- @@ -85,29 +84,29 @@ cw-2: fixture: |- # Use "describe" to define the test suite - test.describe('Person') - - jack = Person('Jack') - - # Use "it" to indicate a condition you are testing for - test.it('should have a name') - - # "assert_equals" will return information about what values were - # expect if the assertion fails. This can be very useful to other - # users trying to pass the kata. - test.assert_equals(jack.name, "Jack") - - test.it("should greet Jill") - - test.assert_equals(jack.greet("Jill"), "Hello Jill, my name is Jack") - - test.it("should greet other people as well") - - # unlike "assert_equals", "expect" is a lower level assertion that - # takes a boolean to determine if it passes. If it fails it will - # output the message that you give it, or a generic one. It is a good - # idea to provide a custom error message to help users pass the kata - test.expect(jack.greet("Jane") == "Hello Jane, my name is Jack", "Jack apparently is only able to greet Jane") + @test.describe('Person') + def describe1(): + jack = Person('Jack') + + # Use "it" to indicate a condition you are testing for + @test.it('should have a name') + def it1(): + # "assert_equals" will return information about what values were + # expect if the assertion fails. This can be very useful to other + # users trying to pass the kata. + test.assert_equals(jack.name, "Jack") + + @test.it("should greet Jill") + def it2(): + test.assert_equals(jack.greet("Jill"), "Hello Jill, my name is Jack") + + @test.it("should greet other people as well") + def it3(): + # unlike "assert_equals", "expect" is a lower level assertion that + # takes a boolean to determine if it passes. If it fails it will + # output the message that you give it, or a generic one. It is a good + # idea to provide a custom error message to help users pass the kata + test.expect(jack.greet("Jane") == "Hello Jane, my name is Jack", "Jack apparently is only able to greet Jane") reference: initial: |- @@ -118,17 +117,16 @@ cw-2: fixture: |- # Use test.describe (or Test.describe) to describe your test suite - test.describe("websites") - - # Use "it" calls to describe the specific test case - test.it("should have the value 'codewars' inside of it") - - # assert equals will pass if both items equal each other (using ==). If - # the test fails, assert_equals will output a descriptive message indicating - # what the values were expected to be. - test.assert_equals(['codewars'], websites) - - # you can also use the lower level test.expect. If you use test.expect directly then - # you should provide a custom error message, as the default one will be pretty useless - # to users trying to pass the kata. - test.expect(['codewars'] == websites, 'Array does not have correct value') \ No newline at end of file + @test.describe("websites") + def describe1(): + # Use "it" calls to describe the specific test case + @test.it("should have the value 'codewars' inside of it") + def it1(): + # assert equals will pass if both items equal each other (using ==). If + # the test fails, assert_equals will output a descriptive message indicating + # what the values were expected to be. + test.assert_equals(['codewars'], websites) + # you can also use the lower level test.expect. If you use test.expect directly then + # you should provide a custom error message, as the default one will be pretty useless + # to users trying to pass the kata. + test.expect(['codewars'] == websites, 'Array does not have correct value') \ No newline at end of file diff --git a/frameworks/python/cw-2.py b/frameworks/python/cw-2.py index 67f68650..805d4fd5 100644 --- a/frameworks/python/cw-2.py +++ b/frameworks/python/cw-2.py @@ -1,24 +1,53 @@ from __future__ import print_function +import re, six class AssertException(Exception): pass +'''Fix the dreaded Unicode Error Trap''' +def uni_print(*args, **kwargs): + from sys import stdout + sep = kwargs.get('sep', ' ') + end = kwargs.get('end', '\n') + file = kwargs.get('file', stdout) + + def _replace(c): + if ord(c) >= 128: return u'&#{};'.format(ord(c)) + return c + def _escape(s): + escaped = ''.join(_replace(c) for c in six.text_type(s)) + escaped = re.sub(r'\\u([\da-f]{4})', lambda m: '&#{};'.format(int(m.group(1), 16)), escaped) + escaped = re.sub(r'\\U([\da-f]{8})', lambda m: '&#{};'.format(int(m.group(1), 16)), escaped) + return escaped + + six.print_(*map(_escape, args), sep=_escape(sep), end=_escape(end), file=file) + + def format_message(message): - return message.replace("\n", "<:LF:>") + def _replace(c): + if ord(c) >= 65536: return r'\U' + hex(ord(c))[2:].zfill(8) + if ord(c) >= 128: return r'\u' + hex(ord(c))[2:].zfill(4) + return c + def _escape(s): return ''.join(_replace(c) for c in s) + return _escape(message.replace("\n", "<:LF:>")) + + +def display(type, message, label="", mode=""): + print("\n<{0}:{1}:{2}>{3}".format(type.upper(), mode.upper(), label, format_message(message))) def expect(passed=None, message=None, allow_raise=False): if passed: - print("\nTest Passed") + display('PASSED', 'Test Passed') else: message = message or "Value is not what was expected" - print("\n{0}".format(message)) + display('FAILED', message) if allow_raise: raise AssertException(message) -def assert_equals(actual, expected, message=None, allow_raise=True): +def assert_equals(actual, expected, message=None, allow_raise=False): equals_msg = "{0} should equal {1}".format(repr(actual), repr(expected)) if message is None: message = equals_msg @@ -28,15 +57,14 @@ def assert_equals(actual, expected, message=None, allow_raise=True): expect(actual == expected, message, allow_raise) -def assert_not_equals(actual, expected, message=None, allow_raise=True): - equals_msg = \ - "{0} should not equal {1}".format(repr(actual), repr(expected)) +def assert_not_equals(actual, expected, message=None, allow_raise=False): + equals_msg = "{0} should not equal {1}".format(repr(actual), repr(expected)) if message is None: message = equals_msg else: message += ": " + equals_msg - expect(actual != expected, message, allow_raise) + expect(not (actual == expected), message, allow_raise) def expect_error(message, function): @@ -48,13 +76,67 @@ def expect_error(message, function): expect(passed, message) -def describe(message): - print("\n{0}".format(format_message(message))) - - -def it(message): - print("\n{0}".format(format_message(message))) - - -def display(type, message, label="", mode=""): - print("\n<{0}:{1}:{2}>{3}".format(type.upper(), mode.upper(), label, format_message(message))) +def pass_(): expect(True) +def fail(message): expect(False, message) + + +def assert_approx_equals(actual, expected, margin=1e-9, message=None, allow_raise=False): + equals_msg = "{0} should be close to {1} with absolute or relative margin of {2}".format( + repr(actual), repr(expected), repr(margin)) + if message is None: message = equals_msg + else: message += ": " + equals_msg + div = max(abs(actual), abs(expected), 1) + expect(abs((actual - expected) / div) < margin, message, allow_raise) + + +''' +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() + try: func() + except: + fail('Unexpected exception raised') + tb_str = ''.join(format_exception(*exc_info())) + display('ERROR', tb_str) + display('COMPLETEDIN', '{:.2f}'.format((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. +''' +def timeout(sec): + def wrapper(func): + from multiprocessing import Process + process = Process(target=func) + process.start() + process.join(sec) + if process.is_alive(): + fail('Exceeded time limit of {:.3f} seconds'.format(sec)) + process.terminate() + process.join() + return wrapper diff --git a/frameworks/python3/cw-2.py b/frameworks/python3/cw-2.py index 3a69f7b8..e02589c4 100644 --- a/frameworks/python3/cw-2.py +++ b/frameworks/python3/cw-2.py @@ -4,21 +4,48 @@ class AssertException(Exception): pass +_print = print + + +'''Fix the dreaded Unicode Error Trap''' +def print(*args, **kwargs): + from sys import stdout + sep = kwargs.get('sep', ' ') + end = kwargs.get('end', '\n') + file = kwargs.get('file', stdout) + + def _replace(c): + if ord(c) >= 128: return u'&#{};'.format(ord(c)) + return c + def _escape(s): return ''.join(_replace(c) for c in s) + + _print(*map(_escape, args), sep=_escape(sep), end=_escape(end), file=file) + + def format_message(message): - return message.replace("\n", "<:LF:>") + def _replace(c): + if ord(c) >= 65536: return r'\U' + hex(ord(c))[2:].zfill(8) + if ord(c) >= 128: return r'\u' + hex(ord(c))[2:].zfill(4) + return c + def _escape(s): return ''.join(_replace(c) for c in s) + return _escape(message.replace("\n", "<:LF:>")) + + +def display(type, message, label="", mode=""): + print("\n<{0}:{1}:{2}>{3}".format(type.upper(), mode.upper(), label, format_message(message))) def expect(passed=None, message=None, allow_raise=False): if passed: - print("Test Passed") + display('PASSED', 'Test Passed') else: message = message or "Value is not what was expected" - print("{0}".format(message)) + display('FAILED', message) if allow_raise: raise AssertException(message) -def assert_equals(actual, expected, message=None, allow_raise=True): +def assert_equals(actual, expected, message=None, allow_raise=False): equals_msg = "{0} should equal {1}".format(repr(actual), repr(expected)) if message is None: message = equals_msg @@ -28,15 +55,14 @@ def assert_equals(actual, expected, message=None, allow_raise=True): expect(actual == expected, message, allow_raise) -def assert_not_equals(actual, expected, message=None, allow_raise=True): - equals_msg = \ - "{0} should not equal {1}".format(repr(actual), repr(expected)) +def assert_not_equals(actual, expected, message=None, allow_raise=False): + equals_msg = "{0} should not equal {1}".format(repr(actual), repr(expected)) if message is None: message = equals_msg else: message += ": " + equals_msg - expect(actual != expected, message, allow_raise) + expect(not (actual == expected), message, allow_raise) def expect_error(message, function): @@ -48,9 +74,67 @@ def expect_error(message, function): expect(passed, message) -def describe(message): - print("{0}".format(format_message(message))) - - -def it(message): - print("{0}".format(format_message(message))) +def pass_(): expect(True) +def fail(message): expect(False, message) + + +def assert_approx_equals(actual, expected, margin=1e-9, message=None, allow_raise=False): + equals_msg = "{0} should be close to {1} with absolute or relative margin of {2}".format( + repr(actual), repr(expected), repr(margin)) + if message is None: message = equals_msg + else: message += ": " + equals_msg + div = max(abs(actual), abs(expected), 1) + expect(abs((actual - expected) / div) < margin, message, allow_raise) + + +''' +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() + try: func() + except: + fail('Unexpected exception raised') + tb_str = ''.join(format_exception(*exc_info())) + display('ERROR', tb_str) + display('COMPLETEDIN', '{:.2f}'.format((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. +''' +def timeout(sec): + def wrapper(func): + from multiprocessing import Process + process = Process(target=func) + process.start() + process.join(sec) + if process.is_alive(): + fail('Exceeded time limit of {:.3f} seconds'.format(sec)) + process.terminate() + process.join() + return wrapper diff --git a/test/runners/python_spec.js b/test/runners/python_spec.js index b4f69437..b0e07d9e 100644 --- a/test/runners/python_spec.js +++ b/test/runners/python_spec.js @@ -256,6 +256,134 @@ describe('Output format commands', function() { } }); +describe('Fixed and new features', function() { + for (const v of ['2', '3', '3.6']) { + it(`should not block execution on failed test by default (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: [ + `test.assert_equals(a, 2)`, + `test.assert_equals(a, 1)`, + ].join('\n'), + }, function(buffer) { + expect(buffer.stdout).to.include('\n'); + done(); + }); + }); + + it(`should support legacy style describe (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: [ + `test.describe('describe')`, + `test.assert_equals(a, 1)`, + ].join('\n'), + }, function(buffer) { + expect(buffer.stdout).to.include('\ndescribe'); + expect(buffer.stdout).to.include('\n'); + done(); + }); + }); + + it(`should support new style describe (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: [ + `@test.describe('describe')`, + `def describe1():`, + ` test.assert_equals(a, 1)`, + ].join('\n'), + }, function(buffer) { + expect(buffer.stdout).to.include('\ndescribe'); + expect(buffer.stdout).to.include('\n'); + expect(buffer.stdout).to.include('\n'); + done(); + }); + }); + + it(`should support timeout (passing) (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: [ + `@test.describe('describe')`, + `def describe1():`, + ` @test.timeout(0.01)`, + ` def dummy(): test.pass_()`, + ].join('\n'), + }, function(buffer) { + expect(buffer.stdout).to.include('\ndescribe'); + expect(buffer.stdout).to.include('\n'); + expect(buffer.stdout).to.include('\n'); + done(); + }); + }); + + it(`should support timeout (failing) (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: [ + `@test.describe('describe')`, + `def describe1():`, + ` @test.timeout(0.01)`, + ` def count():`, + ` x = 0`, + ` while x < 10 ** 9: x += 1`, + ` test.pass_()`, + ].join('\n'), + }, function(buffer) { + expect(buffer.stdout).to.include('\ndescribe'); + expect(buffer.stdout).to.include('\nExceeded time limit'); + expect(buffer.stdout).to.include('\n'); + done(); + }); + }); + + it(`should support unicode output (log) (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: 'test.uni_print(1, "a", u"\\uac00", [314159, "b", u"\\uac01"])', + }, function(buffer) { + expect(buffer.stdout).to.include('1 a 가'); + expect(buffer.stdout).to.include('314159'); + expect(buffer.stdout).to.include('b'); + expect(buffer.stdout).to.include('각'); + done(); + }); + }); + + it(`should support unicode output (test output) (Python${v} cw-2)`, function(done) { + runner.run({ + language: 'python', + languageVersion: v, + testFramework: 'cw-2', + code: 'a = 1', + fixture: 'test.assert_equals(u"\\uac00", "")', + }, function(buffer) { + expect(buffer.stdout).to.include('\\uac00'); + done(); + }); + }); + } +}); + describe('unittest no concat', function() { afterEach(function cleanup(done) { exec([ @@ -289,7 +417,6 @@ describe('unittest no concat', function() { done(); }); }); - it(`should handle a failed assetion (${v})`, function(done) { runner.run({ language: 'python', @@ -309,7 +436,6 @@ describe('unittest no concat', function() { done(); }); }); - it(`syntax error in solution show line numbers (${v})`, function(done) { runner.run({ language: 'python', @@ -331,7 +457,6 @@ describe('unittest no concat', function() { done(); }); }); - it(`should handle error (${v})`, function(done) { runner.run({ language: 'python', @@ -351,7 +476,6 @@ describe('unittest no concat', function() { done(); }); }); - it(`should handle tests in multiple suites (${v})`, function(done) { // combined into one suite runner.run({