diff --git a/bin/add-practice-exercise.ps1 b/bin/add-practice-exercise.ps1 index 1e5637b367..b44b9a11ad 100644 --- a/bin/add-practice-exercise.ps1 +++ b/bin/add-practice-exercise.ps1 @@ -38,25 +38,28 @@ $project = "${exerciseDir}/${ExerciseName}.csproj" # Update project packages & dotnet remove $project package coverlet.collector & dotnet add $project package Exercism.Tests --version 0.1.0-beta1 -& dotnet add $project package xunit.runner.visualstudio --version 2.4.3 -& dotnet add $project package xunit --version 2.4.1 -& dotnet add $project package Microsoft.NET.Test.Sdk --version 16.8.3 +& dotnet add $project package xunit.runner.visualstudio --version 3.0.1 +& dotnet add $project package xunit --version 2.8.1 +& dotnet add $project package Microsoft.NET.Test.Sdk --version 17.12.0 # Remove and update files Remove-Item -Path "${exerciseDir}/UnitTest1.cs" (Get-Content -Path ".editorconfig") -Replace "\[\*\.cs\]", "[${exerciseName}.cs]" | Set-Content -Path "${exerciseDir}/.editorconfig" # Add and run generator (this will update the tests file) -$generator = "generators/Exercises/Generators/${ExerciseName}.cs" +$generator = "${exerciseDir}/.meta/Generator.tpl" Add-Content -Path $generator -Value @" -using System; +using Xunit; -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class ${exerciseName} : ExerciseGenerator +public class ${exerciseName}Tests { + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + // TODO: implement the test + } + {{/test_cases}} } "@ & dotnet run --project generators --exercise $Exercise diff --git a/bin/generate-tests.ps1 b/bin/generate-tests.ps1 new file mode 100644 index 0000000000..e4afb4240b --- /dev/null +++ b/bin/generate-tests.ps1 @@ -0,0 +1,30 @@ +<# +.SYNOPSIS + Generate the tests for exercises +.DESCRIPTION + Generate the tests for exercises that have a template. + The tests are generated from canonical data. +.PARAMETER Exercise + The slug of the exercise to generate the tests for (optional). +.EXAMPLE + The example below will generate the tests for exercises with a template + PS C:\> ./test.ps1 +.EXAMPLE + The example below will generate the tests for the specified exercise + PS C:\> ./test.ps1 acronym +#> + +[CmdletBinding(SupportsShouldProcess)] +param ( + [Parameter(Position = 0, Mandatory = $false)] + [string]$Exercise +) + +$ErrorActionPreference = "Stop" +$PSNativeCommandUseErrorActionPreference = $true + +if ($Exercise) { + dotnet run --project generators --exercise $Exercise +} else { + dotnet run --project generators +} diff --git a/bin/test.ps1 b/bin/test.ps1 index 3aa3651ed2..83ce8ba1c4 100644 --- a/bin/test.ps1 +++ b/bin/test.ps1 @@ -130,6 +130,7 @@ function Parse-Exercises { function Build-Generators { Write-Output "Build generators" & dotnet build generators + & dotnet build generators.deprecated } function Test-Refactoring-Exercise-Default-Implementations { diff --git a/docs/GENERATORS.md b/docs/GENERATORS.md index 84d3880450..f1c07dc22a 100644 --- a/docs/GENERATORS.md +++ b/docs/GENERATORS.md @@ -1,262 +1,54 @@ # Test generators -Test generators allow tracks to generate tests automatically without having to write them ourselves. Each test generator reads from the exercise's `canonical data`, which defines the name of the test, its inputs, and outputs. You can read more about exercism's approach to test suites [here](https://github.com/exercism/problem-specifications#test-data-canonical-datajson). +The C# track uses a [test generator](https://exercism.org/docs/building/tooling/test-generators) to auto-generate practice exercise tests. +It uses the fact that most exercises defined in the [problem-specifications repo](https://github.com/exercism/problem-specifications/) also have a `canonical-data.json` file, which contains standardized test inputs and outputs that can be used to implement the exercise. -Generating tests automatically removes any sort of user error when creating tests. Furthermore, we want the tests to be accurate with respect to its canonical data. Test generation also makes it much easier to keep tests up to date. As the canonical data changes, the tests will be automatically updated when the generator for that test is run. +## Steps -An example of a canonical data file can be found [here](https://github.com/exercism/problem-specifications/blob/master/exercises/bob/canonical-data.json) +To generate a practice exercise's tests, the test generator: -## Common terms +1. Reads the exercise's test cases from its `canonical-data.json` file +2. Uses `tests.toml` file to omit and excluded test cases +3. Renders the test cases using the exercise's generator template +4. Format the rendered template using Roslyn +5. Writes the formatted template to the exercise's test file -When looking through the canonical data and the generator code base, we use a lot of common terminology. This list hopefully clarifies what they represent. +### Step 1: read `canonical-data.json` file -- Canonical Data - Represents the entire test suite. -- Canonical Data Case - A representation of a single test case. -- Description - The name of the test. -- Property - The method to be called when running the test. -- Input - The input for the test case. -- Expected - The expected value when running the test case. +The test generator parses the test cases from the exercise's `canonical-data.json` using the [JSON.net library](https://www.newtonsoft.com/json). -## Adding a simple generator +Since some canonical data uses nesting, the parsed test case includes an additional `path` field that contains the `description` properties of any parent elements, as well as the test case's own `description` property. -Adding a test generator is straightforward. Simply add a new file to the `Exercises/Generators` folder with the name of the exercise (in PascalCase), and create a class that extends the `GeneratorExercise` class. +Note: the JSON is parsed to an `ExpandoObject` instance, which makes dealing with dynamic data easier. -An example of a simple generator would be the Bob exercise. The source is displayed below, but you can freely view it in the repository [here](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Bob.cs). +### Step 2: omit excluded tests from `tests.toml` file -```csharp -namespace Exercism.CSharp.Exercises.Generators -{ - public class Bob : GeneratorExercise - { - } -} -``` +Each exercise has a `tests.toml` file, in which individual tests can be excluded/disabled. +The test generator will remove any test cases that are marked as excluded (`include = false`). -This is a fully working generator, no other code needs to be written! However, it's simplicity stems from the fact that the test suite and the program itself are relatively trivial. +### Step 3: render the test cases -## Adding a complex generator +The (potentially transformed) test cases are then passed to the `.meta/Generator.tpl` file, which defines how the tests should be rendered based on those test cases. -When the generator's default output is not sufficient, you can override the `GeneratorExercise` class' virtual methods to override the default behavior. +### Step 4: format the rendered template using Roslyn -### Method 1: UpdateTestMethod(TestMethod testMethod) +The rendered template is then formatted using [Roslyn](https://github.com/dotnet/roslyn). +This has the following benefits: -Update the test method that described the test method being generated. When you are required to customize a test generator, overriding this method is virtually always what you want to do. +- Exercises are formatted consistently +- You're not required to worry much about whitespace and alignment when writing templates -There are many things that can be customized, of which we'll list the more common usages. +### Step 5: write the rendered template to the exercise's test file -#### Customize test data +Finally, the output of the rendered template is written to the exercise's test file. -It is not uncommon that a generator has to transform its input data or expected value to a different value/representation. +## Templates -An example of this is the [matching-brackets](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/MatchingBrackets.cs) generator, which has a `"value"` input value, which is of type `string`. However, this `string` value contains a backslash, which needs to escaped in order for it to be rendered correctly: +The templates are rendered using the [Handlebars.Net library](https://github.com/Handlebars-Net/Handlebars.Net), which supports [handlebars syntax](https://handlebarsjs.com/). -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - testMethod.Input["value"] = testMethod.Input["value"].Replace("\\", "\\\\"); - // [...] -} -``` +## Command-line interface -Another common use case is to handle empty arrays. If an array is empty, its type will default to `JArray`, which doesn't have any type information. To allow the generator to output a correctly typed array, we have to convert the `JArray` to an array first. +There are two ways in which the test generator can be run: -An example of this is the [proverb](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Proverb.cs) generator, which converts the `JArray` to an empty `string` array: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - // [...] - - if (testMethod.Input["strings"] is JArray) - testMethod.Input["strings"] = Array.Empty(); - - if (testMethod.Expected is JArray) - testMethod.Expected = Array.Empty(); -} -``` - -#### Output test data as variables - -Sometimes, it might make sense to not define a test method's data inline, but as variables. - -An example of this is the [crypto-square](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/CryptoSquare.cs) generator, which indicates that both the test method input as well as the expected value, should be stored in variables: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - testMethod.UseVariablesForInput = true; - testMethod.UseVariableForExpected = true; -} -``` - -#### Custom tested method type - -By default, the generator will test a static method. However, you can also test for instance methods, extension methods, properties and constructors. - -An example of this is the [roman-numerals](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/RomanNumerals.cs) generator, which indicates that it tests an extensions method: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - testMethod.TestedMethodType = TestedMethodType.ExtensionMethod; - testMethod.TestedMethod = "ToRoman"; -} -``` - -#### Change names used - -As we saw in the previous example, you can also customize the name of the tested method. You are also allowed to customize the tested class' name and the test method name. - -An example of this is the [triangle](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Triangle.cs) generator, which by default generates duplicate test method names (which will be a compile-time error), but instead uses the `TestMethodNameWithPath` to use the full path as the test method name (effectively making the test method name unique): - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - // [...] - testMethod.TestMethodName = testMethod.TestMethodNameWithPath; - // [...] -} -``` - -#### Test for an exception being thrown - -Some test methods want to verify that an exception is being thrown. - -An example of this is the [rna-transcription](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/RnaTranscription.cs) generator, which defines that some of its test methods should throw an `ArgumentException`: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - if (testMethod.Expected is null) - testMethod.ExceptionThrown = typeof(ArgumentException); -} -``` - -Note that `ArgumentException` type's namespace will be automatically added to the list of namespaces used in the test class. - -#### Custom input/constructor parameters - -In some cases, you might want to override the parameters that are used as input parameters. - -An example of this is the [two-fer](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/TwoFer.cs) generator, which does not use any input parameters when the `"name"` input parameter is set to `null`: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - // [...] - - if (testMethod.Input["name"] is null) - testMethod.InputParameters = Array.Empty(); -} -``` - -If a test method tests an instance method, you can also specify which parameters to use as constructor parameters (the others will be input parameters, unless specified otherwise). - -An example of this is the [matrix](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Matrix.cs) generator, which specifies that the `"string"` parameter should be passed as a constructor parameter: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - testMethod.TestedMethodType = TestedMethodType.InstanceMethod; - testMethod.ConstructorInputParameters = new[] { "string" }; -} -``` - -#### Custom arrange/act/assert code - -Although this should be used as a last resort, some generators might want to skip the default generation completely and control which arrange, act or assert code the test method should contain. - -An example of this is the [run-length-encoding](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/RunLengthEncoding.cs) generator, which uses a custom assertion for one specific property: - -```csharp -protected override void UpdateTestMethod(TestMethod testMethod) -{ - // [...] - - if (testMethod.Property == "consistency") - testMethod.Assert = RenderConsistencyToAssert(testMethod); -} - -private string RenderConsistencyToAssert(TestMethod testMethod) -{ - var expected = Render.Object(testMethod.Expected); - var actual = $"{testMethod.TestedClass}.Decode({testMethod.TestedClass}.Encode({expected}))"; - return Render.AssertEqual(expected, actual); -} -``` - -Note that the `Render` instance is used to render the assertion and the expected value. - -### Method 2: UpdateNamespaces(ISet namespaces) - -Allows additional namespaces to be added to the test suite. - -All tests use the `Xunit` framework, so each test class will automatically include the `Xunit` namespace. However, some test classes may require additional namespaces. - -An example of this is the [gigasecond](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Gigasecond.cs) generator, which uses the `DateTime` class in its test methods, and thus adds its namespace to the list of namespaces: - -```csharp -protected override void UpdateNamespaces(ISet namespaces) -{ - namespaces.Add(typeof(DateTime).Namespace); -} -``` - -Note that as mentioned before, the namespace of any thrown exception types are automatically added to the list of namespaces. - -### Method 3: UpdateTestClass(TestClass testClass) - -This method allows you to customize the output of the test class. Only in rare cases would you want to override this method. The most common use case to override this method, is to add additional (helper) methods to the test suite. - -An example of this is the [tournament](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Tournament.cs) generator, which adds a helper method to the test suite: - -```csharp -protected override void UpdateTestClass(TestClass testClass) -{ - AddRunTallyMethod(testClass); -} - -private static void AddRunTallyMethod(TestClass testClass) -{ - testClass.AdditionalMethods.Add(@" -private string RunTally(string input) -{ - var encoding = new UTF8Encoding(); - - using (var inStream = new MemoryStream(encoding.GetBytes(input))) - using (var outStream = new MemoryStream()) - { - Tournament.Tally(inStream, outStream); - return encoding.GetString(outStream.ToArray()); - } -}"); -} -``` - -Additional methods will be added to the bottom of the test suite. - -## Updating Existing Files - -It is possible that an existing exercise does not match the canonical data. It is OK to update the exercise stub and/or the exercise example to follow the canonical data! An example might be that an exercise is named SumOfMultiples, but the SumOfMultiples.cs and Example.cs files both use `Multiples` as the name of the class. - -Also, if you find an issue with one of the existing generators or test suites simply open up the generator that you would like to update, make your changes, and then run the generators. - -## Running The Generators - -This repository is coded against [.NET Core](https://www.microsoft.com/net/core). To run the generators all you need to do is run the following command in the generators directory: - -`dotnet run` - -This command will take all of the generators that are in the `Exercises` folder, and generate all of the test cases for that exercise. We use reflection to get all of the exercises, so if you are adding a new test, the test will be automatically included when running the generator. - -If you only need to run a single generator, you can do so by running the following command: - -`dotnet run -e ` - -Once the generator has been run, you can view the output of your generation by navigating to the test file for that exercise. As an example, the test suite for the Bob exercise can be found at: - -`exercises/bob/BobTests.cs` - -## Submitting A Generator - -If you are satisfied with the output of your generator, we would love for you to submit a pull request! Please include your generator, updated test suite, and any other corresponding files that you may have changed. +1. `bin/generate-tests.ps1`: generate the tests for all exercises that have a generator template +2. `bin/generate-tests.ps1 -e `: generate the tests for the specified exercise, if it has a generator template diff --git a/docs/GENERATORS_DEPRECATED.md b/docs/GENERATORS_DEPRECATED.md new file mode 100644 index 0000000000..e8881935c6 --- /dev/null +++ b/docs/GENERATORS_DEPRECATED.md @@ -0,0 +1,262 @@ +# Test generators (deprecated) + +Test generators allow tracks to generate tests automatically without having to write them ourselves. Each test generator reads from the exercise's `canonical data`, which defines the name of the test, its inputs, and outputs. You can read more about exercism's approach to test suites [here](https://github.com/exercism/problem-specifications#test-data-canonical-datajson). + +Generating tests automatically removes any sort of user error when creating tests. Furthermore, we want the tests to be accurate with respect to its canonical data. Test generation also makes it much easier to keep tests up to date. As the canonical data changes, the tests will be automatically updated when the generator for that test is run. + +An example of a canonical data file can be found [here](https://github.com/exercism/problem-specifications/blob/master/exercises/bob/canonical-data.json) + +## Common terms + +When looking through the canonical data and the generator code base, we use a lot of common terminology. This list hopefully clarifies what they represent. + +- Canonical Data - Represents the entire test suite. +- Canonical Data Case - A representation of a single test case. +- Description - The name of the test. +- Property - The method to be called when running the test. +- Input - The input for the test case. +- Expected - The expected value when running the test case. + +## Adding a simple generator + +Adding a test generator is straightforward. Simply add a new file to the `Exercises/Generators` folder with the name of the exercise (in PascalCase), and create a class that extends the `GeneratorExercise` class. + +An example of a simple generator would be the Bob exercise. The source is displayed below, but you can freely view it in the repository [here](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Bob.cs). + +```csharp +namespace Exercism.CSharp.Exercises.Generators +{ + public class Bob : GeneratorExercise + { + } +} +``` + +This is a fully working generator, no other code needs to be written! However, it's simplicity stems from the fact that the test suite and the program itself are relatively trivial. + +## Adding a complex generator + +When the generator's default output is not sufficient, you can override the `GeneratorExercise` class' virtual methods to override the default behavior. + +### Method 1: UpdateTestMethod(TestMethod testMethod) + +Update the test method that described the test method being generated. When you are required to customize a test generator, overriding this method is virtually always what you want to do. + +There are many things that can be customized, of which we'll list the more common usages. + +#### Customize test data + +It is not uncommon that a generator has to transform its input data or expected value to a different value/representation. + +An example of this is the [matching-brackets](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/MatchingBrackets.cs) generator, which has a `"value"` input value, which is of type `string`. However, this `string` value contains a backslash, which needs to escaped in order for it to be rendered correctly: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + testMethod.Input["value"] = testMethod.Input["value"].Replace("\\", "\\\\"); + // [...] +} +``` + +Another common use case is to handle empty arrays. If an array is empty, its type will default to `JArray`, which doesn't have any type information. To allow the generator to output a correctly typed array, we have to convert the `JArray` to an array first. + +An example of this is the [proverb](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Proverb.cs) generator, which converts the `JArray` to an empty `string` array: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + // [...] + + if (testMethod.Input["strings"] is JArray) + testMethod.Input["strings"] = Array.Empty(); + + if (testMethod.Expected is JArray) + testMethod.Expected = Array.Empty(); +} +``` + +#### Output test data as variables + +Sometimes, it might make sense to not define a test method's data inline, but as variables. + +An example of this is the [crypto-square](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/CryptoSquare.cs) generator, which indicates that both the test method input as well as the expected value, should be stored in variables: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + testMethod.UseVariablesForInput = true; + testMethod.UseVariableForExpected = true; +} +``` + +#### Custom tested method type + +By default, the generator will test a static method. However, you can also test for instance methods, extension methods, properties and constructors. + +An example of this is the [roman-numerals](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/RomanNumerals.cs) generator, which indicates that it tests an extensions method: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + testMethod.TestedMethodType = TestedMethodType.ExtensionMethod; + testMethod.TestedMethod = "ToRoman"; +} +``` + +#### Change names used + +As we saw in the previous example, you can also customize the name of the tested method. You are also allowed to customize the tested class' name and the test method name. + +An example of this is the [triangle](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Triangle.cs) generator, which by default generates duplicate test method names (which will be a compile-time error), but instead uses the `TestMethodNameWithPath` to use the full path as the test method name (effectively making the test method name unique): + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + // [...] + testMethod.TestMethodName = testMethod.TestMethodNameWithPath; + // [...] +} +``` + +#### Test for an exception being thrown + +Some test methods want to verify that an exception is being thrown. + +An example of this is the [rna-transcription](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/RnaTranscription.cs) generator, which defines that some of its test methods should throw an `ArgumentException`: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + if (testMethod.Expected is null) + testMethod.ExceptionThrown = typeof(ArgumentException); +} +``` + +Note that `ArgumentException` type's namespace will be automatically added to the list of namespaces used in the test class. + +#### Custom input/constructor parameters + +In some cases, you might want to override the parameters that are used as input parameters. + +An example of this is the [two-fer](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/TwoFer.cs) generator, which does not use any input parameters when the `"name"` input parameter is set to `null`: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + // [...] + + if (testMethod.Input["name"] is null) + testMethod.InputParameters = Array.Empty(); +} +``` + +If a test method tests an instance method, you can also specify which parameters to use as constructor parameters (the others will be input parameters, unless specified otherwise). + +An example of this is the [matrix](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Matrix.cs) generator, which specifies that the `"string"` parameter should be passed as a constructor parameter: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + testMethod.TestedMethodType = TestedMethodType.InstanceMethod; + testMethod.ConstructorInputParameters = new[] { "string" }; +} +``` + +#### Custom arrange/act/assert code + +Although this should be used as a last resort, some generators might want to skip the default generation completely and control which arrange, act or assert code the test method should contain. + +An example of this is the [run-length-encoding](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/RunLengthEncoding.cs) generator, which uses a custom assertion for one specific property: + +```csharp +protected override void UpdateTestMethod(TestMethod testMethod) +{ + // [...] + + if (testMethod.Property == "consistency") + testMethod.Assert = RenderConsistencyToAssert(testMethod); +} + +private string RenderConsistencyToAssert(TestMethod testMethod) +{ + var expected = Render.Object(testMethod.Expected); + var actual = $"{testMethod.TestedClass}.Decode({testMethod.TestedClass}.Encode({expected}))"; + return Render.AssertEqual(expected, actual); +} +``` + +Note that the `Render` instance is used to render the assertion and the expected value. + +### Method 2: UpdateNamespaces(ISet namespaces) + +Allows additional namespaces to be added to the test suite. + +All tests use the `Xunit` framework, so each test class will automatically include the `Xunit` namespace. However, some test classes may require additional namespaces. + +An example of this is the [gigasecond](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Gigasecond.cs) generator, which uses the `DateTime` class in its test methods, and thus adds its namespace to the list of namespaces: + +```csharp +protected override void UpdateNamespaces(ISet namespaces) +{ + namespaces.Add(typeof(DateTime).Namespace); +} +``` + +Note that as mentioned before, the namespace of any thrown exception types are automatically added to the list of namespaces. + +### Method 3: UpdateTestClass(TestClass testClass) + +This method allows you to customize the output of the test class. Only in rare cases would you want to override this method. The most common use case to override this method, is to add additional (helper) methods to the test suite. + +An example of this is the [tournament](https://github.com/exercism/csharp/blob/main/generators/Exercises/Generators/Tournament.cs) generator, which adds a helper method to the test suite: + +```csharp +protected override void UpdateTestClass(TestClass testClass) +{ + AddRunTallyMethod(testClass); +} + +private static void AddRunTallyMethod(TestClass testClass) +{ + testClass.AdditionalMethods.Add(@" +private string RunTally(string input) +{ + var encoding = new UTF8Encoding(); + + using (var inStream = new MemoryStream(encoding.GetBytes(input))) + using (var outStream = new MemoryStream()) + { + Tournament.Tally(inStream, outStream); + return encoding.GetString(outStream.ToArray()); + } +}"); +} +``` + +Additional methods will be added to the bottom of the test suite. + +## Updating Existing Files + +It is possible that an existing exercise does not match the canonical data. It is OK to update the exercise stub and/or the exercise example to follow the canonical data! An example might be that an exercise is named SumOfMultiples, but the SumOfMultiples.cs and Example.cs files both use `Multiples` as the name of the class. + +Also, if you find an issue with one of the existing generators or test suites simply open up the generator that you would like to update, make your changes, and then run the generators. + +## Running The Generators + +This repository is coded against [.NET Core](https://www.microsoft.com/net/core). To run the generators all you need to do is run the following command in the generators directory: + +`dotnet run` + +This command will take all of the generators that are in the `Exercises` folder, and generate all of the test cases for that exercise. We use reflection to get all of the exercises, so if you are adding a new test, the test will be automatically included when running the generator. + +If you only need to run a single generator, you can do so by running the following command: + +`dotnet run -e ` + +Once the generator has been run, you can view the output of your generation by navigating to the test file for that exercise. As an example, the test suite for the Bob exercise can be found at: + +`exercises/bob/BobTests.cs` + +## Submitting A Generator + +If you are satisfied with the output of your generator, we would love for you to submit a pull request! Please include your generator, updated test suite, and any other corresponding files that you may have changed. diff --git a/exercises/concept/roll-the-die/RollTheDie.csproj b/exercises/concept/roll-the-die/RollTheDie.csproj index 08d0ab890b..72065bdf20 100644 --- a/exercises/concept/roll-the-die/RollTheDie.csproj +++ b/exercises/concept/roll-the-die/RollTheDie.csproj @@ -7,7 +7,6 @@ - diff --git a/exercises/practice/acronym/.meta/Generator.tpl b/exercises/practice/acronym/.meta/Generator.tpl new file mode 100644 index 0000000000..730b28e883 --- /dev/null +++ b/exercises/practice/acronym/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class AcronymTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal({{lit expected}}, Acronym.Abbreviate({{lit input.phrase}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/difference-of-squares/.meta/Generator.tpl b/exercises/practice/difference-of-squares/.meta/Generator.tpl new file mode 100644 index 0000000000..db36cd7582 --- /dev/null +++ b/exercises/practice/difference-of-squares/.meta/Generator.tpl @@ -0,0 +1,28 @@ +using Xunit; + +public class DifferenceOfSquaresTests +{ + {{#test_cases_by_property.squareOfSum}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{short_test_method_name}}() + { + Assert.Equal({{expected}}, DifferenceOfSquares.CalculateSquareOfSum({{input.number}})); + } + {{/test_cases_by_property.squareOfSum}} + + {{#test_cases_by_property.sumOfSquares}} + [Fact(Skip = "Remove this Skip property to run this test")] + public void {{short_test_method_name}}() + { + Assert.Equal({{expected}}, DifferenceOfSquares.CalculateSumOfSquares({{input.number}})); + } + {{/test_cases_by_property.sumOfSquares}} + + {{#test_cases_by_property.differenceOfSquares}} + [Fact(Skip = "Remove this Skip property to run this test")] + public void {{short_test_method_name}}() + { + Assert.Equal({{expected}}, DifferenceOfSquares.CalculateDifferenceOfSquares({{input.number}})); + } + {{/test_cases_by_property.differenceOfSquares}} +} diff --git a/exercises/practice/eliuds-eggs/.meta/Generator.tpl b/exercises/practice/eliuds-eggs/.meta/Generator.tpl new file mode 100644 index 0000000000..9e91728bd4 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class EliudsEggsTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal({{expected}}, EliudsEggs.EggCount({{input.number}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/eliuds-eggs/EliudsEggsTests.cs b/exercises/practice/eliuds-eggs/EliudsEggsTests.cs index c171134ba3..fa13627443 100644 --- a/exercises/practice/eliuds-eggs/EliudsEggsTests.cs +++ b/exercises/practice/eliuds-eggs/EliudsEggsTests.cs @@ -3,25 +3,25 @@ public class EliudsEggsTests { [Fact] - public void Number_0_eggs() + public void Zero_eggs() { Assert.Equal(0, EliudsEggs.EggCount(0)); } [Fact(Skip = "Remove this Skip property to run this test")] - public void Number_1_egg() + public void One_egg() { Assert.Equal(1, EliudsEggs.EggCount(16)); } [Fact(Skip = "Remove this Skip property to run this test")] - public void Number_4_eggs() + public void Four_eggs() { Assert.Equal(4, EliudsEggs.EggCount(89)); } [Fact(Skip = "Remove this Skip property to run this test")] - public void Number_13_eggs() + public void Thirteen_eggs() { Assert.Equal(13, EliudsEggs.EggCount(2000000000)); } diff --git a/exercises/practice/hamming/.meta/Generator.tpl b/exercises/practice/hamming/.meta/Generator.tpl new file mode 100644 index 0000000000..1d9ea9c19e --- /dev/null +++ b/exercises/practice/hamming/.meta/Generator.tpl @@ -0,0 +1,17 @@ +using System; +using Xunit; + +public class HammingTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + {{#if expected.error}} + Assert.Throws(() => Hamming.Distance({{lit input.strand1}}, {{lit input.strand2}})); + {{else}} + Assert.Equal({{expected}}, Hamming.Distance({{lit input.strand1}}, {{lit input.strand2}})); + {{/if}} + } + {{/test_cases}} +} diff --git a/exercises/practice/isogram/.meta/Generator.tpl b/exercises/practice/isogram/.meta/Generator.tpl new file mode 100644 index 0000000000..f34414ce4d --- /dev/null +++ b/exercises/practice/isogram/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class IsogramTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.{{expected}}(Isogram.IsIsogram({{lit input.phrase}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/leap/.meta/Generator.tpl b/exercises/practice/leap/.meta/Generator.tpl new file mode 100644 index 0000000000..7eedba9f38 --- /dev/null +++ b/exercises/practice/leap/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class LeapTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.{{expected}}(Leap.IsLeapYear({{input.year}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/pangram/.meta/Generator.tpl b/exercises/practice/pangram/.meta/Generator.tpl new file mode 100644 index 0000000000..8e28209cd6 --- /dev/null +++ b/exercises/practice/pangram/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class PangramTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.{{lit expected}}(Pangram.IsPangram({{lit input.sentence}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/pangram/PangramTests.cs b/exercises/practice/pangram/PangramTests.cs index ecd8d387fa..5d27c3017c 100644 --- a/exercises/practice/pangram/PangramTests.cs +++ b/exercises/practice/pangram/PangramTests.cs @@ -57,7 +57,7 @@ public void Mixed_case_and_punctuation() } [Fact(Skip = "Remove this Skip property to run this test")] - public void A_m_and_a_m_are_26_different_characters_but_not_a_pangram() + public void Am_and_am_are_26_different_characters_but_not_a_pangram() { Assert.False(Pangram.IsPangram("abcdefghijklm ABCDEFGHIJKLM")); } diff --git a/exercises/practice/perfect-numbers/.meta/Generator.tpl b/exercises/practice/perfect-numbers/.meta/Generator.tpl new file mode 100644 index 0000000000..88523e2d7a --- /dev/null +++ b/exercises/practice/perfect-numbers/.meta/Generator.tpl @@ -0,0 +1,17 @@ +using System; +using Xunit; + +public class PerfectNumbersTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{short_test_method_name}}() + { + {{#if expected.error}} + Assert.Throws(() => PerfectNumbers.Classify({{input.number}})); + {{else}} + Assert.Equal(Classification.{{Capitalize expected}}, PerfectNumbers.Classify({{input.number}})); + {{/if}} + } + {{/test_cases}} +} diff --git a/exercises/practice/perfect-numbers/PerfectNumbersTests.cs b/exercises/practice/perfect-numbers/PerfectNumbersTests.cs index 3a1f9c6136..38fed1b016 100644 --- a/exercises/practice/perfect-numbers/PerfectNumbersTests.cs +++ b/exercises/practice/perfect-numbers/PerfectNumbersTests.cs @@ -70,13 +70,13 @@ public void Edge_case_no_factors_other_than_itself_is_classified_correctly() } [Fact(Skip = "Remove this Skip property to run this test")] - public void Zero_is_rejected_as_it_is_not_a_positive_integer_() + public void Zero_is_rejected_as_it_is_not_a_positive_integer() { Assert.Throws(() => PerfectNumbers.Classify(0)); } [Fact(Skip = "Remove this Skip property to run this test")] - public void Negative_integer_is_rejected_as_it_is_not_a_positive_integer_() + public void Negative_integer_is_rejected_as_it_is_not_a_positive_integer() { Assert.Throws(() => PerfectNumbers.Classify(-1)); } diff --git a/exercises/practice/rotational-cipher/.meta/Generator.tpl b/exercises/practice/rotational-cipher/.meta/Generator.tpl new file mode 100644 index 0000000000..acef16e3f4 --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class RotationalCipherTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal({{lit expected}}, RotationalCipher.Rotate({{lit input.text}}, {{input.shiftKey}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/sieve/.meta/Generator.tpl b/exercises/practice/sieve/.meta/Generator.tpl new file mode 100644 index 0000000000..b25b8ce508 --- /dev/null +++ b/exercises/practice/sieve/.meta/Generator.tpl @@ -0,0 +1,13 @@ +using Xunit; + +public class SieveTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + int[] expected = [{{expected}}]; + Assert.Equal(expected, Sieve.Primes({{input.limit}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/sieve/SieveTests.cs b/exercises/practice/sieve/SieveTests.cs index 6634a5eeea..34e4df13b1 100644 --- a/exercises/practice/sieve/SieveTests.cs +++ b/exercises/practice/sieve/SieveTests.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Xunit; public class SieveTests @@ -7,36 +5,35 @@ public class SieveTests [Fact] public void No_primes_under_two() { - Assert.Empty(Sieve.Primes(1).ToArray()); + int[] expected = []; + Assert.Equal(expected, Sieve.Primes(1)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Find_first_prime() { - Assert.Equal(new[] { 2 }, Sieve.Primes(2).ToArray()); + int[] expected = [2]; + Assert.Equal(expected, Sieve.Primes(2)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Find_primes_up_to_10() { - Assert.Equal(new[] { 2, 3, 5, 7 }, Sieve.Primes(10).ToArray()); + int[] expected = [2, 3, 5, 7]; + Assert.Equal(expected, Sieve.Primes(10)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Limit_is_prime() { - Assert.Equal(new[] { 2, 3, 5, 7, 11, 13 }, Sieve.Primes(13).ToArray()); + int[] expected = [2, 3, 5, 7, 11, 13]; + Assert.Equal(expected, Sieve.Primes(13)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Find_primes_up_to_1000() { - Assert.Equal(new[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997 }, Sieve.Primes(1000).ToArray()); - } - - [Fact(Skip = "Remove this Skip property to run this test")] - public void No_negative_numbers() - { - Assert.Throws(() => Sieve.Primes(-1).ToArray()); + int[] expected = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]; + Assert.Equal(expected, Sieve.Primes(1000)); } } diff --git a/exercises/practice/space-age/.meta/Generator.tpl b/exercises/practice/space-age/.meta/Generator.tpl new file mode 100644 index 0000000000..b39b167170 --- /dev/null +++ b/exercises/practice/space-age/.meta/Generator.tpl @@ -0,0 +1,13 @@ +using Xunit; + +public class SpaceAgeTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + var sut = new SpaceAge({{input.seconds}}); + Assert.Equal({{lit expected}}, sut.On{{input.planet}}(), precision: 2); + } + {{/test_cases}} +} diff --git a/exercises/practice/square-root/.meta/Generator.tpl b/exercises/practice/square-root/.meta/Generator.tpl new file mode 100644 index 0000000000..6f416ef87d --- /dev/null +++ b/exercises/practice/square-root/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class SquareRootTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal({{expected}}, SquareRoot.Root({{input.radicand}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/square-root/SquareRootTests.cs b/exercises/practice/square-root/SquareRootTests.cs index d90814402d..c6ed398c7f 100644 --- a/exercises/practice/square-root/SquareRootTests.cs +++ b/exercises/practice/square-root/SquareRootTests.cs @@ -37,4 +37,4 @@ public void Root_of_65025() { Assert.Equal(255, SquareRoot.Root(65025)); } -} \ No newline at end of file +} diff --git a/exercises/practice/sum-of-multiples/.meta/Generator.tpl b/exercises/practice/sum-of-multiples/.meta/Generator.tpl new file mode 100644 index 0000000000..92b0f20744 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class SumOfMultiplesTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal({{expected}}, SumOfMultiples.Sum([{{input.factors}}], {{input.limit}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/sum-of-multiples/SumOfMultiplesTests.cs b/exercises/practice/sum-of-multiples/SumOfMultiplesTests.cs index 8e4044bc2f..646839a42f 100644 --- a/exercises/practice/sum-of-multiples/SumOfMultiplesTests.cs +++ b/exercises/practice/sum-of-multiples/SumOfMultiplesTests.cs @@ -1,4 +1,3 @@ -using System; using Xunit; public class SumOfMultiplesTests @@ -6,96 +5,96 @@ public class SumOfMultiplesTests [Fact] public void No_multiples_within_limit() { - Assert.Equal(0, SumOfMultiples.Sum(new[] { 3, 5 }, 1)); + Assert.Equal(0, SumOfMultiples.Sum([3, 5], 1)); } [Fact(Skip = "Remove this Skip property to run this test")] public void One_factor_has_multiples_within_limit() { - Assert.Equal(3, SumOfMultiples.Sum(new[] { 3, 5 }, 4)); + Assert.Equal(3, SumOfMultiples.Sum([3, 5], 4)); } [Fact(Skip = "Remove this Skip property to run this test")] public void More_than_one_multiple_within_limit() { - Assert.Equal(9, SumOfMultiples.Sum(new[] { 3 }, 7)); + Assert.Equal(9, SumOfMultiples.Sum([3], 7)); } [Fact(Skip = "Remove this Skip property to run this test")] public void More_than_one_factor_with_multiples_within_limit() { - Assert.Equal(23, SumOfMultiples.Sum(new[] { 3, 5 }, 10)); + Assert.Equal(23, SumOfMultiples.Sum([3, 5], 10)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Each_multiple_is_only_counted_once() { - Assert.Equal(2318, SumOfMultiples.Sum(new[] { 3, 5 }, 100)); + Assert.Equal(2318, SumOfMultiples.Sum([3, 5], 100)); } [Fact(Skip = "Remove this Skip property to run this test")] public void A_much_larger_limit() { - Assert.Equal(233168, SumOfMultiples.Sum(new[] { 3, 5 }, 1000)); + Assert.Equal(233168, SumOfMultiples.Sum([3, 5], 1000)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Three_factors() { - Assert.Equal(51, SumOfMultiples.Sum(new[] { 7, 13, 17 }, 20)); + Assert.Equal(51, SumOfMultiples.Sum([7, 13, 17], 20)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Factors_not_relatively_prime() { - Assert.Equal(30, SumOfMultiples.Sum(new[] { 4, 6 }, 15)); + Assert.Equal(30, SumOfMultiples.Sum([4, 6], 15)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Some_pairs_of_factors_relatively_prime_and_some_not() { - Assert.Equal(4419, SumOfMultiples.Sum(new[] { 5, 6, 8 }, 150)); + Assert.Equal(4419, SumOfMultiples.Sum([5, 6, 8], 150)); } [Fact(Skip = "Remove this Skip property to run this test")] public void One_factor_is_a_multiple_of_another() { - Assert.Equal(275, SumOfMultiples.Sum(new[] { 5, 25 }, 51)); + Assert.Equal(275, SumOfMultiples.Sum([5, 25], 51)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Much_larger_factors() { - Assert.Equal(2203160, SumOfMultiples.Sum(new[] { 43, 47 }, 10000)); + Assert.Equal(2203160, SumOfMultiples.Sum([43, 47], 10000)); } [Fact(Skip = "Remove this Skip property to run this test")] public void All_numbers_are_multiples_of_1() { - Assert.Equal(4950, SumOfMultiples.Sum(new[] { 1 }, 100)); + Assert.Equal(4950, SumOfMultiples.Sum([1], 100)); } [Fact(Skip = "Remove this Skip property to run this test")] public void No_factors_means_an_empty_sum() { - Assert.Equal(0, SumOfMultiples.Sum(Array.Empty(), 10000)); + Assert.Equal(0, SumOfMultiples.Sum([], 10000)); } [Fact(Skip = "Remove this Skip property to run this test")] public void The_only_multiple_of_0_is_0() { - Assert.Equal(0, SumOfMultiples.Sum(new[] { 0 }, 1)); + Assert.Equal(0, SumOfMultiples.Sum([0], 1)); } [Fact(Skip = "Remove this Skip property to run this test")] public void The_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { - Assert.Equal(3, SumOfMultiples.Sum(new[] { 3, 0 }, 4)); + Assert.Equal(3, SumOfMultiples.Sum([3, 0], 4)); } [Fact(Skip = "Remove this Skip property to run this test")] public void Solutions_using_include_exclude_must_extend_to_cardinality_greater_than_3() { - Assert.Equal(39614537, SumOfMultiples.Sum(new[] { 2, 3, 5, 7, 11 }, 10000)); + Assert.Equal(39614537, SumOfMultiples.Sum([2, 3, 5, 7, 11], 10000)); } } diff --git a/exercises/practice/two-fer/.meta/Generator.tpl b/exercises/practice/two-fer/.meta/Generator.tpl new file mode 100644 index 0000000000..0eaa2c5150 --- /dev/null +++ b/exercises/practice/two-fer/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class TwoFerTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal({{lit expected}}, TwoFer.Speak({{#if input.name}}{{lit input.name}}{{/if}})); + } + {{/test_cases}} +} diff --git a/exercises/practice/wordy/.meta/Generator.tpl b/exercises/practice/wordy/.meta/Generator.tpl new file mode 100644 index 0000000000..dc097d805c --- /dev/null +++ b/exercises/practice/wordy/.meta/Generator.tpl @@ -0,0 +1,17 @@ +using System; +using Xunit; + +public class WordyTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + {{#if expected.error}} + Assert.Throws(() => Wordy.Answer({{lit input.question}})); + {{else}} + Assert.Equal({{expected}}, Wordy.Answer({{lit input.question}})); + {{/if}} + } + {{/test_cases}} +} diff --git a/exercises/practice/zebra-puzzle/.meta/Generator.tpl b/exercises/practice/zebra-puzzle/.meta/Generator.tpl new file mode 100644 index 0000000000..f2154670b8 --- /dev/null +++ b/exercises/practice/zebra-puzzle/.meta/Generator.tpl @@ -0,0 +1,12 @@ +using Xunit; + +public class ZebraPuzzleTests +{ + {{#test_cases}} + [Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}] + public void {{test_method_name}}() + { + Assert.Equal(Nationality.{{expected}}, ZebraPuzzle.{{Capitalize property}}()); + } + {{/test_cases}} +} diff --git a/generators/Exercises/ExerciseGenerator.cs b/generators.deprecated/Exercises/ExerciseGenerator.cs similarity index 100% rename from generators/Exercises/ExerciseGenerator.cs rename to generators.deprecated/Exercises/ExerciseGenerator.cs diff --git a/generators/Exercises/GeneratorRunner.cs b/generators.deprecated/Exercises/GeneratorRunner.cs similarity index 100% rename from generators/Exercises/GeneratorRunner.cs rename to generators.deprecated/Exercises/GeneratorRunner.cs diff --git a/generators/Exercises/Generators/AffineCipher.cs b/generators.deprecated/Exercises/Generators/AffineCipher.cs similarity index 100% rename from generators/Exercises/Generators/AffineCipher.cs rename to generators.deprecated/Exercises/Generators/AffineCipher.cs diff --git a/generators/Exercises/Generators/AllYourBase.cs b/generators.deprecated/Exercises/Generators/AllYourBase.cs similarity index 100% rename from generators/Exercises/Generators/AllYourBase.cs rename to generators.deprecated/Exercises/Generators/AllYourBase.cs diff --git a/generators/Exercises/Generators/Allergies.cs b/generators.deprecated/Exercises/Generators/Allergies.cs similarity index 100% rename from generators/Exercises/Generators/Allergies.cs rename to generators.deprecated/Exercises/Generators/Allergies.cs diff --git a/generators/Exercises/Generators/Alphametics.cs b/generators.deprecated/Exercises/Generators/Alphametics.cs similarity index 100% rename from generators/Exercises/Generators/Alphametics.cs rename to generators.deprecated/Exercises/Generators/Alphametics.cs diff --git a/generators/Exercises/Generators/Anagram.cs b/generators.deprecated/Exercises/Generators/Anagram.cs similarity index 100% rename from generators/Exercises/Generators/Anagram.cs rename to generators.deprecated/Exercises/Generators/Anagram.cs diff --git a/generators/Exercises/Generators/ArmstrongNumbers.cs b/generators.deprecated/Exercises/Generators/ArmstrongNumbers.cs similarity index 100% rename from generators/Exercises/Generators/ArmstrongNumbers.cs rename to generators.deprecated/Exercises/Generators/ArmstrongNumbers.cs diff --git a/generators/Exercises/Generators/AtbashCipher.cs b/generators.deprecated/Exercises/Generators/AtbashCipher.cs similarity index 100% rename from generators/Exercises/Generators/AtbashCipher.cs rename to generators.deprecated/Exercises/Generators/AtbashCipher.cs diff --git a/generators/Exercises/Generators/BeerSong.cs b/generators.deprecated/Exercises/Generators/BeerSong.cs similarity index 100% rename from generators/Exercises/Generators/BeerSong.cs rename to generators.deprecated/Exercises/Generators/BeerSong.cs diff --git a/generators/Exercises/Generators/BinarySearch.cs b/generators.deprecated/Exercises/Generators/BinarySearch.cs similarity index 100% rename from generators/Exercises/Generators/BinarySearch.cs rename to generators.deprecated/Exercises/Generators/BinarySearch.cs diff --git a/generators/Exercises/Generators/BinarySearchTree.cs b/generators.deprecated/Exercises/Generators/BinarySearchTree.cs similarity index 100% rename from generators/Exercises/Generators/BinarySearchTree.cs rename to generators.deprecated/Exercises/Generators/BinarySearchTree.cs diff --git a/generators/Exercises/Generators/Bob.cs b/generators.deprecated/Exercises/Generators/Bob.cs similarity index 100% rename from generators/Exercises/Generators/Bob.cs rename to generators.deprecated/Exercises/Generators/Bob.cs diff --git a/generators/Exercises/Generators/BookStore.cs b/generators.deprecated/Exercises/Generators/BookStore.cs similarity index 100% rename from generators/Exercises/Generators/BookStore.cs rename to generators.deprecated/Exercises/Generators/BookStore.cs diff --git a/generators/Exercises/Generators/Bowling.cs b/generators.deprecated/Exercises/Generators/Bowling.cs similarity index 100% rename from generators/Exercises/Generators/Bowling.cs rename to generators.deprecated/Exercises/Generators/Bowling.cs diff --git a/generators/Exercises/Generators/Change.cs b/generators.deprecated/Exercises/Generators/Change.cs similarity index 100% rename from generators/Exercises/Generators/Change.cs rename to generators.deprecated/Exercises/Generators/Change.cs diff --git a/generators/Exercises/Generators/CircularBuffer.cs b/generators.deprecated/Exercises/Generators/CircularBuffer.cs similarity index 100% rename from generators/Exercises/Generators/CircularBuffer.cs rename to generators.deprecated/Exercises/Generators/CircularBuffer.cs diff --git a/generators/Exercises/Generators/Clock.cs b/generators.deprecated/Exercises/Generators/Clock.cs similarity index 100% rename from generators/Exercises/Generators/Clock.cs rename to generators.deprecated/Exercises/Generators/Clock.cs diff --git a/generators/Exercises/Generators/CollatzConjecture.cs b/generators.deprecated/Exercises/Generators/CollatzConjecture.cs similarity index 100% rename from generators/Exercises/Generators/CollatzConjecture.cs rename to generators.deprecated/Exercises/Generators/CollatzConjecture.cs diff --git a/generators/Exercises/Generators/ComplexNumbers.cs b/generators.deprecated/Exercises/Generators/ComplexNumbers.cs similarity index 100% rename from generators/Exercises/Generators/ComplexNumbers.cs rename to generators.deprecated/Exercises/Generators/ComplexNumbers.cs diff --git a/generators/Exercises/Generators/Connect.cs b/generators.deprecated/Exercises/Generators/Connect.cs similarity index 100% rename from generators/Exercises/Generators/Connect.cs rename to generators.deprecated/Exercises/Generators/Connect.cs diff --git a/generators/Exercises/Generators/CryptoSquare.cs b/generators.deprecated/Exercises/Generators/CryptoSquare.cs similarity index 100% rename from generators/Exercises/Generators/CryptoSquare.cs rename to generators.deprecated/Exercises/Generators/CryptoSquare.cs diff --git a/generators/Exercises/Generators/CustomSet.cs b/generators.deprecated/Exercises/Generators/CustomSet.cs similarity index 100% rename from generators/Exercises/Generators/CustomSet.cs rename to generators.deprecated/Exercises/Generators/CustomSet.cs diff --git a/generators/Exercises/Generators/Darts.cs b/generators.deprecated/Exercises/Generators/Darts.cs similarity index 100% rename from generators/Exercises/Generators/Darts.cs rename to generators.deprecated/Exercises/Generators/Darts.cs diff --git a/generators/Exercises/Generators/DiffieHellman.cs b/generators.deprecated/Exercises/Generators/DiffieHellman.cs similarity index 100% rename from generators/Exercises/Generators/DiffieHellman.cs rename to generators.deprecated/Exercises/Generators/DiffieHellman.cs diff --git a/generators/Exercises/Generators/DndCharacter.cs b/generators.deprecated/Exercises/Generators/DndCharacter.cs similarity index 100% rename from generators/Exercises/Generators/DndCharacter.cs rename to generators.deprecated/Exercises/Generators/DndCharacter.cs diff --git a/generators/Exercises/Generators/Dominoes.cs b/generators.deprecated/Exercises/Generators/Dominoes.cs similarity index 100% rename from generators/Exercises/Generators/Dominoes.cs rename to generators.deprecated/Exercises/Generators/Dominoes.cs diff --git a/generators/Exercises/Generators/Etl.cs b/generators.deprecated/Exercises/Generators/Etl.cs similarity index 100% rename from generators/Exercises/Generators/Etl.cs rename to generators.deprecated/Exercises/Generators/Etl.cs diff --git a/generators/Exercises/Generators/FlattenArray.cs b/generators.deprecated/Exercises/Generators/FlattenArray.cs similarity index 100% rename from generators/Exercises/Generators/FlattenArray.cs rename to generators.deprecated/Exercises/Generators/FlattenArray.cs diff --git a/generators/Exercises/Generators/FoodChain.cs b/generators.deprecated/Exercises/Generators/FoodChain.cs similarity index 100% rename from generators/Exercises/Generators/FoodChain.cs rename to generators.deprecated/Exercises/Generators/FoodChain.cs diff --git a/generators/Exercises/Generators/Forth.cs b/generators.deprecated/Exercises/Generators/Forth.cs similarity index 100% rename from generators/Exercises/Generators/Forth.cs rename to generators.deprecated/Exercises/Generators/Forth.cs diff --git a/generators/Exercises/Generators/GameOfLife.cs b/generators.deprecated/Exercises/Generators/GameOfLife.cs similarity index 100% rename from generators/Exercises/Generators/GameOfLife.cs rename to generators.deprecated/Exercises/Generators/GameOfLife.cs diff --git a/generators/Exercises/Generators/Gigasecond.cs b/generators.deprecated/Exercises/Generators/Gigasecond.cs similarity index 100% rename from generators/Exercises/Generators/Gigasecond.cs rename to generators.deprecated/Exercises/Generators/Gigasecond.cs diff --git a/generators/Exercises/Generators/GoCounting.cs b/generators.deprecated/Exercises/Generators/GoCounting.cs similarity index 100% rename from generators/Exercises/Generators/GoCounting.cs rename to generators.deprecated/Exercises/Generators/GoCounting.cs diff --git a/generators/Exercises/Generators/GradeSchool.cs b/generators.deprecated/Exercises/Generators/GradeSchool.cs similarity index 100% rename from generators/Exercises/Generators/GradeSchool.cs rename to generators.deprecated/Exercises/Generators/GradeSchool.cs diff --git a/generators/Exercises/Generators/Grains.cs b/generators.deprecated/Exercises/Generators/Grains.cs similarity index 100% rename from generators/Exercises/Generators/Grains.cs rename to generators.deprecated/Exercises/Generators/Grains.cs diff --git a/generators/Exercises/Generators/Grep.cs b/generators.deprecated/Exercises/Generators/Grep.cs similarity index 100% rename from generators/Exercises/Generators/Grep.cs rename to generators.deprecated/Exercises/Generators/Grep.cs diff --git a/generators/Exercises/Generators/HelloWorld.cs b/generators.deprecated/Exercises/Generators/HelloWorld.cs similarity index 100% rename from generators/Exercises/Generators/HelloWorld.cs rename to generators.deprecated/Exercises/Generators/HelloWorld.cs diff --git a/generators/Exercises/Generators/HighScores.cs b/generators.deprecated/Exercises/Generators/HighScores.cs similarity index 100% rename from generators/Exercises/Generators/HighScores.cs rename to generators.deprecated/Exercises/Generators/HighScores.cs diff --git a/generators/Exercises/Generators/House.cs b/generators.deprecated/Exercises/Generators/House.cs similarity index 100% rename from generators/Exercises/Generators/House.cs rename to generators.deprecated/Exercises/Generators/House.cs diff --git a/generators/Exercises/Generators/IsbnVerifier.cs b/generators.deprecated/Exercises/Generators/IsbnVerifier.cs similarity index 100% rename from generators/Exercises/Generators/IsbnVerifier.cs rename to generators.deprecated/Exercises/Generators/IsbnVerifier.cs diff --git a/generators/Exercises/Generators/KindergartenGarden.cs b/generators.deprecated/Exercises/Generators/KindergartenGarden.cs similarity index 100% rename from generators/Exercises/Generators/KindergartenGarden.cs rename to generators.deprecated/Exercises/Generators/KindergartenGarden.cs diff --git a/generators/Exercises/Generators/Knapsack.cs b/generators.deprecated/Exercises/Generators/Knapsack.cs similarity index 100% rename from generators/Exercises/Generators/Knapsack.cs rename to generators.deprecated/Exercises/Generators/Knapsack.cs diff --git a/generators/Exercises/Generators/LargestSeriesProduct.cs b/generators.deprecated/Exercises/Generators/LargestSeriesProduct.cs similarity index 100% rename from generators/Exercises/Generators/LargestSeriesProduct.cs rename to generators.deprecated/Exercises/Generators/LargestSeriesProduct.cs diff --git a/generators/Exercises/Generators/ListOps.cs b/generators.deprecated/Exercises/Generators/ListOps.cs similarity index 100% rename from generators/Exercises/Generators/ListOps.cs rename to generators.deprecated/Exercises/Generators/ListOps.cs diff --git a/generators/Exercises/Generators/Luhn.cs b/generators.deprecated/Exercises/Generators/Luhn.cs similarity index 100% rename from generators/Exercises/Generators/Luhn.cs rename to generators.deprecated/Exercises/Generators/Luhn.cs diff --git a/generators/Exercises/Generators/Markdown.cs b/generators.deprecated/Exercises/Generators/Markdown.cs similarity index 100% rename from generators/Exercises/Generators/Markdown.cs rename to generators.deprecated/Exercises/Generators/Markdown.cs diff --git a/generators/Exercises/Generators/MatchingBrackets.cs b/generators.deprecated/Exercises/Generators/MatchingBrackets.cs similarity index 100% rename from generators/Exercises/Generators/MatchingBrackets.cs rename to generators.deprecated/Exercises/Generators/MatchingBrackets.cs diff --git a/generators/Exercises/Generators/Matrix.cs b/generators.deprecated/Exercises/Generators/Matrix.cs similarity index 100% rename from generators/Exercises/Generators/Matrix.cs rename to generators.deprecated/Exercises/Generators/Matrix.cs diff --git a/generators/Exercises/Generators/Meetup.cs b/generators.deprecated/Exercises/Generators/Meetup.cs similarity index 100% rename from generators/Exercises/Generators/Meetup.cs rename to generators.deprecated/Exercises/Generators/Meetup.cs diff --git a/generators/Exercises/Generators/Minesweeper.cs b/generators.deprecated/Exercises/Generators/Minesweeper.cs similarity index 100% rename from generators/Exercises/Generators/Minesweeper.cs rename to generators.deprecated/Exercises/Generators/Minesweeper.cs diff --git a/generators/Exercises/Generators/NthPrime.cs b/generators.deprecated/Exercises/Generators/NthPrime.cs similarity index 100% rename from generators/Exercises/Generators/NthPrime.cs rename to generators.deprecated/Exercises/Generators/NthPrime.cs diff --git a/generators/Exercises/Generators/NucleotideCount.cs b/generators.deprecated/Exercises/Generators/NucleotideCount.cs similarity index 100% rename from generators/Exercises/Generators/NucleotideCount.cs rename to generators.deprecated/Exercises/Generators/NucleotideCount.cs diff --git a/generators/Exercises/Generators/OcrNumbers.cs b/generators.deprecated/Exercises/Generators/OcrNumbers.cs similarity index 100% rename from generators/Exercises/Generators/OcrNumbers.cs rename to generators.deprecated/Exercises/Generators/OcrNumbers.cs diff --git a/generators/Exercises/Generators/PalindromeProducts.cs b/generators.deprecated/Exercises/Generators/PalindromeProducts.cs similarity index 100% rename from generators/Exercises/Generators/PalindromeProducts.cs rename to generators.deprecated/Exercises/Generators/PalindromeProducts.cs diff --git a/generators/Exercises/Generators/PascalsTriangle.cs b/generators.deprecated/Exercises/Generators/PascalsTriangle.cs similarity index 100% rename from generators/Exercises/Generators/PascalsTriangle.cs rename to generators.deprecated/Exercises/Generators/PascalsTriangle.cs diff --git a/generators/Exercises/Generators/PhoneNumber.cs b/generators.deprecated/Exercises/Generators/PhoneNumber.cs similarity index 100% rename from generators/Exercises/Generators/PhoneNumber.cs rename to generators.deprecated/Exercises/Generators/PhoneNumber.cs diff --git a/generators/Exercises/Generators/PigLatin.cs b/generators.deprecated/Exercises/Generators/PigLatin.cs similarity index 100% rename from generators/Exercises/Generators/PigLatin.cs rename to generators.deprecated/Exercises/Generators/PigLatin.cs diff --git a/generators/Exercises/Generators/Poker.cs b/generators.deprecated/Exercises/Generators/Poker.cs similarity index 100% rename from generators/Exercises/Generators/Poker.cs rename to generators.deprecated/Exercises/Generators/Poker.cs diff --git a/generators/Exercises/Generators/Pov.cs b/generators.deprecated/Exercises/Generators/Pov.cs similarity index 100% rename from generators/Exercises/Generators/Pov.cs rename to generators.deprecated/Exercises/Generators/Pov.cs diff --git a/generators/Exercises/Generators/PrimeFactors.cs b/generators.deprecated/Exercises/Generators/PrimeFactors.cs similarity index 100% rename from generators/Exercises/Generators/PrimeFactors.cs rename to generators.deprecated/Exercises/Generators/PrimeFactors.cs diff --git a/generators/Exercises/Generators/ProteinTranslation.cs b/generators.deprecated/Exercises/Generators/ProteinTranslation.cs similarity index 100% rename from generators/Exercises/Generators/ProteinTranslation.cs rename to generators.deprecated/Exercises/Generators/ProteinTranslation.cs diff --git a/generators/Exercises/Generators/Proverb.cs b/generators.deprecated/Exercises/Generators/Proverb.cs similarity index 100% rename from generators/Exercises/Generators/Proverb.cs rename to generators.deprecated/Exercises/Generators/Proverb.cs diff --git a/generators/Exercises/Generators/PythagoreanTriplet.cs b/generators.deprecated/Exercises/Generators/PythagoreanTriplet.cs similarity index 100% rename from generators/Exercises/Generators/PythagoreanTriplet.cs rename to generators.deprecated/Exercises/Generators/PythagoreanTriplet.cs diff --git a/generators/Exercises/Generators/QueenAttack.cs b/generators.deprecated/Exercises/Generators/QueenAttack.cs similarity index 100% rename from generators/Exercises/Generators/QueenAttack.cs rename to generators.deprecated/Exercises/Generators/QueenAttack.cs diff --git a/generators/Exercises/Generators/RailFenceCipher.cs b/generators.deprecated/Exercises/Generators/RailFenceCipher.cs similarity index 100% rename from generators/Exercises/Generators/RailFenceCipher.cs rename to generators.deprecated/Exercises/Generators/RailFenceCipher.cs diff --git a/generators/Exercises/Generators/Raindrops.cs b/generators.deprecated/Exercises/Generators/Raindrops.cs similarity index 100% rename from generators/Exercises/Generators/Raindrops.cs rename to generators.deprecated/Exercises/Generators/Raindrops.cs diff --git a/generators/Exercises/Generators/RationalNumbers.cs b/generators.deprecated/Exercises/Generators/RationalNumbers.cs similarity index 100% rename from generators/Exercises/Generators/RationalNumbers.cs rename to generators.deprecated/Exercises/Generators/RationalNumbers.cs diff --git a/generators/Exercises/Generators/React.cs b/generators.deprecated/Exercises/Generators/React.cs similarity index 100% rename from generators/Exercises/Generators/React.cs rename to generators.deprecated/Exercises/Generators/React.cs diff --git a/generators/Exercises/Generators/Rectangles.cs b/generators.deprecated/Exercises/Generators/Rectangles.cs similarity index 100% rename from generators/Exercises/Generators/Rectangles.cs rename to generators.deprecated/Exercises/Generators/Rectangles.cs diff --git a/generators/Exercises/Generators/ResistorColor.cs b/generators.deprecated/Exercises/Generators/ResistorColor.cs similarity index 100% rename from generators/Exercises/Generators/ResistorColor.cs rename to generators.deprecated/Exercises/Generators/ResistorColor.cs diff --git a/generators/Exercises/Generators/ResistorColorDuo.cs b/generators.deprecated/Exercises/Generators/ResistorColorDuo.cs similarity index 100% rename from generators/Exercises/Generators/ResistorColorDuo.cs rename to generators.deprecated/Exercises/Generators/ResistorColorDuo.cs diff --git a/generators/Exercises/Generators/ResistorColorTrio.cs b/generators.deprecated/Exercises/Generators/ResistorColorTrio.cs similarity index 100% rename from generators/Exercises/Generators/ResistorColorTrio.cs rename to generators.deprecated/Exercises/Generators/ResistorColorTrio.cs diff --git a/generators/Exercises/Generators/RestApi.cs b/generators.deprecated/Exercises/Generators/RestApi.cs similarity index 100% rename from generators/Exercises/Generators/RestApi.cs rename to generators.deprecated/Exercises/Generators/RestApi.cs diff --git a/generators/Exercises/Generators/ReverseString.cs b/generators.deprecated/Exercises/Generators/ReverseString.cs similarity index 100% rename from generators/Exercises/Generators/ReverseString.cs rename to generators.deprecated/Exercises/Generators/ReverseString.cs diff --git a/generators/Exercises/Generators/RnaTranscription.cs b/generators.deprecated/Exercises/Generators/RnaTranscription.cs similarity index 100% rename from generators/Exercises/Generators/RnaTranscription.cs rename to generators.deprecated/Exercises/Generators/RnaTranscription.cs diff --git a/generators/Exercises/Generators/RobotSimulator.cs b/generators.deprecated/Exercises/Generators/RobotSimulator.cs similarity index 100% rename from generators/Exercises/Generators/RobotSimulator.cs rename to generators.deprecated/Exercises/Generators/RobotSimulator.cs diff --git a/generators/Exercises/Generators/RomanNumerals.cs b/generators.deprecated/Exercises/Generators/RomanNumerals.cs similarity index 100% rename from generators/Exercises/Generators/RomanNumerals.cs rename to generators.deprecated/Exercises/Generators/RomanNumerals.cs diff --git a/generators/Exercises/Generators/RunLengthEncoding.cs b/generators.deprecated/Exercises/Generators/RunLengthEncoding.cs similarity index 100% rename from generators/Exercises/Generators/RunLengthEncoding.cs rename to generators.deprecated/Exercises/Generators/RunLengthEncoding.cs diff --git a/generators/Exercises/Generators/SaddlePoints.cs b/generators.deprecated/Exercises/Generators/SaddlePoints.cs similarity index 100% rename from generators/Exercises/Generators/SaddlePoints.cs rename to generators.deprecated/Exercises/Generators/SaddlePoints.cs diff --git a/generators/Exercises/Generators/Say.cs b/generators.deprecated/Exercises/Generators/Say.cs similarity index 100% rename from generators/Exercises/Generators/Say.cs rename to generators.deprecated/Exercises/Generators/Say.cs diff --git a/generators/Exercises/Generators/ScaleGenerator.cs b/generators.deprecated/Exercises/Generators/ScaleGenerator.cs similarity index 100% rename from generators/Exercises/Generators/ScaleGenerator.cs rename to generators.deprecated/Exercises/Generators/ScaleGenerator.cs diff --git a/generators/Exercises/Generators/ScrabbleScore.cs b/generators.deprecated/Exercises/Generators/ScrabbleScore.cs similarity index 100% rename from generators/Exercises/Generators/ScrabbleScore.cs rename to generators.deprecated/Exercises/Generators/ScrabbleScore.cs diff --git a/generators/Exercises/Generators/SecretHandshake.cs b/generators.deprecated/Exercises/Generators/SecretHandshake.cs similarity index 100% rename from generators/Exercises/Generators/SecretHandshake.cs rename to generators.deprecated/Exercises/Generators/SecretHandshake.cs diff --git a/generators/Exercises/Generators/Series.cs b/generators.deprecated/Exercises/Generators/Series.cs similarity index 100% rename from generators/Exercises/Generators/Series.cs rename to generators.deprecated/Exercises/Generators/Series.cs diff --git a/generators/Exercises/Generators/SgfParsing.cs b/generators.deprecated/Exercises/Generators/SgfParsing.cs similarity index 100% rename from generators/Exercises/Generators/SgfParsing.cs rename to generators.deprecated/Exercises/Generators/SgfParsing.cs diff --git a/generators/Exercises/Generators/SimpleCipher.cs b/generators.deprecated/Exercises/Generators/SimpleCipher.cs similarity index 100% rename from generators/Exercises/Generators/SimpleCipher.cs rename to generators.deprecated/Exercises/Generators/SimpleCipher.cs diff --git a/generators/Exercises/Generators/SpiralMatrix.cs b/generators.deprecated/Exercises/Generators/SpiralMatrix.cs similarity index 100% rename from generators/Exercises/Generators/SpiralMatrix.cs rename to generators.deprecated/Exercises/Generators/SpiralMatrix.cs diff --git a/generators/Exercises/Generators/Sublist.cs b/generators.deprecated/Exercises/Generators/Sublist.cs similarity index 100% rename from generators/Exercises/Generators/Sublist.cs rename to generators.deprecated/Exercises/Generators/Sublist.cs diff --git a/generators/Exercises/Generators/Tournament.cs b/generators.deprecated/Exercises/Generators/Tournament.cs similarity index 100% rename from generators/Exercises/Generators/Tournament.cs rename to generators.deprecated/Exercises/Generators/Tournament.cs diff --git a/generators/Exercises/Generators/Transpose.cs b/generators.deprecated/Exercises/Generators/Transpose.cs similarity index 100% rename from generators/Exercises/Generators/Transpose.cs rename to generators.deprecated/Exercises/Generators/Transpose.cs diff --git a/generators/Exercises/Generators/Triangle.cs b/generators.deprecated/Exercises/Generators/Triangle.cs similarity index 100% rename from generators/Exercises/Generators/Triangle.cs rename to generators.deprecated/Exercises/Generators/Triangle.cs diff --git a/generators/Exercises/Generators/TwelveDays.cs b/generators.deprecated/Exercises/Generators/TwelveDays.cs similarity index 100% rename from generators/Exercises/Generators/TwelveDays.cs rename to generators.deprecated/Exercises/Generators/TwelveDays.cs diff --git a/generators/Exercises/Generators/TwoBucket.cs b/generators.deprecated/Exercises/Generators/TwoBucket.cs similarity index 100% rename from generators/Exercises/Generators/TwoBucket.cs rename to generators.deprecated/Exercises/Generators/TwoBucket.cs diff --git a/generators/Exercises/Generators/VariableLengthQuantity.cs b/generators.deprecated/Exercises/Generators/VariableLengthQuantity.cs similarity index 100% rename from generators/Exercises/Generators/VariableLengthQuantity.cs rename to generators.deprecated/Exercises/Generators/VariableLengthQuantity.cs diff --git a/generators/Exercises/Generators/WordCount.cs b/generators.deprecated/Exercises/Generators/WordCount.cs similarity index 100% rename from generators/Exercises/Generators/WordCount.cs rename to generators.deprecated/Exercises/Generators/WordCount.cs diff --git a/generators/Exercises/Generators/WordSearch.cs b/generators.deprecated/Exercises/Generators/WordSearch.cs similarity index 100% rename from generators/Exercises/Generators/WordSearch.cs rename to generators.deprecated/Exercises/Generators/WordSearch.cs diff --git a/generators/Exercises/Generators/Yacht.cs b/generators.deprecated/Exercises/Generators/Yacht.cs similarity index 100% rename from generators/Exercises/Generators/Yacht.cs rename to generators.deprecated/Exercises/Generators/Yacht.cs diff --git a/generators/Exercises/Generators/Zipper.cs b/generators.deprecated/Exercises/Generators/Zipper.cs similarity index 100% rename from generators/Exercises/Generators/Zipper.cs rename to generators.deprecated/Exercises/Generators/Zipper.cs diff --git a/generators/Exercises/TestedMethodType.cs b/generators.deprecated/Exercises/TestedMethodType.cs similarity index 100% rename from generators/Exercises/TestedMethodType.cs rename to generators.deprecated/Exercises/TestedMethodType.cs diff --git a/generators/GeneratorStatus.cs b/generators.deprecated/GeneratorStatus.cs similarity index 100% rename from generators/GeneratorStatus.cs rename to generators.deprecated/GeneratorStatus.cs diff --git a/generators.deprecated/Generators.deprecated.csproj b/generators.deprecated/Generators.deprecated.csproj new file mode 100644 index 0000000000..3634b40bf5 --- /dev/null +++ b/generators.deprecated/Generators.deprecated.csproj @@ -0,0 +1,24 @@ + + + Exe + net9.0 + Exercism.CSharp + enable + + + + + + + + + + + + + + + + $(MSBuildProjectDirectory) + + \ No newline at end of file diff --git a/generators.deprecated/Generators.deprecated.sln b/generators.deprecated/Generators.deprecated.sln new file mode 100644 index 0000000000..234919335c --- /dev/null +++ b/generators.deprecated/Generators.deprecated.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators.deprecated", "Generators.deprecated.csproj", "{F310316B-5E18-4E7F-A77D-D26AD8D92307}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/generators/Helpers/ArrayExtensions.cs b/generators.deprecated/Helpers/ArrayExtensions.cs similarity index 100% rename from generators/Helpers/ArrayExtensions.cs rename to generators.deprecated/Helpers/ArrayExtensions.cs diff --git a/generators/Helpers/EnumerableExtensions.cs b/generators.deprecated/Helpers/EnumerableExtensions.cs similarity index 100% rename from generators/Helpers/EnumerableExtensions.cs rename to generators.deprecated/Helpers/EnumerableExtensions.cs diff --git a/generators/Helpers/NameExtensions.cs b/generators.deprecated/Helpers/NameExtensions.cs similarity index 100% rename from generators/Helpers/NameExtensions.cs rename to generators.deprecated/Helpers/NameExtensions.cs diff --git a/generators/Input/CanonicalDataJsonParser.cs b/generators.deprecated/Input/CanonicalDataJsonParser.cs similarity index 100% rename from generators/Input/CanonicalDataJsonParser.cs rename to generators.deprecated/Input/CanonicalDataJsonParser.cs diff --git a/generators/Input/ExerciseParser.cs b/generators.deprecated/Input/ExerciseParser.cs similarity index 100% rename from generators/Input/ExerciseParser.cs rename to generators.deprecated/Input/ExerciseParser.cs diff --git a/generators/Input/JTokenHelper.cs b/generators.deprecated/Input/JTokenHelper.cs similarity index 100% rename from generators/Input/JTokenHelper.cs rename to generators.deprecated/Input/JTokenHelper.cs diff --git a/generators/Input/PropSpecsRepository.cs b/generators.deprecated/Input/PropSpecsRepository.cs similarity index 100% rename from generators/Input/PropSpecsRepository.cs rename to generators.deprecated/Input/PropSpecsRepository.cs diff --git a/generators/Input/TestsTomlParser.cs b/generators.deprecated/Input/TestsTomlParser.cs similarity index 100% rename from generators/Input/TestsTomlParser.cs rename to generators.deprecated/Input/TestsTomlParser.cs diff --git a/generators/Logging.cs b/generators.deprecated/Logging.cs similarity index 100% rename from generators/Logging.cs rename to generators.deprecated/Logging.cs diff --git a/generators/Options.cs b/generators.deprecated/Options.cs similarity index 100% rename from generators/Options.cs rename to generators.deprecated/Options.cs diff --git a/generators/Output/Rendering/IndentFilter.cs b/generators.deprecated/Output/Rendering/IndentFilter.cs similarity index 100% rename from generators/Output/Rendering/IndentFilter.cs rename to generators.deprecated/Output/Rendering/IndentFilter.cs diff --git a/generators/Output/Rendering/MultiLineString.cs b/generators.deprecated/Output/Rendering/MultiLineString.cs similarity index 100% rename from generators/Output/Rendering/MultiLineString.cs rename to generators.deprecated/Output/Rendering/MultiLineString.cs diff --git a/generators/Output/Rendering/Render.cs b/generators.deprecated/Output/Rendering/Render.cs similarity index 100% rename from generators/Output/Rendering/Render.cs rename to generators.deprecated/Output/Rendering/Render.cs diff --git a/generators/Output/Rendering/RenderArray.cs b/generators.deprecated/Output/Rendering/RenderArray.cs similarity index 100% rename from generators/Output/Rendering/RenderArray.cs rename to generators.deprecated/Output/Rendering/RenderArray.cs diff --git a/generators/Output/Rendering/RenderAssert.cs b/generators.deprecated/Output/Rendering/RenderAssert.cs similarity index 100% rename from generators/Output/Rendering/RenderAssert.cs rename to generators.deprecated/Output/Rendering/RenderAssert.cs diff --git a/generators/Output/Rendering/RenderCollection.cs b/generators.deprecated/Output/Rendering/RenderCollection.cs similarity index 100% rename from generators/Output/Rendering/RenderCollection.cs rename to generators.deprecated/Output/Rendering/RenderCollection.cs diff --git a/generators/Output/Rendering/RenderDateTime.cs b/generators.deprecated/Output/Rendering/RenderDateTime.cs similarity index 100% rename from generators/Output/Rendering/RenderDateTime.cs rename to generators.deprecated/Output/Rendering/RenderDateTime.cs diff --git a/generators/Output/Rendering/RenderDictionary.cs b/generators.deprecated/Output/Rendering/RenderDictionary.cs similarity index 100% rename from generators/Output/Rendering/RenderDictionary.cs rename to generators.deprecated/Output/Rendering/RenderDictionary.cs diff --git a/generators/Output/Rendering/RenderExtensions.cs b/generators.deprecated/Output/Rendering/RenderExtensions.cs similarity index 100% rename from generators/Output/Rendering/RenderExtensions.cs rename to generators.deprecated/Output/Rendering/RenderExtensions.cs diff --git a/generators/Output/Rendering/RenderHashSet.cs b/generators.deprecated/Output/Rendering/RenderHashSet.cs similarity index 100% rename from generators/Output/Rendering/RenderHashSet.cs rename to generators.deprecated/Output/Rendering/RenderHashSet.cs diff --git a/generators/Output/Rendering/RenderList.cs b/generators.deprecated/Output/Rendering/RenderList.cs similarity index 100% rename from generators/Output/Rendering/RenderList.cs rename to generators.deprecated/Output/Rendering/RenderList.cs diff --git a/generators/Output/Rendering/RenderNumber.cs b/generators.deprecated/Output/Rendering/RenderNumber.cs similarity index 100% rename from generators/Output/Rendering/RenderNumber.cs rename to generators.deprecated/Output/Rendering/RenderNumber.cs diff --git a/generators/Output/Rendering/RenderString.cs b/generators.deprecated/Output/Rendering/RenderString.cs similarity index 100% rename from generators/Output/Rendering/RenderString.cs rename to generators.deprecated/Output/Rendering/RenderString.cs diff --git a/generators/Output/Rendering/RenderVariable.cs b/generators.deprecated/Output/Rendering/RenderVariable.cs similarity index 100% rename from generators/Output/Rendering/RenderVariable.cs rename to generators.deprecated/Output/Rendering/RenderVariable.cs diff --git a/generators/Output/Rendering/Template.cs b/generators.deprecated/Output/Rendering/Template.cs similarity index 100% rename from generators/Output/Rendering/Template.cs rename to generators.deprecated/Output/Rendering/Template.cs diff --git a/generators/Output/Rendering/Templates/_Act.liquid b/generators.deprecated/Output/Rendering/Templates/_Act.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_Act.liquid rename to generators.deprecated/Output/Rendering/Templates/_Act.liquid diff --git a/generators/Output/Rendering/Templates/_Arrange.liquid b/generators.deprecated/Output/Rendering/Templates/_Arrange.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_Arrange.liquid rename to generators.deprecated/Output/Rendering/Templates/_Arrange.liquid diff --git a/generators/Output/Rendering/Templates/_AssertBoolean.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertBoolean.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertBoolean.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertBoolean.liquid diff --git a/generators/Output/Rendering/Templates/_AssertEmpty.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertEmpty.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertEmpty.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertEmpty.liquid diff --git a/generators/Output/Rendering/Templates/_AssertEqual.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertEqual.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertEqual.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertEqual.liquid diff --git a/generators/Output/Rendering/Templates/_AssertEqualWithin.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertEqualWithin.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertEqualWithin.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertEqualWithin.liquid diff --git a/generators/Output/Rendering/Templates/_AssertInRange.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertInRange.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertInRange.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertInRange.liquid diff --git a/generators/Output/Rendering/Templates/_AssertMatches.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertMatches.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertMatches.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertMatches.liquid diff --git a/generators/Output/Rendering/Templates/_AssertNotEqual.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertNotEqual.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertNotEqual.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertNotEqual.liquid diff --git a/generators/Output/Rendering/Templates/_AssertNull.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertNull.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertNull.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertNull.liquid diff --git a/generators/Output/Rendering/Templates/_AssertThrows.liquid b/generators.deprecated/Output/Rendering/Templates/_AssertThrows.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_AssertThrows.liquid rename to generators.deprecated/Output/Rendering/Templates/_AssertThrows.liquid diff --git a/generators/Output/Rendering/Templates/_TestClass.liquid b/generators.deprecated/Output/Rendering/Templates/_TestClass.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_TestClass.liquid rename to generators.deprecated/Output/Rendering/Templates/_TestClass.liquid diff --git a/generators/Output/Rendering/Templates/_TestMethod.liquid b/generators.deprecated/Output/Rendering/Templates/_TestMethod.liquid similarity index 100% rename from generators/Output/Rendering/Templates/_TestMethod.liquid rename to generators.deprecated/Output/Rendering/Templates/_TestMethod.liquid diff --git a/generators/Output/Rendering/UnescapedValue.cs b/generators.deprecated/Output/Rendering/UnescapedValue.cs similarity index 100% rename from generators/Output/Rendering/UnescapedValue.cs rename to generators.deprecated/Output/Rendering/UnescapedValue.cs diff --git a/generators/Output/TestClass.cs b/generators.deprecated/Output/TestClass.cs similarity index 100% rename from generators/Output/TestClass.cs rename to generators.deprecated/Output/TestClass.cs diff --git a/generators/Output/TestClassOutput.cs b/generators.deprecated/Output/TestClassOutput.cs similarity index 100% rename from generators/Output/TestClassOutput.cs rename to generators.deprecated/Output/TestClassOutput.cs diff --git a/generators/Output/TestMethod.cs b/generators.deprecated/Output/TestMethod.cs similarity index 100% rename from generators/Output/TestMethod.cs rename to generators.deprecated/Output/TestMethod.cs diff --git a/generators/Output/TestMethodOutput.cs b/generators.deprecated/Output/TestMethodOutput.cs similarity index 100% rename from generators/Output/TestMethodOutput.cs rename to generators.deprecated/Output/TestMethodOutput.cs diff --git a/generators.deprecated/Program.cs b/generators.deprecated/Program.cs new file mode 100644 index 0000000000..c32e002b9f --- /dev/null +++ b/generators.deprecated/Program.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +using CommandLine; +using Exercism.CSharp.Exercises; + +using Serilog; + +namespace Exercism.CSharp; + +public static class Program +{ + public static void Main(string[] args) + { + Logging.Setup(); + + Options.Parse(args) + .WithParsed(OnParseSuccess) + .WithNotParsed(OnParseError); + } + + private static void OnParseSuccess(Options options) + { + var generatorRunner = new GeneratorRunner(options); + + // TODO: enable nullable + if (options.Exercise == null) + generatorRunner.RegenerateAllExercises(); + else + generatorRunner.RegenerateSingleExercise(options.Exercise); + } + + private static void OnParseError(IEnumerable errors) => + Log.Error("Errors: {Errors}", errors); +} \ No newline at end of file diff --git a/generators/CanonicalData.cs b/generators/CanonicalData.cs new file mode 100644 index 0000000000..7dbc0045fa --- /dev/null +++ b/generators/CanonicalData.cs @@ -0,0 +1,61 @@ +using System.Collections.Immutable; + +using LibGit2Sharp; + +using Newtonsoft.Json.Linq; + +namespace Generators; + +internal record CanonicalData(Exercise Exercise, JObject[] TestCases); + +internal static class CanonicalDataParser +{ + static CanonicalDataParser() => ProbSpecs.Sync(); + + internal static CanonicalData Parse(Exercise exercise) => new(exercise, ParseTestCases(exercise)); + + private static JObject[] ParseTestCases(Exercise exercise) + { + var jsonObject = JObject.Parse(File.ReadAllText(Paths.CanonicalDataFile(exercise))); + return ParseTestCases(jsonObject, ImmutableQueue.Empty).ToArray(); + } + + private static IEnumerable ParseTestCases(JObject jsonObject, ImmutableQueue path) + { + var updatedPath = jsonObject.TryGetValue("description", out var description) + ? path.Enqueue(description.Value()!) + : path; + + return jsonObject.TryGetValue("cases", out var cases) + ? ((JArray)cases).Cast().SelectMany(child => ParseTestCases(child, updatedPath)) + : [ToTestCase(jsonObject, updatedPath)]; + } + + private static JObject ToTestCase(JObject testCaseJson, IEnumerable path) + { + testCaseJson["path"] = JArray.FromObject(path); + return testCaseJson; + } + + private static class ProbSpecs + { + internal static void Sync() + { + Clone(); + Pull(); + } + + private static void Clone() + { + if (!Directory.Exists(Paths.ProbSpecsDir)) + Repository.Clone("https://github.com/exercism/problem-specifications.git", Paths.ProbSpecsDir); + } + + private static void Pull() + { + using var repo = new Repository(Paths.ProbSpecsDir); + Commands.Pull(repo, new Signature("Exercism", "info@exercism.org", DateTimeOffset.Now), new PullOptions()); + } + } +} + diff --git a/generators/Exercises.cs b/generators/Exercises.cs new file mode 100644 index 0000000000..6b4452d408 --- /dev/null +++ b/generators/Exercises.cs @@ -0,0 +1,30 @@ +using Humanizer; + +namespace Generators; + +internal record Exercise(string Slug, string Name); + +internal static class Exercises +{ + internal static Exercise[] TemplatedExercises() => + Directory.EnumerateFiles(Paths.PracticeExercisesDir, "Generator.tpl", SearchOption.AllDirectories) + .Select(templateFile => Directory.GetParent(templateFile)!.Parent!.Name) + .Select(ToExercise) + .OrderBy(exercise => exercise.Slug) + .ToArray(); + + internal static Exercise TemplatedExercise(string slug) + { + var exercise = ToExercise(slug); + + if (!Directory.Exists(Paths.ExerciseDir(exercise))) + throw new ArgumentException($"Could not find exercise '{slug}'."); + + if (!File.Exists(Paths.TemplateFile(exercise))) + throw new ArgumentException($"Could not find template file for exercise '{slug}'."); + + return exercise; + } + + private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize()); +} \ No newline at end of file diff --git a/generators/Exercises/Generators/Acronym.cs b/generators/Exercises/Generators/Acronym.cs deleted file mode 100644 index cbbbf9f6fa..0000000000 --- a/generators/Exercises/Generators/Acronym.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Exercism.CSharp.Exercises.Generators; - -internal class Acronym : ExerciseGenerator -{ -} \ No newline at end of file diff --git a/generators/Exercises/Generators/DifferenceOfSquares.cs b/generators/Exercises/Generators/DifferenceOfSquares.cs deleted file mode 100644 index bdeaf0bceb..0000000000 --- a/generators/Exercises/Generators/DifferenceOfSquares.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class DifferenceOfSquares : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) => testMethod.TestedMethod = $"Calculate{testMethod.TestedMethod}"; -} \ No newline at end of file diff --git a/generators/Exercises/Generators/EliudsEggs.cs b/generators/Exercises/Generators/EliudsEggs.cs deleted file mode 100644 index 25d2409e85..0000000000 --- a/generators/Exercises/Generators/EliudsEggs.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class EliudsEggs : ExerciseGenerator -{ -} diff --git a/generators/Exercises/Generators/Hamming.cs b/generators/Exercises/Generators/Hamming.cs deleted file mode 100644 index d16d143808..0000000000 --- a/generators/Exercises/Generators/Hamming.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class Hamming : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) - { - if (!(testMethod.Expected is int)) - testMethod.ExceptionThrown = typeof(ArgumentException); - } -} \ No newline at end of file diff --git a/generators/Exercises/Generators/Isogram.cs b/generators/Exercises/Generators/Isogram.cs deleted file mode 100644 index 01aa861e41..0000000000 --- a/generators/Exercises/Generators/Isogram.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Exercism.CSharp.Exercises.Generators; - -internal class Isogram : ExerciseGenerator -{ -} \ No newline at end of file diff --git a/generators/Exercises/Generators/Leap.cs b/generators/Exercises/Generators/Leap.cs deleted file mode 100644 index 79220da4a0..0000000000 --- a/generators/Exercises/Generators/Leap.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class Leap : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) => testMethod.TestedMethod = "IsLeapYear"; -} \ No newline at end of file diff --git a/generators/Exercises/Generators/Pangram.cs b/generators/Exercises/Generators/Pangram.cs deleted file mode 100644 index 818924661b..0000000000 --- a/generators/Exercises/Generators/Pangram.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Exercism.CSharp.Exercises.Generators; - -internal class Pangram : ExerciseGenerator -{ -} \ No newline at end of file diff --git a/generators/Exercises/Generators/PerfectNumbers.cs b/generators/Exercises/Generators/PerfectNumbers.cs deleted file mode 100644 index a04e51a39c..0000000000 --- a/generators/Exercises/Generators/PerfectNumbers.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class PerfectNumbers : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) - { - if (testMethod.Expected is string) - testMethod.Expected = Render.Enum("Classification", testMethod.Expected); - else - testMethod.ExceptionThrown = typeof(ArgumentOutOfRangeException); - } -} \ No newline at end of file diff --git a/generators/Exercises/Generators/RotationalCipher.cs b/generators/Exercises/Generators/RotationalCipher.cs deleted file mode 100644 index 3a002a7a4b..0000000000 --- a/generators/Exercises/Generators/RotationalCipher.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Exercism.CSharp.Exercises.Generators; - -internal class RotationalCipher : ExerciseGenerator -{ -} \ No newline at end of file diff --git a/generators/Exercises/Generators/Sieve.cs b/generators/Exercises/Generators/Sieve.cs deleted file mode 100644 index db1258a5a7..0000000000 --- a/generators/Exercises/Generators/Sieve.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Exercism.CSharp.Output; -using Exercism.CSharp.Output.Rendering; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class Sieve : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) => - testMethod.ForceEvaluation = true; - - protected override void UpdateNamespaces(ISet namespaces) - { - namespaces.Add(typeof(ArgumentOutOfRangeException).Namespace!); - namespaces.Add(typeof(Enumerable).Namespace!); - } - - protected override void UpdateTestClass(TestClass testClass) => - AddTestCase(testClass); - - private static readonly string throwExceptionTestCase = @" -[Fact(Skip = ""Remove this Skip property to run this test"")] -public void No_negative_numbers() -{ - Assert.Throws(() => Sieve.Primes(-1).ToArray()); -}"; - - private static void AddTestCase(TestClass testClass) => - testClass.AdditionalMethods.Add(throwExceptionTestCase); -} \ No newline at end of file diff --git a/generators/Exercises/Generators/SpaceAge.cs b/generators/Exercises/Generators/SpaceAge.cs deleted file mode 100644 index a32b34808e..0000000000 --- a/generators/Exercises/Generators/SpaceAge.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class SpaceAge : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) - { - testMethod.TestedMethodType = TestedMethodType.InstanceMethod; - testMethod.ConstructorInputParameters = new[] { "seconds" }; - testMethod.Assert = RenderAssert(testMethod); - } - - private string RenderAssert(TestMethod testMethod) - => Render.AssertEqualWithin(Render.Object(testMethod.Expected), $"sut.On{testMethod.Input["planet"]}()", 2); -} \ No newline at end of file diff --git a/generators/Exercises/Generators/SquareRoot.cs b/generators/Exercises/Generators/SquareRoot.cs deleted file mode 100644 index e9f9635a91..0000000000 --- a/generators/Exercises/Generators/SquareRoot.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class SquareRoot : ExerciseGenerator -{ -} diff --git a/generators/Exercises/Generators/SumOfMultiples.cs b/generators/Exercises/Generators/SumOfMultiples.cs deleted file mode 100644 index bb3a1e2bcd..0000000000 --- a/generators/Exercises/Generators/SumOfMultiples.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using Exercism.CSharp.Output; -using Newtonsoft.Json.Linq; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class SumOfMultiples : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) - { - if (testMethod.Input["factors"] is JArray) - testMethod.Input["factors"] = Array.Empty(); - } - - protected override void UpdateNamespaces(ISet namespaces) => namespaces.Add(typeof(Array).Namespace!); -} \ No newline at end of file diff --git a/generators/Exercises/Generators/TwoFer.cs b/generators/Exercises/Generators/TwoFer.cs deleted file mode 100644 index 0975667d76..0000000000 --- a/generators/Exercises/Generators/TwoFer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class TwoFer : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) - { - testMethod.TestedMethod = "Speak"; - - if (testMethod.Input["name"] is null) - testMethod.InputParameters = Array.Empty(); - } -} \ No newline at end of file diff --git a/generators/Exercises/Generators/Wordy.cs b/generators/Exercises/Generators/Wordy.cs deleted file mode 100644 index 8f5181e541..0000000000 --- a/generators/Exercises/Generators/Wordy.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class Wordy : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) - { - if (!(testMethod.Expected is int)) - testMethod.ExceptionThrown = typeof(ArgumentException); - } -} \ No newline at end of file diff --git a/generators/Exercises/Generators/ZebraPuzzle.cs b/generators/Exercises/Generators/ZebraPuzzle.cs deleted file mode 100644 index c535618d7d..0000000000 --- a/generators/Exercises/Generators/ZebraPuzzle.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Exercism.CSharp.Output; - -namespace Exercism.CSharp.Exercises.Generators; - -internal class ZebraPuzzle : ExerciseGenerator -{ - protected override void UpdateTestMethod(TestMethod testMethod) => testMethod.Expected = Render.Enum("Nationality", testMethod.Expected); -} \ No newline at end of file diff --git a/generators/Formatting.cs b/generators/Formatting.cs new file mode 100644 index 0000000000..9c151f2d80 --- /dev/null +++ b/generators/Formatting.cs @@ -0,0 +1,22 @@ +using System.Globalization; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Formatting; + +namespace Generators; + +internal static class Formatting +{ + private static readonly AdhocWorkspace AdhocWorkspace = new(); + + internal static string FormatCode(string code) => + Formatter.Format(Parse(code).NormalizeWhitespace(), AdhocWorkspace) + .ToFullString() + Environment.NewLine; + + private static SyntaxNode Parse(string code) => + CSharpSyntaxTree.ParseText(code).GetRoot(); + + internal static string FormatLiteral(object obj) => + SymbolDisplay.FormatLiteral(Convert.ToString(obj, CultureInfo.InvariantCulture)!, obj is string); +} \ No newline at end of file diff --git a/generators/Generators.csproj b/generators/Generators.csproj index 3634b40bf5..93a764cecd 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -1,24 +1,27 @@  + Exe net9.0 - Exercism.CSharp + enable enable + - - - + + + + + + - - - + + - + + - - $(MSBuildProjectDirectory) - - \ No newline at end of file + + diff --git a/generators/Generators.sln b/generators/Generators.sln index de1c8b2771..9c170c1d69 100644 --- a/generators/Generators.sln +++ b/generators/Generators.sln @@ -1,22 +1,22 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators", "Generators.csproj", "{F310316B-5E18-4E7F-A77D-D26AD8D92307}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generators", "Generators.csproj", "{254DE060-5394-41DE-A7E1-4FDF1D7D1167}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F310316B-5E18-4E7F-A77D-D26AD8D92307}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {254DE060-5394-41DE-A7E1-4FDF1D7D1167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {254DE060-5394-41DE-A7E1-4FDF1D7D1167}.Debug|Any CPU.Build.0 = Debug|Any CPU + {254DE060-5394-41DE-A7E1-4FDF1D7D1167}.Release|Any CPU.ActiveCfg = Release|Any CPU + {254DE060-5394-41DE-A7E1-4FDF1D7D1167}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection EndGlobal diff --git a/generators/Naming.cs b/generators/Naming.cs new file mode 100644 index 0000000000..d00369c683 --- /dev/null +++ b/generators/Naming.cs @@ -0,0 +1,23 @@ +using Humanizer; + +namespace Generators; + +internal static class Naming +{ + internal static string ToMethodName(params object[] path) => + path.Cast() + .Unwords() + .Words() + .Select(Transform) + .Unwords() + .Underscore() + .Transform(To.SentenceCase); + + private static string Transform(string str, int index) => + index == 0 && int.TryParse(str, out var i) + ? i.ToWords() + : str.Dehumanize(); + + private static IEnumerable Words(this string str) => str.Split(' '); + private static string Unwords(this IEnumerable strs) => string.Join(' ', strs); +} \ No newline at end of file diff --git a/generators/Paths.cs b/generators/Paths.cs new file mode 100644 index 0000000000..e2cbbe7df8 --- /dev/null +++ b/generators/Paths.cs @@ -0,0 +1,25 @@ +namespace Generators; + +internal static class Paths +{ + private static readonly string RootDir = GetRootDir(); + internal static readonly string ProbSpecsDir = Path.Join(RootDir, ".problem-specifications"); + private static readonly string ProbSpecsExercisesDir = Path.Join(ProbSpecsDir, "exercises"); + internal static readonly string PracticeExercisesDir = Path.Join(RootDir, "exercises", "practice"); + + internal static string ExerciseDir(Exercise exercise) => Path.Join(PracticeExercisesDir, exercise.Slug); + internal static string TestsFile(Exercise exercise) => Path.Join(ExerciseDir(exercise), $"{exercise.Name}Tests.cs"); + internal static string TestsTomlFile(Exercise exercise) => Path.Join(ExerciseDir(exercise), ".meta", "tests.toml"); + internal static string TemplateFile(Exercise exercise) => Path.Join(ExerciseDir(exercise), ".meta", "Generator.tpl"); + internal static string CanonicalDataFile(Exercise exercise) => Path.Join(ProbSpecsExercisesDir, exercise.Slug, "canonical-data.json"); + + private static string GetRootDir() + { + // Workaround: IDEs use a different current directory than dotnet run + var currentDir = Environment.CurrentDirectory; + while (!File.Exists(Path.Join(currentDir, "LICENSE"))) + currentDir = Path.GetDirectoryName(currentDir); + + return currentDir!; + } +} \ No newline at end of file diff --git a/generators/Program.cs b/generators/Program.cs index c32e002b9f..6da9d090d0 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -1,34 +1,25 @@ -using System.Collections.Generic; +using CommandLine; -using CommandLine; -using Exercism.CSharp.Exercises; +namespace Generators; -using Serilog; - -namespace Exercism.CSharp; - -public static class Program +public static class Program { - public static void Main(string[] args) + private class Options { - Logging.Setup(); - - Options.Parse(args) - .WithParsed(OnParseSuccess) - .WithNotParsed(OnParseError); - } - - private static void OnParseSuccess(Options options) - { - var generatorRunner = new GeneratorRunner(options); - - // TODO: enable nullable - if (options.Exercise == null) - generatorRunner.RegenerateAllExercises(); - else - generatorRunner.RegenerateSingleExercise(options.Exercise); - } + [Option('e', "exercise", Required = false, HelpText = "The exercise (slug) to generate the tests file for.")] + public string? Exercise { get; set; } + } + + static void Main(string[] args) => + Parser.Default.ParseArguments(args) + .WithParsed(options => + { + foreach (var exercise in Exercises(options)) + TestsGenerator.Generate(exercise); + }); - private static void OnParseError(IEnumerable errors) => - Log.Error("Errors: {Errors}", errors); + private static Exercise[] Exercises(Options options) => + options.Exercise is null + ? Generators.Exercises.TemplatedExercises() + : [Generators.Exercises.TemplatedExercise(options.Exercise)]; } \ No newline at end of file diff --git a/generators/Templates.cs b/generators/Templates.cs new file mode 100644 index 0000000000..4b0c6f82b2 --- /dev/null +++ b/generators/Templates.cs @@ -0,0 +1,58 @@ +using System.Dynamic; +using System.Globalization; + +using HandlebarsDotNet; +using HandlebarsDotNet.Helpers; + +using Newtonsoft.Json.Linq; + +namespace Generators; + +internal static class Templates +{ + private static readonly IHandlebars HandlebarsContext = Handlebars.Create(); + + static Templates() + { + HandlebarsHelpers.Register(HandlebarsContext, options => { options.UseCategoryPrefix = false; }); + HandlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture; + HandlebarsContext.RegisterHelper("lit", (writer, context, parameters) => + { + writer.WriteSafeString(Formatting.FormatLiteral(parameters.First())); + }); + } + + public static string RenderTestsCode(CanonicalData canonicalData) => + CompileTemplate(canonicalData.Exercise)(TemplateData.ForCanonicalData(canonicalData)); + + private static HandlebarsTemplate CompileTemplate(Exercise exercise) => + HandlebarsContext.Compile(File.ReadAllText(Paths.TemplateFile(exercise))); + + private static class TemplateData + { + internal static Dictionary ForCanonicalData(CanonicalData canonicalData) + { + var testCases = canonicalData.TestCases.Select(Create).ToArray(); + + return new() + { + ["test_cases"] = testCases.ToArray(), + ["test_cases_by_property"] = GroupTestCasesByProperty(testCases) + }; + } + + private static ExpandoObject Create(JToken testCase) + { + dynamic testData = testCase.ToObject()!; + testData.test_method_name = Naming.ToMethodName(testData.path.ToArray()); + testData.short_test_method_name = Naming.ToMethodName(testData.description); + + return testData; + } + + private static Dictionary GroupTestCasesByProperty(IEnumerable testCases) => + testCases + .GroupBy(testCase => (string)testCase.property) + .ToDictionary(kv => kv.Key, kv => kv.ToArray()); + } +} diff --git a/generators/TestCasesConfiguration.cs b/generators/TestCasesConfiguration.cs new file mode 100644 index 0000000000..21b367058f --- /dev/null +++ b/generators/TestCasesConfiguration.cs @@ -0,0 +1,28 @@ +using Tomlyn; +using Tomlyn.Model; + +namespace Generators; + +internal static class TestCasesConfiguration +{ + internal static CanonicalData RemoveExcludedTestCases(CanonicalData canonicalData) + { + var excludedTestCaseIds = ExcludedTestCaseIds(canonicalData.Exercise); + var includedTestCases = canonicalData.TestCases + .Where(testCase => !excludedTestCaseIds.Contains(testCase["uuid"]!.ToObject()!)) + .ToArray(); + + return canonicalData with { TestCases = includedTestCases }; + } + + private static HashSet ExcludedTestCaseIds(Exercise exercise) => + Toml.ToModel(File.ReadAllText(Paths.TestsTomlFile(exercise))) + .Where(IsExcluded) + .Select(table => table.Key) + .ToHashSet(); + + private static bool IsExcluded(KeyValuePair idTablePair) => + idTablePair.Value is TomlTable table && + table.TryGetValue("include", out var includeValue) && + includeValue is false; +} \ No newline at end of file diff --git a/generators/TestsGenerator.cs b/generators/TestsGenerator.cs new file mode 100644 index 0000000000..c73ca5416e --- /dev/null +++ b/generators/TestsGenerator.cs @@ -0,0 +1,16 @@ +namespace Generators; + +internal static class TestsGenerator +{ + internal static void Generate(Exercise exercise) + { + Console.WriteLine($"{exercise.Slug}: generating tests..."); + + var canonicalData = CanonicalDataParser.Parse(exercise); + var filteredCanonicalData = TestCasesConfiguration.RemoveExcludedTestCases(canonicalData); + + var testCode = Templates.RenderTestsCode(filteredCanonicalData); + var formattedTestCode = Formatting.FormatCode(testCode); + File.WriteAllText(Paths.TestsFile(exercise), formattedTestCode); + } +} \ No newline at end of file