diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index bde876fa0..10ac0ad61 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -157,6 +157,7 @@ There are some custom tera filters in [`rust-tooling`](/rust-tooling/generate/sr Here's the hopefully up-to-date list: - `to_hex` formats ints in hexadecimal - `snake_case` massages an arbitrary string into a decent Rust identifier +- `make_test_ident` is like snake case, but prepends `test_` if the string starts with a digit Feel free to add your own in the crate `rust-tooling`. Hopefully you'll remember to update the list here as well. 🙂 diff --git a/exercises/practice/all-your-base/.meta/test_template.tera b/exercises/practice/all-your-base/.meta/test_template.tera new file mode 100644 index 000000000..829e823c7 --- /dev/null +++ b/exercises/practice/all-your-base/.meta/test_template.tera @@ -0,0 +1,26 @@ +use allyourbase as ayb; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_test_ident }}() { + let input_base = {{ test.input.inputBase }}; + let input_digits = &{{ test.input.digits | json_encode() }}; + let output_base = {{ test.input.outputBase }}; + {%- if not test.expected is object %} + let output_digits = vec!{{ test.expected | json_encode() }}; + {%- endif %} + assert_eq!( + ayb::convert(input_digits, input_base, output_base), + {%- if not test.expected is object %} + Ok(output_digits) + {%- elif test.expected.error == "input base must be >= 2" %} + Err(ayb::Error::InvalidInputBase) + {%- elif test.expected.error == "all digits must satisfy 0 <= d < input base" %} + Err(ayb::Error::InvalidDigit(2)) + {%- elif test.expected.error == "output base must be >= 2" %} + Err(ayb::Error::InvalidOutputBase) + {%- endif %} + ); +} +{% endfor -%} diff --git a/exercises/practice/all-your-base/.meta/tests.toml b/exercises/practice/all-your-base/.meta/tests.toml index c954370df..628e7d15b 100644 --- a/exercises/practice/all-your-base/.meta/tests.toml +++ b/exercises/practice/all-your-base/.meta/tests.toml @@ -1,6 +1,13 @@ -# 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. [5ce422f9-7a4b-4f44-ad29-49c67cb32d2c] description = "single bit one to decimal" @@ -23,6 +30,9 @@ description = "trinary to hexadecimal" [d3901c80-8190-41b9-bd86-38d988efa956] description = "hexadecimal to trinary" +[5d42f85e-21ad-41bd-b9be-a3e8e4258bbf] +description = "15-bit integer" + [d68788f7-66dd-43f8-a543-f15b6d233f83] description = "empty list" @@ -41,6 +51,16 @@ description = "input base is one" [e21a693a-7a69-450b-b393-27415c26a016] description = "input base is zero" +[54a23be5-d99e-41cc-88e0-a650ffe5fcc2] +description = "input base is negative" +include = false +comment = "we use unsigned integers" + +[9eccf60c-dcc9-407b-95d8-c37b8be56bb6] +description = "negative digit" +include = false +comment = "we use unsigned integers" + [232fa4a5-e761-4939-ba0c-ed046cd0676a] description = "invalid positive digit" @@ -49,3 +69,13 @@ description = "output base is one" [73dac367-da5c-4a37-95fe-c87fad0a4047] description = "output base is zero" + +[13f81f42-ff53-4e24-89d9-37603a48ebd9] +description = "output base is negative" +include = false +comment = "we use unsigned integers" + +[0e6c895d-8a5d-4868-a345-309d094cfe8d] +description = "both bases are negative" +include = false +comment = "we use unsigned integers" diff --git a/exercises/practice/all-your-base/tests/all-your-base.rs b/exercises/practice/all-your-base/tests/all-your-base.rs index 9012a40ae..75cd0d043 100644 --- a/exercises/practice/all-your-base/tests/all-your-base.rs +++ b/exercises/practice/all-your-base/tests/all-your-base.rs @@ -92,7 +92,7 @@ fn hexadecimal_to_trinary() { #[test] #[ignore] -fn fifteen_bit_integer() { +fn test_15_bit_integer() { let input_base = 97; let input_digits = &[3, 46, 60]; let output_base = 73; @@ -157,20 +157,20 @@ fn leading_zeros() { #[test] #[ignore] -fn invalid_positive_digit() { - let input_base = 2; - let input_digits = &[1, 2, 1, 0, 1, 0]; +fn input_base_is_one() { + let input_base = 1; + let input_digits = &[0]; let output_base = 10; assert_eq!( ayb::convert(input_digits, input_base, output_base), - Err(ayb::Error::InvalidDigit(2)) + Err(ayb::Error::InvalidInputBase) ); } #[test] #[ignore] -fn input_base_is_one() { - let input_base = 1; +fn input_base_is_zero() { + let input_base = 0; let input_digits = &[]; let output_base = 10; assert_eq!( @@ -181,25 +181,25 @@ fn input_base_is_one() { #[test] #[ignore] -fn output_base_is_one() { +fn invalid_positive_digit() { let input_base = 2; - let input_digits = &[1, 0, 1, 0, 1, 0]; - let output_base = 1; + let input_digits = &[1, 2, 1, 0, 1, 0]; + let output_base = 10; assert_eq!( ayb::convert(input_digits, input_base, output_base), - Err(ayb::Error::InvalidOutputBase) + Err(ayb::Error::InvalidDigit(2)) ); } #[test] #[ignore] -fn input_base_is_zero() { - let input_base = 0; - let input_digits = &[]; - let output_base = 10; +fn output_base_is_one() { + let input_base = 2; + let input_digits = &[1, 0, 1, 0, 1, 0]; + let output_base = 1; assert_eq!( ayb::convert(input_digits, input_base, output_base), - Err(ayb::Error::InvalidInputBase) + Err(ayb::Error::InvalidOutputBase) ); } diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs index 23e17154a..c1a416354 100644 --- a/rust-tooling/generate/src/custom_filters.rs +++ b/rust-tooling/generate/src/custom_filters.rs @@ -4,7 +4,11 @@ use tera::{Result, Value}; type Filter = fn(&Value, &HashMap) -> Result; -pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[("to_hex", to_hex), ("snake_case", snake_case)]; +pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[ + ("to_hex", to_hex), + ("snake_case", snake_case), + ("make_test_ident", make_test_ident), +]; pub fn to_hex(value: &Value, _args: &HashMap) -> Result { let Some(value) = value.as_u64() else { @@ -28,3 +32,18 @@ pub fn snake_case(value: &Value, _args: &HashMap) -> Result) -> Result { + let value = snake_case(value, _args)?; + let Some(value) = value.as_str() else { + return Err(tera::Error::call_filter( + "make_test_ident filter expects a string", + "serde_json::value::Value::as_str", + )); + }; + if !value.chars().next().unwrap_or_default().is_alphabetic() { + // identifiers cannot start with digits etc. + return Ok(Value::String(format!("test_{value}"))); + } + Ok(Value::String(value.into())) +}