singrdk/base/Windows/mkjig/mkjig.cs

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;
}}
}}
}}
";
}