396 lines
13 KiB
C#
396 lines
13 KiB
C#
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// Examine assembly metadata to generate test jig code.
|
|
//
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
using Bartok.MSIL;
|
|
|
|
|
|
internal class SuiteDesc
|
|
{
|
|
public readonly List<string> tests = new List<string>();
|
|
public string init = null;
|
|
public string cleanup = null;
|
|
public string testInit = null;
|
|
public string testCleanup = null;
|
|
}
|
|
|
|
internal class ModuleDesc
|
|
{
|
|
public readonly IDictionary<string, SuiteDesc> suites = new SortedDictionary<string, SuiteDesc>();
|
|
public string init = null;
|
|
public string cleanup = null;
|
|
public SuiteDesc ProvideSuite(string className)
|
|
{
|
|
SuiteDesc res;
|
|
if (!suites.TryGetValue(className, out res)) {
|
|
res = new SuiteDesc();
|
|
suites.Add(className, res);
|
|
}
|
|
return res;
|
|
}
|
|
public SuiteDesc ProvideSuite(MetaDataObject m, out string child)
|
|
{
|
|
string className;
|
|
child = mktests.Tail(m.FullName, '.', out className);
|
|
return ProvideSuite(className);
|
|
}
|
|
}
|
|
|
|
public class mktests
|
|
{
|
|
const string ATTRIBUTE_PREFIX = "Microsoft.Singularity.UnitTest.";
|
|
|
|
private string m_outpath;
|
|
private ModuleDesc m_module;
|
|
private IDictionary<string, MetaDataObject> m_suites = new SortedDictionary<string, MetaDataObject>();
|
|
|
|
// TEMPLATE STRINGS DEFINE AT BOTTOM OF FILE
|
|
|
|
private mktests(string path)
|
|
{
|
|
m_outpath = path;
|
|
m_module = new ModuleDesc();
|
|
}
|
|
|
|
public static int Main(string[] args)
|
|
{
|
|
if (args.Length != 2) {
|
|
Usage();
|
|
return -1;
|
|
}
|
|
DateTime timeBegin = DateTime.Now;
|
|
string outfile = args[0];
|
|
string infile = args[1];
|
|
ArrayList infiles = new ArrayList();
|
|
infiles.Add(infile);
|
|
|
|
mktests it = new mktests(outfile);
|
|
it.ProcessAssembly(infiles);
|
|
//FileStream oo = new FileStream(outfile, FileMode.Create, FileAccess.Write);
|
|
// try {
|
|
// }
|
|
// finally {
|
|
// oo.Close();
|
|
// }
|
|
|
|
TimeSpan elapsed = DateTime.Now - timeBegin;
|
|
Console.WriteLine("mkjig: {0} seconds elapsed.", elapsed.TotalSeconds);
|
|
return 0;
|
|
}
|
|
|
|
private static void Usage()
|
|
{
|
|
Console.WriteLine("Usage:\n mkjig <test_jig_source> <assembly> \n");
|
|
}
|
|
|
|
private void ProcessAssembly(ArrayList infiles)
|
|
{
|
|
MetaDataResolver resolver =
|
|
new MetaDataResolver(infiles, new ArrayList(), new DateTime(), false, false);
|
|
|
|
MetaDataResolver.ResolveCustomAttributes(new MetaDataResolver[] { resolver });
|
|
foreach (MetaData md in resolver.MetaDataList) {
|
|
// Assume that if we are processing an assembly, it will contain tests
|
|
string name = md.Name;
|
|
string prefix = name.Substring(0, name.LastIndexOf('.')); // trim extension
|
|
string module = prefix.Substring(prefix.LastIndexOf('\\')+1); // extract filename
|
|
ProcessAssembly(md);
|
|
}
|
|
}
|
|
|
|
private void ProcessAssembly(MetaData md)
|
|
{
|
|
// Look for the annotation that tells us that this assembly is a stand-alone
|
|
// test app.
|
|
MetaDataAssembly mda = (MetaDataAssembly) md.Assemblies[0];
|
|
foreach (MetaDataCustomAttribute attrib in md.CustomAttributes) {
|
|
MetaDataObject parent = attrib.Parent;
|
|
//Console.WriteLine("Found: {0} in {1} {2}", attrib.Name, parent.FullName, parent.FullNameWithContext);
|
|
if (!attrib.Name.StartsWith(ATTRIBUTE_PREFIX)) {
|
|
continue;
|
|
}
|
|
Console.WriteLine("Found: {0} in {1} {2}", attrib.Name, parent.FullName, parent.FullNameWithContext);
|
|
string attribName = attrib.Name.Substring(ATTRIBUTE_PREFIX.Length);
|
|
string className;
|
|
string item = Tail(attrib.Parent, out className);
|
|
if (attribName == "TestClassAttribute") {
|
|
m_suites.Add(attrib.Parent.FullName, attrib);
|
|
}
|
|
else if (attribName == "TestMethodAttribute") {
|
|
m_module.ProvideSuite(className).tests.Add(item);
|
|
}
|
|
else if (attribName == "ClassInitializeAttribute") {
|
|
m_module.ProvideSuite(className).init = item;
|
|
}
|
|
else if (attribName == "ClassCleanupAttribute") {
|
|
m_module.ProvideSuite(className).cleanup = item;
|
|
}
|
|
else if (attribName == "TestInitializeAttribute") {
|
|
m_module.ProvideSuite(className).testInit = item;
|
|
}
|
|
else if (attribName == "TestCleanupAttribute") {
|
|
m_module.ProvideSuite(className).testCleanup = item;
|
|
}
|
|
else if (attribName == "AssemblyInitializeAttribute") {
|
|
m_module.init = item;
|
|
}
|
|
else if (attribName == "TestCleanupAttribute") {
|
|
m_module.cleanup = item;
|
|
}
|
|
else {
|
|
// IGNORE
|
|
}
|
|
}
|
|
StringBuilder suiteStr = new StringBuilder();
|
|
StringBuilder jigsStr = new StringBuilder();
|
|
foreach (KeyValuePair<string, SuiteDesc> kvp in m_module.suites) {
|
|
string fullname = kvp.Key;
|
|
if (!m_suites.ContainsKey(fullname)) {
|
|
Console.WriteLine("TestMethod declared outside of a TestClass: {0}", fullname);
|
|
continue;
|
|
}
|
|
string pkg;
|
|
string className = Tail(fullname, '.', out pkg);
|
|
SuiteDesc desc = kvp.Value;
|
|
GenTests(className, pkg, desc, suiteStr);
|
|
jigsStr.AppendFormat(SUITE_CASE_TEMPLATE, className, pkg);
|
|
}
|
|
string modulename = "Foo";
|
|
StringBuilder otherStr = new StringBuilder();
|
|
//AppendOpt(MODULE_INIT_TEMPLATE, m_module.init, otherStr);
|
|
//AppendOpt(MODULE_INIT_TEMPLATE, m_module.cleanup, otherStr);
|
|
string content = string.Format(FILE_TEMPLATE, modulename, jigsStr, otherStr, suiteStr);
|
|
File.WriteAllText(m_outpath, content);
|
|
}
|
|
|
|
private void GenTests(string className, string pkg, SuiteDesc desc, StringBuilder suiteStr)
|
|
{
|
|
StringBuilder caseStr = new StringBuilder();
|
|
desc.tests.Sort();
|
|
foreach (string t in desc.tests) {
|
|
caseStr.AppendFormat(TEST_CASE_TEMPLATE, t);
|
|
}
|
|
StringBuilder otherStr = new StringBuilder();
|
|
AppendOpt(INIT_TEMPLATE, desc.init, otherStr);
|
|
AppendOpt(CLEANUP_TEMPLATE, desc.cleanup, otherStr);
|
|
// TODO test init
|
|
suiteStr.AppendFormat(SUITE_TEMPLATE, className, pkg, caseStr, otherStr);
|
|
}
|
|
|
|
private static void AppendOpt(string format, string optS, StringBuilder otherStr)
|
|
{
|
|
if (optS != null) {
|
|
otherStr.AppendFormat(format, optS);
|
|
}
|
|
}
|
|
|
|
// Split a string at the last occcurence of a character, returning both
|
|
// the before and after. If the character is not present, then the tail
|
|
// is empty and before is the entire string.
|
|
public static string Tail(string it, char pattern, out string before)
|
|
{
|
|
int i = it.LastIndexOf(pattern);
|
|
if (i < 0) {
|
|
before = it;
|
|
return "";
|
|
}
|
|
else {
|
|
before = it.Substring(0, i);
|
|
return it.Substring(i + 1);
|
|
}
|
|
}
|
|
|
|
public static string Tail(MetaDataObject m, out string before)
|
|
{
|
|
return Tail(m.FullName, '.', out before);
|
|
}
|
|
|
|
//Assert(m != null, "TestMethod attribute is on a method");
|
|
// oo.WriteStartElement("Suite");
|
|
// oo.WriteAttributeString("Name", );
|
|
// inSuite = true;
|
|
// object timeout = psItem.Fields["Test Timeout"].Value;
|
|
// if (timeout != null) {
|
|
// oo.WriteAttributeString("Timeout", timeout.ToString());
|
|
// }
|
|
// object knownFailure = psItem.Fields["Test Known Failure"].Value;
|
|
// if (knownFailure != null) {
|
|
// oo.WriteAttributeString("KnownFailure", knownFailure.ToString());
|
|
// }
|
|
private static void Assert(bool cond, string trueText)
|
|
{
|
|
if (!cond) {
|
|
throw new Exception("Expected: " + trueText);
|
|
}
|
|
}
|
|
|
|
private const string TEST_INIT_TEMPLATE = @"
|
|
override public void TestInitialize()
|
|
{{
|
|
m_test.{0}();
|
|
}}
|
|
";
|
|
|
|
private const string TEST_CLEANUP_TEMPLATE = @"
|
|
override public void TestCleanup()
|
|
{{
|
|
m_test.{0}();
|
|
}}
|
|
";
|
|
|
|
private const string INIT_TEMPLATE = @"
|
|
override public void Initialize()
|
|
{{
|
|
m_test.{0}();
|
|
}}
|
|
";
|
|
|
|
private const string CLEANUP_TEMPLATE = @"
|
|
override public void Cleanup()
|
|
{{
|
|
m_test.{0}();
|
|
}}
|
|
";
|
|
|
|
private const string TEST_CASE_TEMPLATE = @"
|
|
case ""{0}"":
|
|
m_test.{0}();
|
|
break;
|
|
";
|
|
|
|
private const string SUITE_CASE_TEMPLATE = @"
|
|
case ""{0}"":
|
|
return new {1}.{0}_Jig(log);
|
|
";
|
|
|
|
// suite name, suite namespace, suite cases, suite other
|
|
private const string SUITE_TEMPLATE = @"
|
|
namespace {1} {{
|
|
internal class {0}_Jig : SuiteJig
|
|
{{
|
|
private {0}! m_test;
|
|
|
|
public {0}_Jig(TestLog! log)
|
|
{{
|
|
{0} t = new {0}();
|
|
t.SetLog(log);
|
|
m_test = t;
|
|
}}
|
|
|
|
override public void DoTest(string! test)
|
|
{{
|
|
switch (test) {{
|
|
{2}
|
|
default:
|
|
base.DoTest(test);
|
|
break;
|
|
}}
|
|
}}
|
|
{3}
|
|
}}
|
|
}}
|
|
";
|
|
|
|
// module name, module cases, module other, suite jigs
|
|
private const string FILE_TEMPLATE = @"
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// Generated test jig code
|
|
|
|
using System;
|
|
using System.Threading;
|
|
|
|
using Microsoft.Singularity.UnitTest;
|
|
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Contracts;
|
|
using Microsoft.SingSharp.Reflection;
|
|
using Microsoft.Singularity.Applications;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Configuration;
|
|
using Microsoft.Singularity.Test.Contracts;
|
|
using Microsoft.Singularity.Configuration;
|
|
|
|
[assembly: Transform(typeof(ApplicationResourceTransform))]
|
|
|
|
// GENERATED SUITE JIGS
|
|
{3}
|
|
|
|
namespace Microsoft.Singularity.Applications {{
|
|
|
|
// GENERATED MODULE JIG
|
|
public class {0}_ModuleJig : ModuleJig
|
|
{{
|
|
override public SuiteJig GetSuite(string! name, TestLog! log)
|
|
{{
|
|
switch (name) {{
|
|
{1}
|
|
default:
|
|
return base.GetSuite(name, log);
|
|
}}
|
|
}}
|
|
{2}
|
|
}}
|
|
|
|
[ConsoleCategory(HelpMessage=""ModuleTester"", Action=""test"")]
|
|
internal class ModuleTest_Category {{
|
|
[InputEndpoint(""data"")]
|
|
public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;
|
|
[OutputEndpoint(""data"")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[CustomEndpoint]
|
|
public readonly TRef<ModuleTesterContract.Exp:START> testerRef;
|
|
|
|
reflective internal ModuleTest_Category();
|
|
|
|
internal int AppMain() {{
|
|
if (testerRef == null) {{
|
|
DebugStub.WriteLine(""TEST endpoint not setup"");
|
|
throw new Exception(""TEST endpoint not setup "");
|
|
}}
|
|
ModuleTesterContract.Exp tester = testerRef.Acquire();
|
|
if (tester == null) {{
|
|
DebugStub.WriteLine(""TEST unable to acquite handle to test driver"");
|
|
throw new Exception(""Unable to acquire handle to the test driver"");
|
|
}}
|
|
ModuleJig jig = new {0}_ModuleJig();
|
|
ModuleTester.RunTests(tester, jig);
|
|
return 0;
|
|
}}
|
|
}}
|
|
|
|
// Currently required to get process launch code generated.
|
|
[ConsoleCategory(HelpMessage=""Run using the test framework"", DefaultAction=true)]
|
|
internal class ModuleConsole_Category {{
|
|
[InputEndpoint(""data"")]
|
|
public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;
|
|
[OutputEndpoint(""data"")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
reflective internal ModuleConsole_Category();
|
|
|
|
internal int AppMain() {{
|
|
Console.WriteLine(""This is a test application and can only be run from the tester."");
|
|
return -1;
|
|
}}
|
|
}}
|
|
}}
|
|
";
|
|
}
|