Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: xunit code genarator #4

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
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