diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 92f5d3df6..484e28a04 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -152,6 +152,10 @@ Feel free to add your own in the crate `rust-tooling`. Custom filters added there will be available to all templates. How to create such custom filters is documented int he [tera docs][tera-docs-filters]. +For a rather complicated example, check out the test template of `triangle`. +It organizes the test cases in modules and dynamically detects which tests to put behind feature gates. +That exercise also reimplements some test cases from upstream in `additional-tests.json`, in order to add more information to them necessary for generating good tests. + [tera-docs]: https://keats.github.io/tera/docs/#templates [word-count-tmpl]: /exercises/practice/word-count/.meta/test_template.tera [var-len-q-tmpl]: /exercises/practice/variable-length-quantity/.meta/test_template.tera diff --git a/exercises/practice/triangle/.meta/additional-tests.json b/exercises/practice/triangle/.meta/additional-tests.json new file mode 100644 index 000000000..b797996a3 --- /dev/null +++ b/exercises/practice/triangle/.meta/additional-tests.json @@ -0,0 +1,52 @@ +[ + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "all zero sides is not a triangle", + "comments": ["reimplements 16e8ceb0-eadb-46d1-b892-c50327479251"], + "property": "invalid", + "input": { + "sides": [0, 0, 0] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "first triangle inequality violation", + "comments": ["reimplements 2eba0cfb-6c65-4c40-8146-30b608905eae"], + "property": "invalid", + "input": { + "sides": [1, 1, 3] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "second triangle inequality violation", + "comments": ["reimplements 278469cb-ac6b-41f0-81d4-66d9b828f8ac"], + "property": "invalid", + "input": { + "sides": [1, 3, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "third triangle inequality violation", + "comments": ["reimplements 90efb0c7-72bb-4514-b320-3a3892e278ff"], + "property": "invalid", + "input": { + "sides": [3, 1, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "may not violate triangle inequality", + "comments": ["reimplements 70ad5154-0033-48b7-af2c-b8d739cd9fdc"], + "property": "invalid", + "input": { + "sides": [7, 3, 2] + }, + "expected": false + } +] \ No newline at end of file diff --git a/exercises/practice/triangle/.meta/example.rs b/exercises/practice/triangle/.meta/example.rs index 213cfb788..39f85574a 100644 --- a/exercises/practice/triangle/.meta/example.rs +++ b/exercises/practice/triangle/.meta/example.rs @@ -37,7 +37,7 @@ where } pub fn is_isosceles(&self) -> bool { - self.count_distinct_pairs() == 2 + self.count_distinct_pairs() <= 2 } pub fn is_scalene(&self) -> bool { diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera new file mode 100644 index 000000000..ed7c03a9f --- /dev/null +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -0,0 +1,85 @@ +mod equilateral { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "equilateral" %}{% continue %}{% endif %} + +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +{% if test.description is containing("float") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_equilateral()); + {% else %} + assert!(!output.is_equilateral()); + {% endif -%} +} +{% endfor -%} +} + +mod isosceles { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "isosceles" %}{% continue %}{% endif %} + +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_isosceles()); + {% else %} + assert!(!output.is_isosceles()); + {% endif -%} +} +{% endfor -%} +} + +mod scalene { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "scalene" %}{% continue %}{% endif %} + +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_scalene()); + {% else %} + assert!(!output.is_scalene()); + {% endif -%} +} +{% endfor -%} +} + +mod invalid { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "invalid" %}{% continue %}{% endif %} + +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input); + assert!(output.is_none()); +} +{% endfor -%} +} diff --git a/exercises/practice/triangle/.meta/tests.toml b/exercises/practice/triangle/.meta/tests.toml index be690e975..2590d6ff4 100644 --- a/exercises/practice/triangle/.meta/tests.toml +++ b/exercises/practice/triangle/.meta/tests.toml @@ -1,3 +1,108 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[8b2c43ac-7257-43f9-b552-7631a91988af] +description = "equilateral triangle -> all sides are equal" + +[33eb6f87-0498-4ccf-9573-7f8c3ce92b7b] +description = "equilateral triangle -> any side is unequal" + +[c6585b7d-a8c0-4ad8-8a34-e21d36f7ad87] +description = "equilateral triangle -> no sides are equal" + +[16e8ceb0-eadb-46d1-b892-c50327479251] +description = "equilateral triangle -> all zero sides is not a triangle" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[3022f537-b8e5-4cc1-8f12-fd775827a00c] +description = "equilateral triangle -> sides may be floats" + +[cbc612dc-d75a-4c1c-87fc-e2d5edd70b71] +description = "isosceles triangle -> last two sides are equal" + +[e388ce93-f25e-4daf-b977-4b7ede992217] +description = "isosceles triangle -> first two sides are equal" + +[d2080b79-4523-4c3f-9d42-2da6e81ab30f] +description = "isosceles triangle -> first and last sides are equal" + +[8d71e185-2bd7-4841-b7e1-71689a5491d8] +description = "isosceles triangle -> equilateral triangles are also isosceles" + +[840ed5f8-366f-43c5-ac69-8f05e6f10bbb] +description = "isosceles triangle -> no sides are equal" + +[2eba0cfb-6c65-4c40-8146-30b608905eae] +description = "isosceles triangle -> first triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[278469cb-ac6b-41f0-81d4-66d9b828f8ac] +description = "isosceles triangle -> second triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[90efb0c7-72bb-4514-b320-3a3892e278ff] +description = "isosceles triangle -> third triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[adb4ee20-532f-43dc-8d31-e9271b7ef2bc] +description = "isosceles triangle -> sides may be floats" + +[e8b5f09c-ec2e-47c1-abec-f35095733afb] +description = "scalene triangle -> no sides are equal" + +[2510001f-b44d-4d18-9872-2303e7977dc1] +description = "scalene triangle -> all sides are equal" + +[c6e15a92-90d9-4fb3-90a2-eef64f8d3e1e] +description = "scalene triangle -> first and second sides are equal" + +[3da23a91-a166-419a-9abf-baf4868fd985] +description = "scalene triangle -> first and third sides are equal" + +[b6a75d98-1fef-4c42-8e9a-9db854ba0a4d] +description = "scalene triangle -> second and third sides are equal" + +[70ad5154-0033-48b7-af2c-b8d739cd9fdc] +description = "scalene triangle -> may not violate triangle inequality" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[26d9d59d-f8f1-40d3-ad58-ae4d54123d7d] +description = "scalene triangle -> sides may be floats" diff --git a/exercises/practice/triangle/tests/triangle.rs b/exercises/practice/triangle/tests/triangle.rs index c93221df4..4992a3df9 100644 --- a/exercises/practice/triangle/tests/triangle.rs +++ b/exercises/practice/triangle/tests/triangle.rs @@ -1,204 +1,185 @@ -use triangle::*; - -#[test] -fn positive_length_sides_are_ok() { - let sides = [2, 2, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_some()); -} - -#[test] -#[ignore] -fn zero_length_sides_are_illegal() { - let sides = [0, 0, 0]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_first() { - let sides = [0, 2, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_second() { - let sides = [2, 0, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_third() { - let sides = [2, 2, 0]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn equilateral_triangles_have_equal_sides() { - let sides = [2, 2, 2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn larger_equilateral_triangles_have_equal_sides() { - let sides = [10, 10, 10]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_one() { - let sides = [3, 4, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_two() { - let sides = [4, 4, 3]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_three() { - let sides = [4, 3, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_four() { - let sides = [4, 7, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_one() { - let sides = [3, 4, 5]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_two() { - let sides = [5, 4, 6]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_three() { - let sides = [10, 11, 12]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_four() { - let sides = [5, 4, 2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_one() { - let sides = [7, 3, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_two() { - let sides = [1, 1, 3]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn scalene_triangle_with_floating_point_sides() { - let sides = [0.4, 0.6, 0.3]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn equilateral_triangles_with_floating_point_sides() { - let sides = [0.2, 0.2, 0.2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn isosceles_triangle_with_floating_point_sides() { - let sides = [0.3, 0.4, 0.4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn invalid_triangle_with_floating_point_sides_one() { - let sides = [0.0, 0.4, 0.3]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn invalid_triangle_with_floating_point_sides_two() { - let sides = [0.1, 0.3, 0.5]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); +mod equilateral { + use triangle::Triangle; + + #[test] + fn all_sides_are_equal() { + let input = [2, 2, 2]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_equilateral()); + } + + #[test] + #[ignore] + fn any_side_is_unequal() { + let input = [2, 3, 2]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_equilateral()); + } + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [5, 4, 6]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_equilateral()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.5, 0.5]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_equilateral()); + } +} + +mod isosceles { + use triangle::Triangle; + + #[test] + #[ignore] + fn last_two_sides_are_equal() { + let input = [3, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn first_two_sides_are_equal() { + let input = [4, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn first_and_last_sides_are_equal() { + let input = [4, 3, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn equilateral_triangles_are_also_isosceles() { + let input = [4, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [2, 3, 4]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_isosceles()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.4, 0.5]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } +} + +mod scalene { + use triangle::Triangle; + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [5, 4, 6]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_scalene()); + } + + #[test] + #[ignore] + fn all_sides_are_equal() { + let input = [4, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn first_and_second_sides_are_equal() { + let input = [4, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn first_and_third_sides_are_equal() { + let input = [3, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn second_and_third_sides_are_equal() { + let input = [4, 3, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.4, 0.6]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_scalene()); + } +} + +mod invalid { + use triangle::Triangle; + + #[test] + #[ignore] + fn all_zero_sides_is_not_a_triangle() { + let input = [0, 0, 0]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn first_triangle_inequality_violation() { + let input = [1, 1, 3]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn second_triangle_inequality_violation() { + let input = [1, 3, 1]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn third_triangle_inequality_violation() { + let input = [3, 1, 1]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn may_not_violate_triangle_inequality() { + let input = [7, 3, 2]; + let output = Triangle::build(input); + assert!(output.is_none()); + } } diff --git a/problem-specifications b/problem-specifications index 988eafc34..57f1387b3 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 988eafc342c42defd78d1fc87b80d8586bc01ac4 +Subproject commit 57f1387b313ae9c94ab4a43f9586e85af67aa870