From 25e5da46740a02c844ec9f1fb0dca270aff88a81 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 10 Mar 2017 19:08:25 +0100 Subject: [PATCH 01/11] Started work on generators --- generators/CanonicalData.cs | 18 +++++++++++++ generators/CanonicalDataCase.cs | 26 ++++++++++++++++++ generators/CanonicalDataParser.cs | 28 ++++++++++++++++++++ generators/GeneratorCollection.cs | 22 ++++++++++++++++ generators/Generators.csproj | 12 +++++++++ generators/Generators.sln | 22 ++++++++++++++++ generators/Program.cs | 44 +++++++++++++++++++++++++++++++ 7 files changed, 172 insertions(+) create mode 100644 generators/CanonicalData.cs create mode 100644 generators/CanonicalDataCase.cs create mode 100644 generators/CanonicalDataParser.cs create mode 100644 generators/GeneratorCollection.cs create mode 100644 generators/Generators.csproj create mode 100644 generators/Generators.sln create mode 100644 generators/Program.cs diff --git a/generators/CanonicalData.cs b/generators/CanonicalData.cs new file mode 100644 index 0000000000..5436dc426b --- /dev/null +++ b/generators/CanonicalData.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; + +namespace Generators +{ + public class CanonicalData + { + [Required] + public string Exercise { get; set; } + + [Required] + public string Version { get; set; } + + public string[] Comments { get; set; } + + public CanonicalDataCase[] Cases { get; set; } + } +} \ No newline at end of file diff --git a/generators/CanonicalDataCase.cs b/generators/CanonicalDataCase.cs new file mode 100644 index 0000000000..7e20e88482 --- /dev/null +++ b/generators/CanonicalDataCase.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; + +namespace Generators +{ + public class CanonicalDataCase : IValidatableObject + { + [Required] + public string Description { get; set; } + + public string Property { get; set; } + + public string[] Comments { get; set; } + + public object Expected { get; set; } + + public CanonicalDataCase[] Cases { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (Cases == null && string.IsNullOrWhiteSpace(Property)) + yield return new ValidationResult("This field is required.", new[] { nameof(Property) }); + } + } +} \ No newline at end of file diff --git a/generators/CanonicalDataParser.cs b/generators/CanonicalDataParser.cs new file mode 100644 index 0000000000..4e227c64e6 --- /dev/null +++ b/generators/CanonicalDataParser.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; +using System; +using System.ComponentModel.DataAnnotations; +using System.Net.Http; + +namespace Generators +{ + public class CanonicalDataParser + { + private readonly HttpClient httpClient = new HttpClient(); + + public CanonicalData Parse(string exercise) + { + var canonicalDataJson = DownloadCanonicalDataJson(exercise); + var canonicalData = JsonConvert.DeserializeObject(canonicalDataJson); + + Validator.ValidateObject(canonicalData, new ValidationContext(canonicalData)); + + return canonicalData; + } + + private string DownloadCanonicalDataJson(string exercise) + => httpClient.GetStringAsync(GetCanonicalDataUrl(exercise)).GetAwaiter().GetResult(); + + private static Uri GetCanonicalDataUrl(string exercise) + => new Uri($"https://raw.githubusercontent.com/exercism/x-common/master/exercises/{exercise}/canonical-data.json"); + } +} \ No newline at end of file diff --git a/generators/GeneratorCollection.cs b/generators/GeneratorCollection.cs new file mode 100644 index 0000000000..ba25a37683 --- /dev/null +++ b/generators/GeneratorCollection.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Collections; +using System.Reflection; + +namespace Generators +{ + public class GeneratorCollection : IEnumerable + { + private readonly IEnumerable generators = GetDefinedGenerators(); + + public IEnumerator GetEnumerator() => generators.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private static IEnumerable GetDefinedGenerators() => + from type in Assembly.GetEntryAssembly().GetTypes() + where typeof(Generator).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract + select (Generator)Activator.CreateInstance(type); + } +} \ No newline at end of file diff --git a/generators/Generators.csproj b/generators/Generators.csproj new file mode 100644 index 0000000000..5ad19965ca --- /dev/null +++ b/generators/Generators.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp1.1 + + + + + + + diff --git a/generators/Generators.sln b/generators/Generators.sln new file mode 100644 index 0000000000..de1c8b2771 --- /dev/null +++ b/generators/Generators.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", "Generators.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/Program.cs b/generators/Program.cs new file mode 100644 index 0000000000..b92e1ceed5 --- /dev/null +++ b/generators/Program.cs @@ -0,0 +1,44 @@ +using System; + +namespace Generators +{ + class Program + { + static void Main() + { + foreach (var generator in new GeneratorCollection()) + generator.Generate(); + } + } + + public abstract class Generator + { + private static readonly CanonicalDataParser CanonicalDataParser = new CanonicalDataParser(); + + public Generator(string exercise) + { + Exercise = exercise; + } + + public string Exercise { get; } + + public void Generate() + { + Generate(CanonicalDataParser.Parse(Exercise)); + } + + protected abstract void Generate(CanonicalData canonicalData); + } + + public class LeapExerciseGenerator : Generator + { + public LeapExerciseGenerator() : base("leap") + { + } + + protected override void Generate(CanonicalData canonicalData) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file From abf1b4d051d9d994d2587c2f8ddacae25997b143 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sat, 11 Mar 2017 09:31:11 +0100 Subject: [PATCH 02/11] Add Razor and C# scripting packages --- generators/Generators.csproj | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/generators/Generators.csproj b/generators/Generators.csproj index 5ad19965ca..305087ef05 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -1,12 +1,11 @@  - Exe netcoreapp1.1 - + + - - + \ No newline at end of file From 3b1f1051ff5193585572dd31d40a54bdf8da90fa Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 12 Mar 2017 14:49:01 +0100 Subject: [PATCH 03/11] Support nested canonical data cases --- generators/CanonicalData.cs | 7 +++-- generators/CanonicalDataCase.cs | 14 ++------- generators/CanonicalDataCasesJsonConverter.cs | 29 +++++++++++++++++++ generators/Generators.csproj | 1 + 4 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 generators/CanonicalDataCasesJsonConverter.cs diff --git a/generators/CanonicalData.cs b/generators/CanonicalData.cs index 5436dc426b..367c04862a 100644 --- a/generators/CanonicalData.cs +++ b/generators/CanonicalData.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; namespace Generators { @@ -12,7 +12,8 @@ public class CanonicalData public string Version { get; set; } public string[] Comments { get; set; } - + + [JsonConverter(typeof(CanonicalDataCasesJsonConverter))] public CanonicalDataCase[] Cases { get; set; } } } \ No newline at end of file diff --git a/generators/CanonicalDataCase.cs b/generators/CanonicalDataCase.cs index 7e20e88482..1acf50d813 100644 --- a/generators/CanonicalDataCase.cs +++ b/generators/CanonicalDataCase.cs @@ -1,26 +1,18 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; -using System.Collections.Generic; namespace Generators { - public class CanonicalDataCase : IValidatableObject + public class CanonicalDataCase { [Required] public string Description { get; set; } - + + [Required] public string Property { get; set; } public string[] Comments { get; set; } public object Expected { get; set; } - - public CanonicalDataCase[] Cases { get; set; } - - public IEnumerable Validate(ValidationContext validationContext) - { - if (Cases == null && string.IsNullOrWhiteSpace(Property)) - yield return new ValidationResult("This field is required.", new[] { nameof(Property) }); - } } } \ No newline at end of file diff --git a/generators/CanonicalDataCasesJsonConverter.cs b/generators/CanonicalDataCasesJsonConverter.cs new file mode 100644 index 0000000000..bcb1c8734e --- /dev/null +++ b/generators/CanonicalDataCasesJsonConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Generators +{ + public class CanonicalDataCasesJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var casesToken = JToken.ReadFrom(reader); + var caseTokens = casesToken.SelectTokens("$..*[?(@.property)]"); + + return new JArray(caseTokens).ToObject(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/generators/Generators.csproj b/generators/Generators.csproj index 305087ef05..d29f5a3c26 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -7,5 +7,6 @@ + \ No newline at end of file From db8a4d38b27a07ad97c30632a6e7d8e42447f72f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 12 Mar 2017 16:59:43 +0100 Subject: [PATCH 04/11] Switch to dotliquid --- generators/GeneratorCollection.cs | 2 +- generators/Generators.csproj | 8 +-- generators/Generators.csproj.user | 6 +++ generators/Program.cs | 68 +++++++++++++++++++++--- generators/Templates/TemplateReader.cs | 20 +++++++ generators/Templates/TemplateRenderer.cs | 13 +++++ generators/Templates/TestClass.cs | 8 +++ generators/Templates/TestClass.liquid | 12 +++++ generators/Templates/TestMethod.cs | 8 +++ generators/Templates/TestMethod.liquid | 4 ++ 10 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 generators/Generators.csproj.user create mode 100644 generators/Templates/TemplateReader.cs create mode 100644 generators/Templates/TemplateRenderer.cs create mode 100644 generators/Templates/TestClass.cs create mode 100644 generators/Templates/TestClass.liquid create mode 100644 generators/Templates/TestMethod.cs create mode 100644 generators/Templates/TestMethod.liquid diff --git a/generators/GeneratorCollection.cs b/generators/GeneratorCollection.cs index ba25a37683..e34688d5ae 100644 --- a/generators/GeneratorCollection.cs +++ b/generators/GeneratorCollection.cs @@ -1,7 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Collections; using System.Reflection; namespace Generators diff --git a/generators/Generators.csproj b/generators/Generators.csproj index d29f5a3c26..9bb2e5c2cb 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -2,11 +2,13 @@ Exe netcoreapp1.1 + true - - + + + + - \ No newline at end of file diff --git a/generators/Generators.csproj.user b/generators/Generators.csproj.user new file mode 100644 index 0000000000..baf24173ca --- /dev/null +++ b/generators/Generators.csproj.user @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/generators/Program.cs b/generators/Program.cs index b92e1ceed5..b6200cbdb9 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -1,10 +1,11 @@ -using System; +using Generators.Templates; +using System.Linq; namespace Generators { - class Program + public static class Program { - static void Main() + public static void Main() { foreach (var generator in new GeneratorCollection()) generator.Generate(); @@ -14,6 +15,7 @@ static void Main() public abstract class Generator { private static readonly CanonicalDataParser CanonicalDataParser = new CanonicalDataParser(); + //private static readonly TemplateParser TemplateParser = new TemplateParser(); public Generator(string exercise) { @@ -25,20 +27,74 @@ public Generator(string exercise) public void Generate() { Generate(CanonicalDataParser.Parse(Exercise)); + + var testClass = new TestClass + { + ClassName = "Leap", + TestMethods = new[] + { + new TestMethod { MethodName = "Valid_leap_year", Body = "Assert.True(Year.IsLeap(1996));" }, + new TestMethod { MethodName = "Invalid_leap_year", Body = "Assert.False(Year.IsLeap(1997));" } + } + }; + + const string testClassTemplate = +@" +using Xunit; + +public class {ClassName}Test +{ +{TestMethods} +} +"; + + const string testMethodTemplate = +@" + [Fact{Skip}] + public void {TestName}() + { + {TestBody} + } +"; + + var x1 = new + { + ClassName = testClass.ClassName, + TestMethods = + from testMethod in testClass.TestMethods + select new { MethodName = testMethod.MethodName, testMethod.Body } + }; + + var y = TemplateRenderer.Render("TestClass", x1); + + System.Console.WriteLine(y); + + //var actualTestTemplate = testClassTemplate + // .Replace("{ClassName}", testClass.ClassName) + // .Replace("{TestMethods}", string.Join("", testClass.TestMethods.Select((testMethod, index) => + // testMethodTemplate + // .Replace("{TestName}", testMethod.MethodName) + // .Replace("{TestBody}", testMethod.Body) + // .Replace("{Skip}", index == 0 ? "" : "(Skip = \"Remove to run test\")")))); + + + //System.Console.WriteLine(actualTestTemplate); } - protected abstract void Generate(CanonicalData canonicalData); + protected abstract string Generate(CanonicalData canonicalData); } + + public class LeapExerciseGenerator : Generator { public LeapExerciseGenerator() : base("leap") { } - protected override void Generate(CanonicalData canonicalData) + protected override string Generate(CanonicalData canonicalData) { - throw new NotImplementedException(); + return null; } } } \ No newline at end of file diff --git a/generators/Templates/TemplateReader.cs b/generators/Templates/TemplateReader.cs new file mode 100644 index 0000000000..8420c2af1a --- /dev/null +++ b/generators/Templates/TemplateReader.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Reflection; + +namespace Generators.Templates +{ + public static class TemplateReader + { + private static readonly Assembly assembly = Assembly.GetEntryAssembly(); + + public static string Read(string templateFileName) + { + using (var stream = assembly.GetManifestResourceStream(GetTemplateFileResourceName(templateFileName))) + using (var streamReader = new StreamReader(stream)) + return streamReader.ReadToEnd(); + } + + private static string GetTemplateFileResourceName(string templateFileName) + => $"{typeof(TemplateReader).Namespace}.{templateFileName}.liquid"; + } +} diff --git a/generators/Templates/TemplateRenderer.cs b/generators/Templates/TemplateRenderer.cs new file mode 100644 index 0000000000..d91a4a43d5 --- /dev/null +++ b/generators/Templates/TemplateRenderer.cs @@ -0,0 +1,13 @@ +using DotLiquid; + +namespace Generators.Templates +{ + public static class TemplateRenderer + { + public static string Render(string templateFileName, object templateModel) + { + var template = Template.Parse(TemplateReader.Read(templateFileName)); + return template.Render(Hash.FromAnonymousObject(templateModel)); + } + } +} diff --git a/generators/Templates/TestClass.cs b/generators/Templates/TestClass.cs new file mode 100644 index 0000000000..b0c9eef1ff --- /dev/null +++ b/generators/Templates/TestClass.cs @@ -0,0 +1,8 @@ +namespace Generators.Templates +{ + public class TestClass + { + public string ClassName { get; set; } + public TestMethod[] TestMethods { get; set; } + } +} diff --git a/generators/Templates/TestClass.liquid b/generators/Templates/TestClass.liquid new file mode 100644 index 0000000000..cadd4ec876 --- /dev/null +++ b/generators/Templates/TestClass.liquid @@ -0,0 +1,12 @@ +using Xunit; + +public class {{ ClassName }} +{ +{% for testMethod in TestMethods -%} + {% if forloop.first %}[Fact]{% else %} + [Fact(Skip = "Remove to run test")]{% endif %} + public void {{ testMethod.MethodName }} + { + } +{% endfor -%} +} \ No newline at end of file diff --git a/generators/Templates/TestMethod.cs b/generators/Templates/TestMethod.cs new file mode 100644 index 0000000000..c7197513a4 --- /dev/null +++ b/generators/Templates/TestMethod.cs @@ -0,0 +1,8 @@ +namespace Generators.Templates +{ + public class TestMethod + { + public string MethodName { get; set; } + public string Body { get; set; } + } +} diff --git a/generators/Templates/TestMethod.liquid b/generators/Templates/TestMethod.liquid new file mode 100644 index 0000000000..de0a7687df --- /dev/null +++ b/generators/Templates/TestMethod.liquid @@ -0,0 +1,4 @@ +[Fact] +public void {{ MethodName }} +{ +} \ No newline at end of file From 4ff0336d0ba70bab9194d7d9014927d47d2f034e Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 13 Mar 2017 08:40:54 +0100 Subject: [PATCH 05/11] Remove dotliquid and allow access to all field in test case --- generators/CanonicalDataCase.cs | 4 ++ generators/CanonicalDataCaseJsonConverter.cs | 32 ++++++++++++ generators/CanonicalDataCasesJsonConverter.cs | 3 +- generators/Generators.csproj | 5 -- generators/Program.cs | 49 ++++++------------- generators/Templates/TemplateReader.cs | 20 -------- generators/Templates/TemplateRenderer.cs | 13 ----- generators/Templates/TestClass.liquid | 12 ----- generators/Templates/TestMethod.liquid | 4 -- generators/{Templates => }/TestClass.cs | 2 +- generators/TestClassRenderer.cs | 41 ++++++++++++++++ generators/{Templates => }/TestMethod.cs | 2 +- 12 files changed, 95 insertions(+), 92 deletions(-) create mode 100644 generators/CanonicalDataCaseJsonConverter.cs delete mode 100644 generators/Templates/TemplateReader.cs delete mode 100644 generators/Templates/TemplateRenderer.cs delete mode 100644 generators/Templates/TestClass.liquid delete mode 100644 generators/Templates/TestMethod.liquid rename generators/{Templates => }/TestClass.cs (81%) create mode 100644 generators/TestClassRenderer.cs rename generators/{Templates => }/TestMethod.cs (79%) diff --git a/generators/CanonicalDataCase.cs b/generators/CanonicalDataCase.cs index 1acf50d813..d38027b718 100644 --- a/generators/CanonicalDataCase.cs +++ b/generators/CanonicalDataCase.cs @@ -1,8 +1,10 @@ using Newtonsoft.Json; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Generators { + [JsonConverter(typeof(CanonicalDataCaseJsonConverter))] public class CanonicalDataCase { [Required] @@ -14,5 +16,7 @@ public class CanonicalDataCase public string[] Comments { get; set; } public object Expected { get; set; } + + public IDictionary Data { get; set; } } } \ No newline at end of file diff --git a/generators/CanonicalDataCaseJsonConverter.cs b/generators/CanonicalDataCaseJsonConverter.cs new file mode 100644 index 0000000000..7fa7b31600 --- /dev/null +++ b/generators/CanonicalDataCaseJsonConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Generators +{ + public class CanonicalDataCaseJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(CanonicalDataCase) == objectType; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jToken = JToken.ReadFrom(reader); + + var canonicalDataCase = new CanonicalDataCase(); + serializer.Populate(new JTokenReader(jToken), canonicalDataCase); + + canonicalDataCase.Data = jToken.ToObject>(); + + return canonicalDataCase; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/generators/CanonicalDataCasesJsonConverter.cs b/generators/CanonicalDataCasesJsonConverter.cs index bcb1c8734e..05b0fc7596 100644 --- a/generators/CanonicalDataCasesJsonConverter.cs +++ b/generators/CanonicalDataCasesJsonConverter.cs @@ -3,6 +3,7 @@ using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Linq; namespace Generators { @@ -16,7 +17,7 @@ public override bool CanConvert(Type objectType) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var casesToken = JToken.ReadFrom(reader); - var caseTokens = casesToken.SelectTokens("$..*[?(@.property)]"); + var caseTokens = casesToken.SelectTokens("$..*[?(@.property)]").ToArray(); return new JArray(caseTokens).ToObject(objectType); } diff --git a/generators/Generators.csproj b/generators/Generators.csproj index 9bb2e5c2cb..5e7a6bf2cf 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -2,13 +2,8 @@ Exe netcoreapp1.1 - true - - - - \ No newline at end of file diff --git a/generators/Program.cs b/generators/Program.cs index b6200cbdb9..f89020511b 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -1,5 +1,4 @@ -using Generators.Templates; -using System.Linq; +using System.Linq; namespace Generators { @@ -38,25 +37,6 @@ public void Generate() } }; - const string testClassTemplate = -@" -using Xunit; - -public class {ClassName}Test -{ -{TestMethods} -} -"; - - const string testMethodTemplate = -@" - [Fact{Skip}] - public void {TestName}() - { - {TestBody} - } -"; - var x1 = new { ClassName = testClass.ClassName, @@ -65,26 +45,13 @@ from testMethod in testClass.TestMethods select new { MethodName = testMethod.MethodName, testMethod.Body } }; - var y = TemplateRenderer.Render("TestClass", x1); + var y = TestClassRenderer.Render(testClass); System.Console.WriteLine(y); - - //var actualTestTemplate = testClassTemplate - // .Replace("{ClassName}", testClass.ClassName) - // .Replace("{TestMethods}", string.Join("", testClass.TestMethods.Select((testMethod, index) => - // testMethodTemplate - // .Replace("{TestName}", testMethod.MethodName) - // .Replace("{TestBody}", testMethod.Body) - // .Replace("{Skip}", index == 0 ? "" : "(Skip = \"Remove to run test\")")))); - - - //System.Console.WriteLine(actualTestTemplate); } protected abstract string Generate(CanonicalData canonicalData); } - - public class LeapExerciseGenerator : Generator { @@ -97,4 +64,16 @@ protected override string Generate(CanonicalData canonicalData) return null; } } + + public class PigLatinExerciseGenerator : Generator + { + public PigLatinExerciseGenerator() : base("pig-latin") + { + } + + protected override string Generate(CanonicalData canonicalData) + { + return null; + } + } } \ No newline at end of file diff --git a/generators/Templates/TemplateReader.cs b/generators/Templates/TemplateReader.cs deleted file mode 100644 index 8420c2af1a..0000000000 --- a/generators/Templates/TemplateReader.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Reflection; - -namespace Generators.Templates -{ - public static class TemplateReader - { - private static readonly Assembly assembly = Assembly.GetEntryAssembly(); - - public static string Read(string templateFileName) - { - using (var stream = assembly.GetManifestResourceStream(GetTemplateFileResourceName(templateFileName))) - using (var streamReader = new StreamReader(stream)) - return streamReader.ReadToEnd(); - } - - private static string GetTemplateFileResourceName(string templateFileName) - => $"{typeof(TemplateReader).Namespace}.{templateFileName}.liquid"; - } -} diff --git a/generators/Templates/TemplateRenderer.cs b/generators/Templates/TemplateRenderer.cs deleted file mode 100644 index d91a4a43d5..0000000000 --- a/generators/Templates/TemplateRenderer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using DotLiquid; - -namespace Generators.Templates -{ - public static class TemplateRenderer - { - public static string Render(string templateFileName, object templateModel) - { - var template = Template.Parse(TemplateReader.Read(templateFileName)); - return template.Render(Hash.FromAnonymousObject(templateModel)); - } - } -} diff --git a/generators/Templates/TestClass.liquid b/generators/Templates/TestClass.liquid deleted file mode 100644 index cadd4ec876..0000000000 --- a/generators/Templates/TestClass.liquid +++ /dev/null @@ -1,12 +0,0 @@ -using Xunit; - -public class {{ ClassName }} -{ -{% for testMethod in TestMethods -%} - {% if forloop.first %}[Fact]{% else %} - [Fact(Skip = "Remove to run test")]{% endif %} - public void {{ testMethod.MethodName }} - { - } -{% endfor -%} -} \ No newline at end of file diff --git a/generators/Templates/TestMethod.liquid b/generators/Templates/TestMethod.liquid deleted file mode 100644 index de0a7687df..0000000000 --- a/generators/Templates/TestMethod.liquid +++ /dev/null @@ -1,4 +0,0 @@ -[Fact] -public void {{ MethodName }} -{ -} \ No newline at end of file diff --git a/generators/Templates/TestClass.cs b/generators/TestClass.cs similarity index 81% rename from generators/Templates/TestClass.cs rename to generators/TestClass.cs index b0c9eef1ff..ca0d39ffd3 100644 --- a/generators/Templates/TestClass.cs +++ b/generators/TestClass.cs @@ -1,4 +1,4 @@ -namespace Generators.Templates +namespace Generators { public class TestClass { diff --git a/generators/TestClassRenderer.cs b/generators/TestClassRenderer.cs new file mode 100644 index 0000000000..8c1a0d140f --- /dev/null +++ b/generators/TestClassRenderer.cs @@ -0,0 +1,41 @@ +using System.Linq; + +namespace Generators +{ + public static class TestClassRenderer + { + public static string Render(TestClass testClass) + { + const string testClassTemplate = +@" +using Xunit; + +public class {ClassName}Test +{ +{TestMethods} +} +"; + + const string testMethodTemplate = +@" + [Fact{Skip}] + public void {TestName}() + { + {TestBody} + } +"; + + + var actualTestTemplate = testClassTemplate + .Replace("{ClassName}", testClass.ClassName) + .Replace("{TestMethods}", string.Join("", testClass.TestMethods.Select((testMethod, index) => + testMethodTemplate + .Replace("{TestName}", testMethod.MethodName) + .Replace("{TestBody}", testMethod.Body) + .Replace("{Skip}", index == 0 ? "" : "(Skip = \"Remove to run test\")")))); + + return actualTestTemplate; + + } + } +} diff --git a/generators/Templates/TestMethod.cs b/generators/TestMethod.cs similarity index 79% rename from generators/Templates/TestMethod.cs rename to generators/TestMethod.cs index c7197513a4..abe79326e7 100644 --- a/generators/Templates/TestMethod.cs +++ b/generators/TestMethod.cs @@ -1,4 +1,4 @@ -namespace Generators.Templates +namespace Generators { public class TestMethod { From f41031882dc662c93a9ab59a1eaf6cf67722e5c9 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 13 Mar 2017 09:28:04 +0100 Subject: [PATCH 06/11] Lots of cleaning up. Almost working first exercise generator --- generators/CanonicalDataCase.cs | 2 + generators/CanonicalDataParser.cs | 8 +-- generators/ExerciseCollection.cs | 23 ++++++++ generators/Exercises/Exercise.cs | 14 +++++ generators/Exercises/LeapExercise.cs | 34 ++++++++++++ generators/GeneratorCollection.cs | 22 -------- generators/Generators.csproj | 1 + generators/Program.cs | 79 ++++++++-------------------- generators/TestClassRenderer.cs | 30 ++--------- generators/TestMethod.cs | 1 + generators/TestMethodRenderer.cs | 18 +++++++ 11 files changed, 123 insertions(+), 109 deletions(-) create mode 100644 generators/ExerciseCollection.cs create mode 100644 generators/Exercises/Exercise.cs create mode 100644 generators/Exercises/LeapExercise.cs delete mode 100644 generators/GeneratorCollection.cs create mode 100644 generators/TestMethodRenderer.cs diff --git a/generators/CanonicalDataCase.cs b/generators/CanonicalDataCase.cs index d38027b718..7ece6aaef7 100644 --- a/generators/CanonicalDataCase.cs +++ b/generators/CanonicalDataCase.cs @@ -15,6 +15,8 @@ public class CanonicalDataCase public string[] Comments { get; set; } + public object Input { get; set; } + public object Expected { get; set; } public IDictionary Data { get; set; } diff --git a/generators/CanonicalDataParser.cs b/generators/CanonicalDataParser.cs index 4e227c64e6..130d505368 100644 --- a/generators/CanonicalDataParser.cs +++ b/generators/CanonicalDataParser.cs @@ -5,11 +5,11 @@ namespace Generators { - public class CanonicalDataParser + public static class CanonicalDataParser { - private readonly HttpClient httpClient = new HttpClient(); + private static readonly HttpClient httpClient = new HttpClient(); - public CanonicalData Parse(string exercise) + public static CanonicalData Parse(string exercise) { var canonicalDataJson = DownloadCanonicalDataJson(exercise); var canonicalData = JsonConvert.DeserializeObject(canonicalDataJson); @@ -19,7 +19,7 @@ public CanonicalData Parse(string exercise) return canonicalData; } - private string DownloadCanonicalDataJson(string exercise) + private static string DownloadCanonicalDataJson(string exercise) => httpClient.GetStringAsync(GetCanonicalDataUrl(exercise)).GetAwaiter().GetResult(); private static Uri GetCanonicalDataUrl(string exercise) diff --git a/generators/ExerciseCollection.cs b/generators/ExerciseCollection.cs new file mode 100644 index 0000000000..ded5636a85 --- /dev/null +++ b/generators/ExerciseCollection.cs @@ -0,0 +1,23 @@ +using Generators.Exercises; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Generators +{ + public class ExerciseCollection : IEnumerable + { + private readonly IEnumerable generators = GetDefinedGenerators(); + + public IEnumerator GetEnumerator() => generators.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private static IEnumerable GetDefinedGenerators() => + from type in Assembly.GetEntryAssembly().GetTypes() + where typeof(Exercise).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract + select (Exercise)Activator.CreateInstance(type); + } +} \ No newline at end of file diff --git a/generators/Exercises/Exercise.cs b/generators/Exercises/Exercise.cs new file mode 100644 index 0000000000..280a7a9c71 --- /dev/null +++ b/generators/Exercises/Exercise.cs @@ -0,0 +1,14 @@ +namespace Generators.Exercises +{ + public abstract class Exercise + { + public Exercise(string name) + { + Name = name; + } + + public string Name { get; } + + public abstract TestClass CreateTestClass(CanonicalData canonicalData); + } +} \ No newline at end of file diff --git a/generators/Exercises/LeapExercise.cs b/generators/Exercises/LeapExercise.cs new file mode 100644 index 0000000000..0986a2886c --- /dev/null +++ b/generators/Exercises/LeapExercise.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Humanizer; + +namespace Generators.Exercises +{ + public class LeapExercise : Exercise + { + public LeapExercise() : base("leap") + { + } + + public override TestClass CreateTestClass(CanonicalData canonicalData) + { + return new TestClass + { + ClassName = "Leap", + TestMethods = canonicalData.Cases.Select(CreateTestMethod).ToArray() + }; + } + + private static TestMethod CreateTestMethod(CanonicalDataCase canonicalDataCase) + { + var year = Convert.ToInt32(canonicalDataCase.Input); + var isTrue = Convert.ToBoolean(canonicalDataCase.Expected); + + return new TestMethod + { + MethodName = canonicalDataCase.Description.Underscore(), + Body = $"Assert.{isTrue}(Year.IsLeap({year}));" + }; + } + } +} \ No newline at end of file diff --git a/generators/GeneratorCollection.cs b/generators/GeneratorCollection.cs deleted file mode 100644 index e34688d5ae..0000000000 --- a/generators/GeneratorCollection.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Generators -{ - public class GeneratorCollection : IEnumerable - { - private readonly IEnumerable generators = GetDefinedGenerators(); - - public IEnumerator GetEnumerator() => generators.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private static IEnumerable GetDefinedGenerators() => - from type in Assembly.GetEntryAssembly().GetTypes() - where typeof(Generator).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract - select (Generator)Activator.CreateInstance(type); - } -} \ No newline at end of file diff --git a/generators/Generators.csproj b/generators/Generators.csproj index 5e7a6bf2cf..ba92a825da 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -4,6 +4,7 @@ netcoreapp1.1 + \ No newline at end of file diff --git a/generators/Program.cs b/generators/Program.cs index f89020511b..1ae7afbf3e 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -1,4 +1,5 @@ -using System.Linq; +using Generators.Exercises; +using System.IO; namespace Generators { @@ -6,74 +7,36 @@ public static class Program { public static void Main() { - foreach (var generator in new GeneratorCollection()) - generator.Generate(); + foreach (var exercise in new ExerciseCollection()) + TestFileGenerator.Generate(exercise); } } - public abstract class Generator - { - private static readonly CanonicalDataParser CanonicalDataParser = new CanonicalDataParser(); - //private static readonly TemplateParser TemplateParser = new TemplateParser(); - - public Generator(string exercise) - { - Exercise = exercise; - } - - public string Exercise { get; } - - public void Generate() + public static class TestFileGenerator + { + public static void Generate(Exercise exercise) { - Generate(CanonicalDataParser.Parse(Exercise)); - - var testClass = new TestClass - { - ClassName = "Leap", - TestMethods = new[] - { - new TestMethod { MethodName = "Valid_leap_year", Body = "Assert.True(Year.IsLeap(1996));" }, - new TestMethod { MethodName = "Invalid_leap_year", Body = "Assert.False(Year.IsLeap(1997));" } - } - }; - - var x1 = new - { - ClassName = testClass.ClassName, - TestMethods = - from testMethod in testClass.TestMethods - select new { MethodName = testMethod.MethodName, testMethod.Body } - }; + var testClassContents = GenerateTestClassContents(exercise); + + System.Console.WriteLine(testClassContents); - var y = TestClassRenderer.Render(testClass); - - System.Console.WriteLine(y); + SaveTestClassContentsToFile(TestFilePath(exercise), testClassContents); } - protected abstract string Generate(CanonicalData canonicalData); - } - - public class LeapExerciseGenerator : Generator - { - public LeapExerciseGenerator() : base("leap") + private static string GenerateTestClassContents(Exercise exercise) { + var canonicalData = CanonicalDataParser.Parse(exercise.Name); + var testClass = exercise.CreateTestClass(canonicalData); + return TestClassRenderer.Render(testClass); } - protected override string Generate(CanonicalData canonicalData) - { - return null; - } - } + private static void SaveTestClassContentsToFile(string testClassFilePath, string testClassContents) => + File.WriteAllText(testClassFilePath, testClassContents); - public class PigLatinExerciseGenerator : Generator - { - public PigLatinExerciseGenerator() : base("pig-latin") - { - } + private static string TestFilePath(Exercise exercise) => + Path.Combine(@"d:\", TestFileName(exercise)); - protected override string Generate(CanonicalData canonicalData) - { - return null; - } + private static string TestFileName(Exercise exercise) => + $"{exercise.Name}Test.cs"; } } \ No newline at end of file diff --git a/generators/TestClassRenderer.cs b/generators/TestClassRenderer.cs index 8c1a0d140f..af1abb0e8a 100644 --- a/generators/TestClassRenderer.cs +++ b/generators/TestClassRenderer.cs @@ -4,11 +4,8 @@ namespace Generators { public static class TestClassRenderer { - public static string Render(TestClass testClass) - { - const string testClassTemplate = -@" -using Xunit; + private const string testClassTemplate = +@"using Xunit; public class {ClassName}Test { @@ -16,26 +13,9 @@ public class {ClassName}Test } "; - const string testMethodTemplate = -@" - [Fact{Skip}] - public void {TestName}() - { - {TestBody} - } -"; - - - var actualTestTemplate = testClassTemplate + public static string Render(TestClass testClass) => + testClassTemplate .Replace("{ClassName}", testClass.ClassName) - .Replace("{TestMethods}", string.Join("", testClass.TestMethods.Select((testMethod, index) => - testMethodTemplate - .Replace("{TestName}", testMethod.MethodName) - .Replace("{TestBody}", testMethod.Body) - .Replace("{Skip}", index == 0 ? "" : "(Skip = \"Remove to run test\")")))); - - return actualTestTemplate; - - } + .Replace("{TestMethods}", string.Join("\n\n", testClass.TestMethods.Select(TestMethodRenderer.Render))); } } diff --git a/generators/TestMethod.cs b/generators/TestMethod.cs index abe79326e7..3e8b67ffc4 100644 --- a/generators/TestMethod.cs +++ b/generators/TestMethod.cs @@ -2,6 +2,7 @@ { public class TestMethod { + public int Index { get; set; } public string MethodName { get; set; } public string Body { get; set; } } diff --git a/generators/TestMethodRenderer.cs b/generators/TestMethodRenderer.cs new file mode 100644 index 0000000000..79e118d8c1 --- /dev/null +++ b/generators/TestMethodRenderer.cs @@ -0,0 +1,18 @@ +namespace Generators +{ + public static class TestMethodRenderer + { + private const string testMethodTemplate = +@" [Fact{Skip}] + public void {Name}() + { + {Body} + }"; + + public static string Render(TestMethod testMethod) => + testMethodTemplate + .Replace("{Name}", testMethod.MethodName) + .Replace("{Body}", testMethod.Body) + .Replace("{Skip}", testMethod.Index == 0 ? "" : "(Skip = \"Remove to run test\")"); + } +} From 8332fa3b97ccc13bf35eb3a2f2e18410b39b3196 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 13 Mar 2017 16:05:46 +0100 Subject: [PATCH 07/11] Fine-tune rendering of test method name --- exercises/leap/LeapTest.cs | 16 ++++++++-------- generators/Exercises/Exercise.cs | 2 +- generators/Exercises/LeapExercise.cs | 5 +++-- generators/Program.cs | 9 ++------- generators/TestClassRenderer.cs | 7 +++---- generators/TestMethodNameTransformer.cs | 11 +++++++++++ generators/TestMethodRenderer.cs | 4 ++-- generators/To.cs | 7 +++++++ 8 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 generators/TestMethodNameTransformer.cs create mode 100644 generators/To.cs diff --git a/exercises/leap/LeapTest.cs b/exercises/leap/LeapTest.cs index 937717e055..a3b935a1bd 100644 --- a/exercises/leap/LeapTest.cs +++ b/exercises/leap/LeapTest.cs @@ -3,26 +3,26 @@ public class LeapTest { [Fact] - public void Valid_leap_year() + public void Year_not_divisible_by_4_is_common_year() { - Assert.True(Year.IsLeap(1996)); + Assert.False(Year.IsLeap(2015)); } [Fact(Skip = "Remove to run test")] - public void Invalid_leap_year() + public void Year_divisible_by_4_not_divisible_by_100_is_leap_year() { - Assert.False(Year.IsLeap(1997)); + Assert.True(Year.IsLeap(2016)); } [Fact(Skip = "Remove to run test")] - public void Turn_of_the_20th_century_is_not_a_leap_year() + public void Year_divisible_by_100_not_divisible_by_400_is_common_year() { - Assert.False(Year.IsLeap(1900)); + Assert.False(Year.IsLeap(2100)); } [Fact(Skip = "Remove to run test")] - public void Turn_of_the_25th_century_is_a_leap_year() + public void Year_divisible_by_400_is_leap_year() { - Assert.True(Year.IsLeap(2400)); + Assert.True(Year.IsLeap(2000)); } } \ No newline at end of file diff --git a/generators/Exercises/Exercise.cs b/generators/Exercises/Exercise.cs index 280a7a9c71..6effa58d07 100644 --- a/generators/Exercises/Exercise.cs +++ b/generators/Exercises/Exercise.cs @@ -2,7 +2,7 @@ { public abstract class Exercise { - public Exercise(string name) + protected Exercise(string name) { Name = name; } diff --git a/generators/Exercises/LeapExercise.cs b/generators/Exercises/LeapExercise.cs index 0986a2886c..d1b8b58478 100644 --- a/generators/Exercises/LeapExercise.cs +++ b/generators/Exercises/LeapExercise.cs @@ -19,14 +19,15 @@ public override TestClass CreateTestClass(CanonicalData canonicalData) }; } - private static TestMethod CreateTestMethod(CanonicalDataCase canonicalDataCase) + private static TestMethod CreateTestMethod(CanonicalDataCase canonicalDataCase, int index) { var year = Convert.ToInt32(canonicalDataCase.Input); var isTrue = Convert.ToBoolean(canonicalDataCase.Expected); return new TestMethod { - MethodName = canonicalDataCase.Description.Underscore(), + Index = index, + MethodName = canonicalDataCase.Description.Replace(":", " is").Transform(To.TestMethodName), Body = $"Assert.{isTrue}(Year.IsLeap({year}));" }; } diff --git a/generators/Program.cs b/generators/Program.cs index 1ae7afbf3e..99e80637eb 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -17,9 +17,6 @@ public static class TestFileGenerator public static void Generate(Exercise exercise) { var testClassContents = GenerateTestClassContents(exercise); - - System.Console.WriteLine(testClassContents); - SaveTestClassContentsToFile(TestFilePath(exercise), testClassContents); } @@ -33,10 +30,8 @@ private static string GenerateTestClassContents(Exercise exercise) private static void SaveTestClassContentsToFile(string testClassFilePath, string testClassContents) => File.WriteAllText(testClassFilePath, testClassContents); - private static string TestFilePath(Exercise exercise) => - Path.Combine(@"d:\", TestFileName(exercise)); + private static string TestFilePath(Exercise exercise) => Path.Combine("..", "exercises", exercise.Name, TestFileName(exercise)); - private static string TestFileName(Exercise exercise) => - $"{exercise.Name}Test.cs"; + private static string TestFileName(Exercise exercise) => $"{exercise.Name}Test.cs"; } } \ No newline at end of file diff --git a/generators/TestClassRenderer.cs b/generators/TestClassRenderer.cs index af1abb0e8a..63e03ba2fe 100644 --- a/generators/TestClassRenderer.cs +++ b/generators/TestClassRenderer.cs @@ -4,17 +4,16 @@ namespace Generators { public static class TestClassRenderer { - private const string testClassTemplate = + private const string TestClassTemplate = @"using Xunit; public class {ClassName}Test { {TestMethods} -} -"; +}"; public static string Render(TestClass testClass) => - testClassTemplate + TestClassTemplate .Replace("{ClassName}", testClass.ClassName) .Replace("{TestMethods}", string.Join("\n\n", testClass.TestMethods.Select(TestMethodRenderer.Render))); } diff --git a/generators/TestMethodNameTransformer.cs b/generators/TestMethodNameTransformer.cs new file mode 100644 index 0000000000..15115f6e82 --- /dev/null +++ b/generators/TestMethodNameTransformer.cs @@ -0,0 +1,11 @@ +using System.Text.RegularExpressions; +using Humanizer; + +namespace Generators +{ + public class TestMethodNameTransformer : IStringTransformer + { + public string Transform(string input) + => Regex.Replace(input, @"[^\w]+", "_", RegexOptions.Compiled).Underscore().Transform(Humanizer.To.TitleCase); + } +} \ No newline at end of file diff --git a/generators/TestMethodRenderer.cs b/generators/TestMethodRenderer.cs index 79e118d8c1..ce941e5920 100644 --- a/generators/TestMethodRenderer.cs +++ b/generators/TestMethodRenderer.cs @@ -2,7 +2,7 @@ { public static class TestMethodRenderer { - private const string testMethodTemplate = + private const string TestMethodTemplate = @" [Fact{Skip}] public void {Name}() { @@ -10,7 +10,7 @@ public static class TestMethodRenderer }"; public static string Render(TestMethod testMethod) => - testMethodTemplate + TestMethodTemplate .Replace("{Name}", testMethod.MethodName) .Replace("{Body}", testMethod.Body) .Replace("{Skip}", testMethod.Index == 0 ? "" : "(Skip = \"Remove to run test\")"); diff --git a/generators/To.cs b/generators/To.cs new file mode 100644 index 0000000000..8a459165cb --- /dev/null +++ b/generators/To.cs @@ -0,0 +1,7 @@ +namespace Generators +{ + public static class To + { + public static readonly TestMethodNameTransformer TestMethodName = new TestMethodNameTransformer(); + } +} \ No newline at end of file From 3bad10781137a599be59a5942f11e549886956dd Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 13 Mar 2017 16:14:14 +0100 Subject: [PATCH 08/11] Add logging and use correct test filename --- exercises/leap/LeapTest.cs | 16 ++++++------- generators/Exercises/LeapExercise.cs | 35 ---------------------------- generators/Generators.csproj | 2 ++ generators/Program.cs | 26 +++++++++++++++++++-- 4 files changed, 34 insertions(+), 45 deletions(-) delete mode 100644 generators/Exercises/LeapExercise.cs diff --git a/exercises/leap/LeapTest.cs b/exercises/leap/LeapTest.cs index a3b935a1bd..937717e055 100644 --- a/exercises/leap/LeapTest.cs +++ b/exercises/leap/LeapTest.cs @@ -3,26 +3,26 @@ public class LeapTest { [Fact] - public void Year_not_divisible_by_4_is_common_year() + public void Valid_leap_year() { - Assert.False(Year.IsLeap(2015)); + Assert.True(Year.IsLeap(1996)); } [Fact(Skip = "Remove to run test")] - public void Year_divisible_by_4_not_divisible_by_100_is_leap_year() + public void Invalid_leap_year() { - Assert.True(Year.IsLeap(2016)); + Assert.False(Year.IsLeap(1997)); } [Fact(Skip = "Remove to run test")] - public void Year_divisible_by_100_not_divisible_by_400_is_common_year() + public void Turn_of_the_20th_century_is_not_a_leap_year() { - Assert.False(Year.IsLeap(2100)); + Assert.False(Year.IsLeap(1900)); } [Fact(Skip = "Remove to run test")] - public void Year_divisible_by_400_is_leap_year() + public void Turn_of_the_25th_century_is_a_leap_year() { - Assert.True(Year.IsLeap(2000)); + Assert.True(Year.IsLeap(2400)); } } \ No newline at end of file diff --git a/generators/Exercises/LeapExercise.cs b/generators/Exercises/LeapExercise.cs deleted file mode 100644 index d1b8b58478..0000000000 --- a/generators/Exercises/LeapExercise.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Linq; -using Humanizer; - -namespace Generators.Exercises -{ - public class LeapExercise : Exercise - { - public LeapExercise() : base("leap") - { - } - - public override TestClass CreateTestClass(CanonicalData canonicalData) - { - return new TestClass - { - ClassName = "Leap", - TestMethods = canonicalData.Cases.Select(CreateTestMethod).ToArray() - }; - } - - private static TestMethod CreateTestMethod(CanonicalDataCase canonicalDataCase, int index) - { - var year = Convert.ToInt32(canonicalDataCase.Input); - var isTrue = Convert.ToBoolean(canonicalDataCase.Expected); - - return new TestMethod - { - Index = index, - MethodName = canonicalDataCase.Description.Replace(":", " is").Transform(To.TestMethodName), - Body = $"Assert.{isTrue}(Year.IsLeap({year}));" - }; - } - } -} \ No newline at end of file diff --git a/generators/Generators.csproj b/generators/Generators.csproj index ba92a825da..efab961ac0 100644 --- a/generators/Generators.csproj +++ b/generators/Generators.csproj @@ -6,5 +6,7 @@ + + \ No newline at end of file diff --git a/generators/Program.cs b/generators/Program.cs index 99e80637eb..888eb7c6bc 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -1,5 +1,7 @@ using Generators.Exercises; using System.IO; +using Humanizer; +using Serilog; namespace Generators { @@ -7,8 +9,25 @@ public static class Program { public static void Main() { + SetupLogger(); + GenerateAll(); + } + + private static void SetupLogger() + { + Log.Logger = new LoggerConfiguration() + .WriteTo.LiterateConsole() + .CreateLogger(); + } + + private static void GenerateAll() + { + Log.Information("Start generating tests..."); + foreach (var exercise in new ExerciseCollection()) TestFileGenerator.Generate(exercise); + + Log.Information("Finished generating tests for all supported exercises."); } } @@ -17,7 +36,10 @@ public static class TestFileGenerator public static void Generate(Exercise exercise) { var testClassContents = GenerateTestClassContents(exercise); - SaveTestClassContentsToFile(TestFilePath(exercise), testClassContents); + var testClassFilePath = TestFilePath(exercise); + + SaveTestClassContentsToFile(testClassFilePath, testClassContents); + Log.Information("Generated tests for {Exercise} exercise in {TestFile}.", exercise.Name, testClassFilePath); } private static string GenerateTestClassContents(Exercise exercise) @@ -32,6 +54,6 @@ private static void SaveTestClassContentsToFile(string testClassFilePath, string private static string TestFilePath(Exercise exercise) => Path.Combine("..", "exercises", exercise.Name, TestFileName(exercise)); - private static string TestFileName(Exercise exercise) => $"{exercise.Name}Test.cs"; + private static string TestFileName(Exercise exercise) => $"{exercise.Name.Transform(Humanizer.To.TitleCase)}Test.cs"; } } \ No newline at end of file From 96d8606fcf34cd9bc51ec31351d6189432cc9ed4 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 13 Mar 2017 16:28:36 +0100 Subject: [PATCH 09/11] Add generate scripts --- generators/generate.ps1 | 2 ++ generators/generate.sh | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 generators/generate.ps1 create mode 100644 generators/generate.sh diff --git a/generators/generate.ps1 b/generators/generate.ps1 new file mode 100644 index 0000000000..e43799710c --- /dev/null +++ b/generators/generate.ps1 @@ -0,0 +1,2 @@ +Invoke-Expression "dotnet restore" +Invoke-Expression "dotnet run" \ No newline at end of file diff --git a/generators/generate.sh b/generators/generate.sh new file mode 100644 index 0000000000..2f8a56bcd6 --- /dev/null +++ b/generators/generate.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +exec dotnet restore +exec dotnet run \ No newline at end of file From 15d42b238a22787ef548048eb0f37fd818f6df01 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 14 Mar 2017 18:00:41 +0100 Subject: [PATCH 10/11] Add support for specifying namespaces and before- and after test methods code --- generators/TestClass.cs | 9 +++++++-- generators/TestClassRenderer.cs | 24 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/generators/TestClass.cs b/generators/TestClass.cs index ca0d39ffd3..ee71e89a36 100644 --- a/generators/TestClass.cs +++ b/generators/TestClass.cs @@ -1,8 +1,13 @@ -namespace Generators +using System.Collections.Generic; + +namespace Generators { public class TestClass { + public ISet UsingNamespaces { get; set; } = new HashSet { "Xunit" }; public string ClassName { get; set; } + public string BeforeTestMethods { get; set; } public TestMethod[] TestMethods { get; set; } + public string AfterTestMethods { get; set; } } -} +} \ No newline at end of file diff --git a/generators/TestClassRenderer.cs b/generators/TestClassRenderer.cs index 63e03ba2fe..b289a80979 100644 --- a/generators/TestClassRenderer.cs +++ b/generators/TestClassRenderer.cs @@ -1,20 +1,36 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; namespace Generators { public static class TestClassRenderer { private const string TestClassTemplate = -@"using Xunit; +@"{UsingNamespaces} public class {ClassName}Test { -{TestMethods} +{Body} }"; public static string Render(TestClass testClass) => TestClassTemplate + .Replace("{UsingNamespaces}", RenderUsingNamespaces(testClass)) .Replace("{ClassName}", testClass.ClassName) - .Replace("{TestMethods}", string.Join("\n\n", testClass.TestMethods.Select(TestMethodRenderer.Render))); + .Replace("{Body}", RenderBody(testClass)); + + private static string RenderUsingNamespaces(TestClass testClass) => + string.Join("\n", testClass.UsingNamespaces.Select(usingNamespace => $"using {usingNamespace};")); + + private static string RenderBody(TestClass testClass) => + string.Join("\n\n", GetBodyParts(testClass)); + + private static IEnumerable GetBodyParts(TestClass testClass) => + from bodyPart in new [] { testClass.BeforeTestMethods, RenderTestMethods(testClass), testClass.AfterTestMethods } + where !string.IsNullOrWhiteSpace(bodyPart) + select bodyPart; + + private static string RenderTestMethods(TestClass testClass) => + string.Join("\n\n", testClass.TestMethods.Select(TestMethodRenderer.Render)); } } From 4b288c2ecc2b546b2c458e47866fb1c0dd83a52a Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 14 Mar 2017 19:25:48 +0100 Subject: [PATCH 11/11] Add generators to ignored directories in config.json --- config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index bf902cb430..2bcc3385d8 100644 --- a/config.json +++ b/config.json @@ -857,7 +857,8 @@ ], "ignored": [ "docs", - "img" + "img", + "generators" ], "foregone": [ "lens-person",