Skip to content
58 changes: 11 additions & 47 deletions Generator.cs → Generators/CS/MSTestGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,25 @@
using System.Text;
using Tenrec.Components;

namespace Tenrec
namespace Tenrec.Generators.CS
{
/// <summary>
/// Produces code files automatically, generating test code based on Grasshopper files.
/// </summary>
/// <remarks>
/// This must be called once to produce the test files automatically, or every time you add or remove a <see cref="Group_UnitTest"/> or add or remove a file from the test folder.
/// </remarks>
public class Generator
/// <inheritdoc/>
public class MSTestGenerator : IGenerator
{
/// <summary>
/// Generate a code file containing all the <see cref="Group_UnitTest"/>-based tests present in Grasshopper files.
/// </summary>
/// <param name="ghTestFolders">The folder containing the Grasshopper files.</param>
/// <param name="outputFolder">The folder where to save the source code file.</param>
/// <param name="outputName">The name of the resulting code file.</param>
/// <param name="language">Language of the code file. Currently only supports "cs"(C#).</param>
/// <param name="testFramework">Testing framework. Currently only supports MSTest.</param>
/// <returns>A log of the process.</returns>
public static string CreateAutoTestSourceFile(string[] ghTestFolders,
string outputFolder, string outputName = "TenrecGeneratedTests",
string language = "cs", string testFramework = "mstest")
/// <inheritdoc/>
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)
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();
Expand All @@ -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<IGH_DocumentObject>();
foreach (var obj in doc.Objects)
Expand All @@ -65,18 +47,18 @@ 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;");
sb.AppendLine(" public TestContext TestContext { get => testContextInstance; set => testContextInstance = value; }");
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);");
Expand Down Expand Up @@ -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;
}
}
}
112 changes: 112 additions & 0 deletions Generators/CS/XUnitGenerator.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <inheritdoc/>
public class XUnitGenerator : IGenerator
{
/// <inheritdoc/>
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<AutoTest_{StringHelper.CodeableNickname(doc.DisplayName)}_Fixture>", 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();
}
}
}
21 changes: 21 additions & 0 deletions Generators/IGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Tenrec.Generators
{
/// <summary>
/// Produces code files automatically, generating test code based on Grasshopper files.
/// </summary>
/// <remarks>
/// This must be called once to produce the test files automatically, or every time you add or remove a <see cref="Group_UnitTest"/> or add or remove a file from the test folder.
/// </remarks>
public interface IGenerator
{
/// <summary>
/// Generate a code file containing all the <see cref="Group_UnitTest"/>-based tests present in Grasshopper files.
/// </summary>
/// <param name="ghTestFolders">The folder containing the Grasshopper files.</param>
/// <param name="outputFolder">The folder where to save the source code file.</param>
/// <param name="outputName">The name of the resulting code file.</param>
/// <returns>A log of the process.</returns>
string CreateAutoTestSourceFile
(string[] ghTestFolders, string outputFolder, string outputName);
}
}
6 changes: 5 additions & 1 deletion Tenrec.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Components\Group_UnitTest.cs" />
<Compile Include="Generators\CS\MSTestGenerator.cs" />
<Compile Include="Generators\CS\XUnitGenerator.cs" />
<Compile Include="Generators\IGenerator.cs" />
<Compile Include="ObjectMessage.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
Expand All @@ -79,7 +82,6 @@
</Compile>
<Compile Include="UI\CanvasLog.cs" />
<Compile Include="Components\Comp_Assert.cs" />
<Compile Include="Generator.cs" />
<Compile Include="Plugin\Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Runner.cs" />
Expand All @@ -90,6 +92,8 @@
<DependentUpon>UnitTestsSourceCodeGeneratorForm.cs</DependentUpon>
</Compile>
<Compile Include="TypeUtils.cs" />
<Compile Include="Utils\IOHelper.cs" />
<Compile Include="Utils\StringHelper.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
Expand Down
6 changes: 0 additions & 6 deletions Tenrec.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion UI/UnitTestsSourceCodeGeneratorForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions UI/UnitTestsSourceCodeGeneratorForm.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using Tenrec.Generators;
using Tenrec.Generators.CS;

namespace Tenrec.UI
{
Expand Down Expand Up @@ -51,15 +53,15 @@ private bool CanGenerate(out string message)
}
else
{
foreach(var folder in folders)
foreach (var folder in folders)
{
if (!System.IO.Directory.Exists(folder))
{
message = $"File folder {folder} doesn't exists.";
return false;
}
}
}
}
var outputFolder = GetOutputFolder();
if (string.IsNullOrEmpty(outputFolder))
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions Utils/IOHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Grasshopper.Kernel;
using System;

namespace Tenrec.Utils
{
/// <summary>
/// Contains helper classes for input/output operations used in Tenrec project.
/// </summary>
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;
}
}
}
Loading