diff --git a/Generator.cs b/Generators/CS/MSTestGenerator.cs similarity index 62% rename from Generator.cs rename to Generators/CS/MSTestGenerator.cs index a2500cc..81d0084 100644 --- a/Generator.cs +++ b/Generators/CS/MSTestGenerator.cs @@ -6,28 +6,14 @@ using System.Text; using Tenrec.Components; -namespace Tenrec +namespace Tenrec.Generators.CS { - /// - /// Produces code files automatically, generating test code based on Grasshopper files. - /// - /// - /// This must be called once to produce the test files automatically, or every time you add or remove a or add or remove a file from the test folder. - /// - public class Generator + /// + public class MSTestGenerator : IGenerator { - /// - /// Generate a code file containing all the -based tests present in Grasshopper files. - /// - /// The folder containing the Grasshopper files. - /// The folder where to save the source code file. - /// The name of the resulting code file. - /// Language of the code file. Currently only supports "cs"(C#). - /// Testing framework. Currently only supports MSTest. - /// A log of the process. - public static string CreateAutoTestSourceFile(string[] ghTestFolders, - string outputFolder, string outputName = "TenrecGeneratedTests", - string language = "cs", string testFramework = "mstest") + /// + public string CreateAutoTestSourceFile + (string[] ghTestFolders, string outputFolder, string outputName) { var log = new StringBuilder(); var sb = new StringBuilder(); @@ -35,14 +21,10 @@ public static string CreateAutoTestSourceFile(string[] ghTestFolders, var fileName = string.Empty; try { - if (ghTestFolders == null && ghTestFolders.Length == 0) + if (ghTestFolders == null || ghTestFolders.Length == 0) throw new ArgumentNullException(nameof(outputFolder)); if (string.IsNullOrEmpty(outputFolder)) throw new ArgumentNullException(nameof(outputFolder)); - if (!language.Equals("cs")) - throw new NotImplementedException(nameof(language)); - if (!testFramework.Equals("mstest")) - throw new NotImplementedException(nameof(testFramework)); sb.AppendLine("using Microsoft.VisualStudio.TestTools.UnitTesting;"); sb.AppendLine(); @@ -55,7 +37,7 @@ public static string CreateAutoTestSourceFile(string[] ghTestFolders, { foreach (var file in files) { - if (OpenDocument(file, out GH_Document doc)) + if (Utils.IOHelper.OpenDocument(file, out GH_Document doc)) { var groups = new List(); foreach (var obj in doc.Objects) @@ -65,10 +47,10 @@ public static string CreateAutoTestSourceFile(string[] ghTestFolders, groups.Add(obj); } } - if (groups != null && groups.Any()) + if (groups.Any()) { sb.AppendLine(" [TestClass]"); - sb.AppendLine($" public class AutoTest_{CodeableNickname(doc.DisplayName)}"); + sb.AppendLine($" public class AutoTest_{Utils.StringHelper.CodeableNickname(doc.DisplayName)}"); sb.AppendLine(" {"); sb.AppendLine($" public string FilePath => @\"{doc.FilePath}\";"); sb.AppendLine(" private TestContext testContextInstance;"); @@ -76,7 +58,7 @@ public static string CreateAutoTestSourceFile(string[] ghTestFolders, foreach (var group in groups) { sb.AppendLine(" [TestMethod]"); - sb.AppendLine($" public void {CodeableNickname(group.NickName)}()"); + sb.AppendLine($" public void {Utils.StringHelper.CodeableNickname(group.NickName)}()"); sb.AppendLine(" {"); sb.AppendLine($" Tenrec.Runner.Initialize(TestContext);"); sb.AppendLine($" Tenrec.Runner.RunTenrecGroup(FilePath, new System.Guid(\"{group.InstanceGuid}\"), TestContext);"); @@ -111,23 +93,5 @@ public static string CreateAutoTestSourceFile(string[] ghTestFolders, return log.ToString(); } - - private static string CodeableNickname(string nickname) - { - return nickname.Replace(" ", "_"); - } - - private static bool OpenDocument(string filePath, out GH_Document doc) - { - var io = new GH_DocumentIO(); - if (!io.Open(filePath)) - { - doc = null; - throw new Exception($"Failed to open file: {filePath}"); - } - doc = io.Document; - doc.Enabled = true; - return true; - } } } diff --git a/Generators/CS/XUnitGenerator.cs b/Generators/CS/XUnitGenerator.cs new file mode 100644 index 0000000..80e3a3e --- /dev/null +++ b/Generators/CS/XUnitGenerator.cs @@ -0,0 +1,112 @@ +using Grasshopper.Kernel; +using System; +using System.IO; +using System.Linq; +using System.Text; +using Tenrec.Components; +using Tenrec.Utils; + +namespace Tenrec.Generators.CS +{ + /// + public class XUnitGenerator : IGenerator + { + /// + public string CreateAutoTestSourceFile + (string[] ghTestFolders, string outputFolder, string outputName) + { + var log = new StringBuilder(); + var sb = new StringBuilder(); + var exits = false; + var fileName = string.Empty; + try + { + if (ghTestFolders == null || ghTestFolders.Length == 0) + throw new ArgumentNullException(nameof(outputFolder)); + if (string.IsNullOrEmpty(outputFolder)) + throw new ArgumentNullException(nameof(outputFolder)); + + sb.AppendLine("using Xunit;"); + sb.AppendLine("using Xunit.Abstractions;"); + sb.AppendLine(); + sb.AppendLine("namespace TenrecGeneratedTests"); + sb.AppendLine("{"); + foreach (var folder in ghTestFolders) + { + var files = Directory.EnumerateFiles(folder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".gh") || s.EndsWith(".ghx")); + if (!files.Any()) + { + log.Append($"{folder} does not contain any readable format (.gh|.ghx)"); + continue; + } + foreach (var file in files) + { + if (!IOHelper.OpenDocument(file, out GH_Document doc)) + { + log.Append($"Failed to open {file}."); + continue; + } + var groups = doc.Objects.Where(o => o.ComponentGuid == Group_UnitTest.ID).ToList(); + if (groups.Count == 0) + continue; + + #region Crearte_Fixture_Class + sb.AppendLine(StringHelper.IndexedString($"public class AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}_Fixture : GHFileFixture", 1)); + sb.AppendLine(StringHelper.IndexedString("{", 1)); + sb.AppendLine(StringHelper.IndexedString($"public AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}_Fixture()", 2)); + sb.AppendLine(StringHelper.IndexedString($" : base(@\"{file}\")", 3)); + sb.AppendLine(StringHelper.IndexedString("{", 2)); + sb.AppendLine(StringHelper.IndexedString("}", 2)); + sb.AppendLine(StringHelper.IndexedString("}", 1)); + #endregion + + sb.AppendLine(StringHelper.IndexedString($"public class AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}" + + $" : IClassFixture", 1)); + sb.AppendLine(StringHelper.IndexedString("{", 1)); + + sb.AppendLine(StringHelper.IndexedString($"private readonly AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}_Fixture fixture;", 2)); + sb.AppendLine(StringHelper.IndexedString("private readonly ITestOutputHelper context;", 2)); + + #region Test_Class_Constructor + sb.AppendLine(StringHelper.IndexedString($"public AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}" + + $" (AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}_Fixture fixture, " + + $"ITestOutputHelper context)", 2)); + sb.AppendLine(StringHelper.IndexedString("{", 2)); + sb.AppendLine(StringHelper.IndexedString("this.fixture = fixture;", 3)); + sb.AppendLine(StringHelper.IndexedString("this.context = context;", 3)); + sb.AppendLine(StringHelper.IndexedString("}", 2)); + #endregion + + foreach (var group in groups) + { + sb.AppendLine(StringHelper.IndexedString("[Fact]", 2)); + sb.AppendLine(StringHelper.IndexedString($"public void {StringHelper.CodeableNickname(group.NickName)}()", 2)); + sb.AppendLine(StringHelper.IndexedString("{", 2)); + sb.AppendLine(StringHelper.IndexedString($"fixture.RunGroup(fixture.Doc, new System.Guid(\"{group.InstanceGuid}\"), context);", 3)); + sb.AppendLine(StringHelper.IndexedString("}", 2)); + } + sb.AppendLine(StringHelper.IndexedString("}", 1)); + sb.AppendLine(); + doc.Dispose(); + } + } + sb.AppendLine("}"); + + fileName = Path.Combine(outputFolder, outputName + ".cs"); + exits = File.Exists(fileName); + File.WriteAllText(fileName, sb.ToString()); + } + catch (Exception e) + { + log.AppendLine($"EXCEPTION: {e}."); + } + + if (exits) + log.AppendLine($"File successfully overwritten."); + else + log.AppendLine($"File successfully created."); + + return log.ToString(); + } + } +} diff --git a/Generators/IGenerator.cs b/Generators/IGenerator.cs new file mode 100644 index 0000000..819d14c --- /dev/null +++ b/Generators/IGenerator.cs @@ -0,0 +1,21 @@ +namespace Tenrec.Generators +{ + /// + /// Produces code files automatically, generating test code based on Grasshopper files. + /// + /// + /// This must be called once to produce the test files automatically, or every time you add or remove a or add or remove a file from the test folder. + /// + public interface IGenerator + { + /// + /// Generate a code file containing all the -based tests present in Grasshopper files. + /// + /// The folder containing the Grasshopper files. + /// The folder where to save the source code file. + /// The name of the resulting code file. + /// A log of the process. + string CreateAutoTestSourceFile + (string[] ghTestFolders, string outputFolder, string outputName); + } +} diff --git a/Tenrec.csproj b/Tenrec.csproj index 3817348..9d8bc47 100644 --- a/Tenrec.csproj +++ b/Tenrec.csproj @@ -71,6 +71,9 @@ + + + True @@ -79,7 +82,6 @@ - @@ -90,6 +92,8 @@ UnitTestsSourceCodeGeneratorForm.cs + + diff --git a/Tenrec.sln b/Tenrec.sln index 46c3631..dee81bf 100644 --- a/Tenrec.sln +++ b/Tenrec.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.31229.75 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tenrec", "Tenrec.csproj", "{C0E5FFFB-45D6-41A1-9E5A-342E01327EB3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTestProjectTenrec", "..\SampleTestProjectTenrec\SampleTestProjectTenrec.csproj", "{C8CF27BE-2BBE-4AD2-A62A-BC55DC3A3C31}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,10 +15,6 @@ Global {C0E5FFFB-45D6-41A1-9E5A-342E01327EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU {C0E5FFFB-45D6-41A1-9E5A-342E01327EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C0E5FFFB-45D6-41A1-9E5A-342E01327EB3}.Release|Any CPU.Build.0 = Release|Any CPU - {C8CF27BE-2BBE-4AD2-A62A-BC55DC3A3C31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C8CF27BE-2BBE-4AD2-A62A-BC55DC3A3C31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C8CF27BE-2BBE-4AD2-A62A-BC55DC3A3C31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C8CF27BE-2BBE-4AD2-A62A-BC55DC3A3C31}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/UI/UnitTestsSourceCodeGeneratorForm.Designer.cs b/UI/UnitTestsSourceCodeGeneratorForm.Designer.cs index dd14374..eae5147 100644 --- a/UI/UnitTestsSourceCodeGeneratorForm.Designer.cs +++ b/UI/UnitTestsSourceCodeGeneratorForm.Designer.cs @@ -199,7 +199,7 @@ private void InitializeComponent() this.comboBoxFramework.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxFramework.FormattingEnabled = true; this.comboBoxFramework.Items.AddRange(new object[] { - "MSTest"}); + "MSTest", "XUnit"}); this.comboBoxFramework.Location = new System.Drawing.Point(3, 16); this.comboBoxFramework.Name = "comboBoxFramework"; this.comboBoxFramework.Size = new System.Drawing.Size(192, 21); diff --git a/UI/UnitTestsSourceCodeGeneratorForm.cs b/UI/UnitTestsSourceCodeGeneratorForm.cs index 8b5dc17..149a07b 100644 --- a/UI/UnitTestsSourceCodeGeneratorForm.cs +++ b/UI/UnitTestsSourceCodeGeneratorForm.cs @@ -1,6 +1,8 @@ using System; using System.Drawing; using System.Windows.Forms; +using Tenrec.Generators; +using Tenrec.Generators.CS; namespace Tenrec.UI { @@ -51,7 +53,7 @@ private bool CanGenerate(out string message) } else { - foreach(var folder in folders) + foreach (var folder in folders) { if (!System.IO.Directory.Exists(folder)) { @@ -59,7 +61,7 @@ private bool CanGenerate(out string message) return false; } } - } + } var outputFolder = GetOutputFolder(); if (string.IsNullOrEmpty(outputFolder)) { @@ -101,7 +103,7 @@ private void UpdateState() private void UnitTestsSourceCodeGeneratorForm_Load(object sender, EventArgs e) { var activeDoc = Grasshopper.Instances.ActiveCanvas?.Document; - if(activeDoc != null && !string.IsNullOrEmpty(activeDoc.FilePath)) + if (activeDoc != null && !string.IsNullOrEmpty(activeDoc.FilePath)) { textBoxFiles.Text = System.IO.Path.GetDirectoryName(activeDoc.FilePath); } @@ -122,8 +124,16 @@ private void buttonGenerate_Click(object sender, EventArgs e) var outputFolder = GetOutputFolder(); var outputName = GetOutputName(); var language = GetLanguage(); - var framework = GetFramework(); - textBoxLog.Text = Generator.CreateAutoTestSourceFile(folderFiles, outputFolder, outputName, language, framework); + var framework = GetFramework(); + IGenerator generator = null; + if (language == "cs") + { + if (framework == "mstest") + generator = new MSTestGenerator(); + else if (framework == "xunit") + generator = new XUnitGenerator(); + } + textBoxLog.Text = generator.CreateAutoTestSourceFile(folderFiles, outputFolder, outputName); if (textBoxLog.Text.Contains("successfully")) textBoxLog.ForeColor = Grasshopper.GUI.GH_GraphicsUtil.BlendColour(Color.Green, Color.Black, 0.5); else diff --git a/Utils/IOHelper.cs b/Utils/IOHelper.cs new file mode 100644 index 0000000..2e87db8 --- /dev/null +++ b/Utils/IOHelper.cs @@ -0,0 +1,24 @@ +using Grasshopper.Kernel; +using System; + +namespace Tenrec.Utils +{ + /// + /// Contains helper classes for input/output operations used in Tenrec project. + /// + public static class IOHelper + { + public static bool OpenDocument(string filePath, out GH_Document doc) + { + var io = new GH_DocumentIO(); + if (!io.Open(filePath)) + { + doc = null; + throw new Exception($"Failed to open file: {filePath}"); + } + doc = io.Document; + doc.Enabled = true; + return true; + } + } +} diff --git a/Utils/StringHelper.cs b/Utils/StringHelper.cs new file mode 100644 index 0000000..bbc877c --- /dev/null +++ b/Utils/StringHelper.cs @@ -0,0 +1,28 @@ +namespace Tenrec.Utils +{ + /// + /// Contains helper classes for string manipulation required in Tenrec project. + /// + public static class StringHelper + { + /// + /// Replaces all spaces in the string with '_' + /// + /// source string + /// source string without any spaces (all spaces replaced with '_') + public static string CodeableNickname(string nickname) + { + return nickname.Replace(" ", "_"); + } + /// + /// Adds identation based on provided index. index can start from 0 (no identation). + /// + /// source string + /// level of identation desired. + /// An identated string + public static string IndexedString(string str, int index) + { + return string.Concat(System.Linq.Enumerable.Repeat(" ", index)) + str; + } + } +}