From 987f5fd2a0638b146f86d19bb5920bc357a03826 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 25 Oct 2021 11:00:13 -0700 Subject: [PATCH 1/6] Added instruciton append for error handling, updated JinJa2 template and example and regenerated test file. --- .../forth/.docs/instructions.append.md | 37 +++++++ exercises/practice/forth/.meta/example.py | 18 +-- exercises/practice/forth/.meta/template.j2 | 18 ++- exercises/practice/forth/forth_test.py | 104 +++++++++++++----- 4 files changed, 139 insertions(+), 38 deletions(-) create mode 100644 exercises/practice/forth/.docs/instructions.append.md diff --git a/exercises/practice/forth/.docs/instructions.append.md b/exercises/practice/forth/.docs/instructions.append.md new file mode 100644 index 0000000000..fd1813223d --- /dev/null +++ b/exercises/practice/forth/.docs/instructions.append.md @@ -0,0 +1,37 @@ +# Instructions append + +## Customizing and Raising Exceptions + +Sometimes it is necessary to both [customize](https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions) and [`raise`](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) exceptions in your code. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. + +Custom exceptions can be created through new exception classes (see [`classes`](https://docs.python.org/3/tutorial/classes.html#tut-classes) for more detail.) that are typically subclasses of [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception). + +For situations where you know the error source will be a derivative of a certain exception type, you can choose to inherit from one of the [`built in error types`](https://docs.python.org/3/library/exceptions.html#base-classes) under the _Exception_ class. When raising the error, you should still include a meaningful message. + +This particular exercise requires that you create a _custom exception_ to be [raised](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)/"thrown" when the stack is not sufficiently filled. The tests will only pass if you customize an appropriate exception, `raise` that exception, and include appropriate error messages. + + +```python +# subclassing the Exception to create a StackUnderflowError +class StackUnderflowError(Exception): + """Exception raised when Stack is not full. + message: explanation of the error. + """ + def __init__(self, message): + self.message = message + + +# raising a StackUnderflowError +raise StackUnderflowError("Insufficient number of items in stack") +``` + +Additionally, this exercise requires that you raise several `built-in exceptions` with error messages. +To raise a `built-in exception` with a message, write the message as an argument to the `exception` type: + +```python +# an example when division by zero is attempted. +raise ZeroDivisionError("divide by zero") + +#an example when the operation is undefined. +raise ValueError("undefined operation") +``` diff --git a/exercises/practice/forth/.meta/example.py b/exercises/practice/forth/.meta/example.py index c8216dce3d..ce9173f538 100644 --- a/exercises/practice/forth/.meta/example.py +++ b/exercises/practice/forth/.meta/example.py @@ -1,5 +1,9 @@ class StackUnderflowError(Exception): - pass + """Exception raised when Stack is not full. + message: explanation of the error. + """ + def __init__(self, message): + self.message = message def is_integer(string): @@ -20,11 +24,11 @@ def evaluate(input_data): values.pop(0) key = values.pop(0).lower() if is_integer(key): - raise ValueError("Integers cannot be redefined") + raise ValueError("illegal operation") defines[key] = [ - x - for v in values - for x in defines.get(v, [v]) + x + for v in values + for x in defines.get(v, [v]) ] stack = [] input_data = input_data[-1].split() @@ -44,7 +48,7 @@ def evaluate(input_data): elif word == '/': divisor = stack.pop() if divisor == 0: - raise ZeroDivisionError("Attempted to divide by zero") + raise ZeroDivisionError("divide by zero") stack.append(int(stack.pop() / divisor)) elif word == 'dup': stack.append(stack[-1]) @@ -56,7 +60,7 @@ def evaluate(input_data): elif word == 'over': stack.append(stack[-2]) else: - raise ValueError("{} has not been defined".format(word)) + raise ValueError("undefined operation") except IndexError: raise StackUnderflowError("Insufficient number of items in stack") return stack diff --git a/exercises/practice/forth/.meta/template.j2 b/exercises/practice/forth/.meta/template.j2 index e343e19655..b6fe07e71a 100644 --- a/exercises/practice/forth/.meta/template.j2 +++ b/exercises/practice/forth/.meta/template.j2 @@ -7,15 +7,23 @@ {%- if case is error_case %} {%- if case["expected"]["error"] == "divide by zero" %} # {{ case["expected"]["error"] }} - with self.assertRaisesWithMessage(ZeroDivisionError): + with self.assertRaises(ZeroDivisionError) as err: + {{ case["property"] }}({{ case["input"]["instructions"] }}) + self.assertEqual(type(err.exception), ZeroDivisionError) + self.assertEqual(str(err.exception.args[0]), "{{ case["expected"]["error"] }}") {%- else %} {%- if "stack" in case["expected"]["error"] %} - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: + {{ case["property"] }}({{ case["input"]["instructions"] }}) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual(str(err.exception.args[0]), "Insufficient number of items in stack") {%- else %} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: + {{ case["property"] }}({{ case["input"]["instructions"] }}) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(str(err.exception.args[0]), "{{ case["expected"]["error"] }}") {%- endif %} {%- endif %} - {{ case["property"] }}({{ case["input"]["instructions"] }}) {%- else %} self.assertEqual({{ case["property"] }}({{ case["input"]["instructions"] }}), {{ case["expected"] }}) {%- endif %} @@ -27,5 +35,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case["description"], subcase) }} {% endfor %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index 6d7139eddc..6eabfb8fd5 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -16,34 +16,58 @@ def test_addition_can_add_two_numbers(self): self.assertEqual(evaluate(["1 2 +"]), [3]) def test_addition_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["+"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_addition_errors_if_there_is_only_one_value_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["1 +"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_subtraction_can_subtract_two_numbers(self): self.assertEqual(evaluate(["3 4 -"]), [-1]) def test_subtraction_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["-"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_subtraction_errors_if_there_is_only_one_value_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["1 -"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_multiplication_can_multiply_two_numbers(self): self.assertEqual(evaluate(["2 4 *"]), [8]) def test_multiplication_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["*"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_multiplication_errors_if_there_is_only_one_value_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["1 *"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_division_can_divide_two_numbers(self): self.assertEqual(evaluate(["12 3 /"]), [4]) @@ -53,16 +77,26 @@ def test_division_performs_integer_division(self): def test_division_errors_if_dividing_by_zero(self): # divide by zero - with self.assertRaisesWithMessage(ZeroDivisionError): + with self.assertRaises(ZeroDivisionError) as err: evaluate(["4 0 /"]) + self.assertEqual(type(err.exception), ZeroDivisionError) + self.assertEqual(str(err.exception.args[0]), "divide by zero") def test_division_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["/"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_division_errors_if_there_is_only_one_value_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["1 /"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_combined_arithmetic_addition_and_subtraction(self): self.assertEqual(evaluate(["1 2 + 4 -"]), [-1]) @@ -77,8 +111,12 @@ def test_dup_copies_the_top_value_on_the_stack(self): self.assertEqual(evaluate(["1 2 dup"]), [1, 2, 2]) def test_dup_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["dup"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_drop_removes_the_top_value_on_the_stack_if_it_is_the_only_one(self): self.assertEqual(evaluate(["1 drop"]), []) @@ -87,8 +125,12 @@ def test_drop_removes_the_top_value_on_the_stack_if_it_is_not_the_only_one(self) self.assertEqual(evaluate(["1 2 drop"]), [1]) def test_drop_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["drop"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_swap_swaps_the_top_two_values_on_the_stack_if_they_are_the_only_ones(self): self.assertEqual(evaluate(["1 2 swap"]), [2, 1]) @@ -99,12 +141,20 @@ def test_swap_swaps_the_top_two_values_on_the_stack_if_they_are_not_the_only_one self.assertEqual(evaluate(["1 2 3 swap"]), [1, 3, 2]) def test_swap_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["swap"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_swap_errors_if_there_is_only_one_value_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["1 swap"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_over_copies_the_second_element_if_there_are_only_two(self): self.assertEqual(evaluate(["1 2 over"]), [1, 2, 1]) @@ -113,12 +163,20 @@ def test_over_copies_the_second_element_if_there_are_more_than_two(self): self.assertEqual(evaluate(["1 2 3 over"]), [1, 2, 3, 2]) def test_over_errors_if_there_is_nothing_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["over"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_over_errors_if_there_is_only_one_value_on_the_stack(self): - with self.assertRaisesWithMessage(StackUnderflowError): + with self.assertRaises(StackUnderflowError) as err: evaluate(["1 over"]) + self.assertEqual(type(err.exception), StackUnderflowError) + self.assertEqual( + str(err.exception.args[0]), "Insufficient number of items in stack" + ) def test_user_defined_words_can_consist_of_built_in_words(self): self.assertEqual(evaluate([": dup-twice dup dup ;", "1 dup-twice"]), [1, 1, 1]) @@ -146,12 +204,16 @@ def test_user_defined_words_can_define_word_that_uses_word_with_the_same_name(se self.assertEqual(evaluate([": foo 10 ;", ": foo foo 1 + ;", "foo"]), [11]) def test_user_defined_words_cannot_redefine_non_negative_numbers(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: evaluate([": 1 2 ;"]) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(str(err.exception.args[0]), "illegal operation") def test_user_defined_words_errors_if_executing_a_non_existent_word(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: evaluate(["foo"]) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(str(err.exception.args[0]), "undefined operation") def test_case_insensitivity_dup_is_case_insensitive(self): self.assertEqual(evaluate(["1 DUP Dup dup"]), [1, 1, 1, 1]) @@ -170,11 +232,3 @@ def test_case_insensitivity_user_defined_words_are_case_insensitive(self): def test_case_insensitivity_definitions_are_case_insensitive(self): self.assertEqual(evaluate([": SWAP DUP Dup dup ;", "1 swap"]), [1, 1, 1, 1]) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() From fdb6a0e43193f46b22ac1ee278664fdd37cc8c51 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 25 Oct 2021 11:18:31 -0700 Subject: [PATCH 2/6] Added instruction append on error handling and edited JinJa2 template and example.py. Regenerated test file. --- .../go-counting/.docs/instructions.append.md | 14 +++++++++++ .../practice/go-counting/.meta/example.py | 2 +- .../practice/go-counting/.meta/template.j2 | 6 ++++- .../practice/go-counting/go_counting_test.py | 24 +++++++++---------- 4 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 exercises/practice/go-counting/.docs/instructions.append.md diff --git a/exercises/practice/go-counting/.docs/instructions.append.md b/exercises/practice/go-counting/.docs/instructions.append.md new file mode 100644 index 0000000000..e37a312f14 --- /dev/null +++ b/exercises/practice/go-counting/.docs/instructions.append.md @@ -0,0 +1,14 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when given invalid coordinates. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# when the coordinates for the piece are invalid +raise ValueError('Invalid coordinate') +``` diff --git a/exercises/practice/go-counting/.meta/example.py b/exercises/practice/go-counting/.meta/example.py index 4c2ca3d54a..06f2a165ec 100644 --- a/exercises/practice/go-counting/.meta/example.py +++ b/exercises/practice/go-counting/.meta/example.py @@ -37,7 +37,7 @@ def walk(self, x, y, def territory(self, x, y): if not self.valid(x, y): - raise ValueError('invalid coordinate') + raise ValueError('Invalid coordinate') if self.board[y][x] in STONES: return (NONE, set()) diff --git a/exercises/practice/go-counting/.meta/template.j2 b/exercises/practice/go-counting/.meta/template.j2 index bb3830407f..9a929b47c7 100644 --- a/exercises/practice/go-counting/.meta/template.j2 +++ b/exercises/practice/go-counting/.meta/template.j2 @@ -15,11 +15,15 @@ {% macro test_case(case) -%} + {%- set expected = case["expected"] %} + {%- set exp_error = expected["error"] %} def test_{{ case["description"] | to_snake }}(self): board = Board({{ case["input"]["board"] }}) {%- if case is error_case %} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: board.territory(x={{ case["input"]["x"] }}, y={{ case["input"]["y"] }}) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ exp_error }}") {%- else %} {%- if "owner" in case["expected"] %} stone, territory = board.territory(x={{ case["input"]["x"] }}, y={{ case["input"]["y"] }}) diff --git a/exercises/practice/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py index 60c97cc414..1e83c838da 100644 --- a/exercises/practice/go-counting/go_counting_test.py +++ b/exercises/practice/go-counting/go_counting_test.py @@ -37,23 +37,31 @@ def test_a_stone_and_not_a_territory_on_5x5_board(self): def test_invalid_because_x_is_too_low_for_5x5_board(self): board = Board([" B ", " B B ", "B W B", " W W ", " W "]) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: board.territory(x=-1, y=1) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Invalid coordinate") def test_invalid_because_x_is_too_high_for_5x5_board(self): board = Board([" B ", " B B ", "B W B", " W W ", " W "]) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: board.territory(x=5, y=1) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Invalid coordinate") def test_invalid_because_y_is_too_low_for_5x5_board(self): board = Board([" B ", " B B ", "B W B", " W W ", " W "]) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: board.territory(x=1, y=-1) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Invalid coordinate") def test_invalid_because_y_is_too_high_for_5x5_board(self): board = Board([" B ", " B B ", "B W B", " W W ", " W "]) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: board.territory(x=1, y=5) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Invalid coordinate") def test_one_territory_is_the_whole_board(self): board = Board([" "]) @@ -75,11 +83,3 @@ def test_two_region_rectangular_board(self): self.assertSetEqual(territories[BLACK], {(0, 0), (2, 0)}) self.assertSetEqual(territories[WHITE], set()) self.assertSetEqual(territories[NONE], set()) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() From e44159917a07843d9be66e2ec320af7585549a30 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 25 Oct 2021 11:33:08 -0700 Subject: [PATCH 3/6] Added instructions append for error handling, updated JinJa2 template and example, and regenerated test files. --- .../grains/.docs/instructions.append.md | 14 +++++++++++++ exercises/practice/grains/.meta/example.py | 6 +++--- exercises/practice/grains/.meta/template.j2 | 12 ++++++----- exercises/practice/grains/grains_test.py | 20 +++++++++---------- 4 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 exercises/practice/grains/.docs/instructions.append.md diff --git a/exercises/practice/grains/.docs/instructions.append.md b/exercises/practice/grains/.docs/instructions.append.md new file mode 100644 index 0000000000..a0d4abd6f9 --- /dev/null +++ b/exercises/practice/grains/.docs/instructions.append.md @@ -0,0 +1,14 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the square input is out of range. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# when the square value is not in the acceptable range +raise ValueError("square must be between 1 and 64") +``` diff --git a/exercises/practice/grains/.meta/example.py b/exercises/practice/grains/.meta/example.py index 062a08397f..08fa34a6f5 100644 --- a/exercises/practice/grains/.meta/example.py +++ b/exercises/practice/grains/.meta/example.py @@ -1,10 +1,10 @@ def square(number): if number == 0: - raise ValueError("Square input of zero is invalid.") + raise ValueError("square must be between 1 and 64") elif number < 0: - raise ValueError("Negative square input is invalid.") + raise ValueError("square must be between 1 and 64") elif number > 64: - raise ValueError("Square input greater than 64 is invalid.") + raise ValueError("square must be between 1 and 64") return 2 ** (number - 1) diff --git a/exercises/practice/grains/.meta/template.j2 b/exercises/practice/grains/.meta/template.j2 index d0a2be169e..6b4e0bb5e3 100644 --- a/exercises/practice/grains/.meta/template.j2 +++ b/exercises/practice/grains/.meta/template.j2 @@ -2,10 +2,14 @@ {% macro test_case(case) %} {%- set input = case["input"] %} + {%- set expected = case["expected"] %} + {%- set exp_error = expected["error"] %} def test_{{ case["description"] | to_snake }}(self): {%- if case is error_case %} - with self.assertRaisesWithMessage(ValueError): - {{ case["property"] | to_snake }}({{ input["square"] }}) + with self.assertRaises(ValueError) as err: + {{ case["property"] | to_snake }}({{ input["square"] }}) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ exp_error }}") {%- else%} self.assertEqual({{ case["property"] | to_snake }}( {{ input["square"] }}), {{ case["expected"] }}) @@ -23,6 +27,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%-else%} {{test_case(case)}} {%-endif%} - {% endfor %} - -{{ macros.footer(has_error_case) }} \ No newline at end of file + {% endfor %} \ No newline at end of file diff --git a/exercises/practice/grains/grains_test.py b/exercises/practice/grains/grains_test.py index ed528501ed..f7e277c070 100644 --- a/exercises/practice/grains/grains_test.py +++ b/exercises/practice/grains/grains_test.py @@ -31,24 +31,22 @@ def test_grains_on_square_64(self): self.assertEqual(square(64), 9223372036854775808) def test_square_0_raises_an_exception(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: square(0) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "square must be between 1 and 64") def test_negative_square_raises_an_exception(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: square(-1) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "square must be between 1 and 64") def test_square_greater_than_64_raises_an_exception(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: square(65) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "square must be between 1 and 64") def test_returns_the_total_number_of_grains_on_the_board(self): self.assertEqual(total(), 18446744073709551615) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() From b40b5655279b62c0f282680102a68de584106813 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 25 Oct 2021 11:40:50 -0700 Subject: [PATCH 4/6] Added instructions append for error handling, updated JinJa2 template and example, and regenerated test files. --- .../hamming/.docs/instructions.append.md | 14 ++++++++++ exercises/practice/hamming/.meta/example.py | 2 +- exercises/practice/hamming/.meta/template.j2 | 6 ++--- exercises/practice/hamming/hamming_test.py | 26 +++++++++++-------- 4 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/hamming/.docs/instructions.append.md diff --git a/exercises/practice/hamming/.docs/instructions.append.md b/exercises/practice/hamming/.docs/instructions.append.md new file mode 100644 index 0000000000..34d01a8939 --- /dev/null +++ b/exercises/practice/hamming/.docs/instructions.append.md @@ -0,0 +1,14 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the strands being checked are not the same length. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# When the sequences being passed are not the same length. +raise ValueError("Strands must be of equal length.") +``` diff --git a/exercises/practice/hamming/.meta/example.py b/exercises/practice/hamming/.meta/example.py index 2178da979a..29a3de356c 100644 --- a/exercises/practice/hamming/.meta/example.py +++ b/exercises/practice/hamming/.meta/example.py @@ -1,5 +1,5 @@ def distance(s1, s2): if len(s1) != len(s2): - raise ValueError("Sequences not of equal length.") + raise ValueError("Strands must be of equal length.") return sum(a != b for a, b in zip(s1, s2)) diff --git a/exercises/practice/hamming/.meta/template.j2 b/exercises/practice/hamming/.meta/template.j2 index a569f4066a..498fbc56ce 100644 --- a/exercises/practice/hamming/.meta/template.j2 +++ b/exercises/practice/hamming/.meta/template.j2 @@ -12,8 +12,10 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} def test_{{ case["description"] | to_snake }}(self): {%- if case is error_case %} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: {{- test_call(case) }} + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Strands must be of equal length.") {%- else %} self.assertEqual( {{- test_call(case) }}, @@ -21,5 +23,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): ) {%- endif %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py index 885ae0a88e..1210f0d6ba 100644 --- a/exercises/practice/hamming/hamming_test.py +++ b/exercises/practice/hamming/hamming_test.py @@ -24,25 +24,29 @@ def test_long_different_strands(self): self.assertEqual(distance("GGACGGATTCTG", "AGGACGGATTCT"), 9) def test_disallow_first_strand_longer(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: distance("AATG", "AAA") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Strands must be of equal length.") + def test_disallow_second_strand_longer(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: distance("ATA", "AGTG") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Strands must be of equal length.") + def test_disallow_left_empty_strand(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: distance("", "G") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Strands must be of equal length.") + def test_disallow_right_empty_strand(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: distance("G", "") - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Strands must be of equal length.") From 5b99eaa08d37672b1fd455bb6d352abba1251a74 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 25 Oct 2021 11:52:15 -0700 Subject: [PATCH 5/6] Regenerated go-counting test file. Again. --- exercises/practice/go-counting/go_counting_test.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exercises/practice/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py index 1e83c838da..30972ca6a2 100644 --- a/exercises/practice/go-counting/go_counting_test.py +++ b/exercises/practice/go-counting/go_counting_test.py @@ -83,3 +83,11 @@ def test_two_region_rectangular_board(self): self.assertSetEqual(territories[BLACK], {(0, 0), (2, 0)}) self.assertSetEqual(territories[WHITE], set()) self.assertSetEqual(territories[NONE], set()) + + # Utility functions + def assertRaisesWithMessage(self, exception): + return self.assertRaisesRegex(exception, r".+") + + +if __name__ == "__main__": + unittest.main() From 1c71db17c2f2f4ad41d5cbb830e084d99fe643fc Mon Sep 17 00:00:00 2001 From: Victor Goff Date: Mon, 25 Oct 2021 16:47:11 -0400 Subject: [PATCH 6/6] EOL --- exercises/practice/grains/.meta/template.j2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/grains/.meta/template.j2 b/exercises/practice/grains/.meta/template.j2 index 6b4e0bb5e3..5ea2b5b0f8 100644 --- a/exercises/practice/grains/.meta/template.j2 +++ b/exercises/practice/grains/.meta/template.j2 @@ -27,4 +27,5 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%-else%} {{test_case(case)}} {%-endif%} - {% endfor %} \ No newline at end of file + {% endfor %} + \ No newline at end of file