diff --git a/Directory.Build.props b/Directory.Build.props index e0bc547db..d6e7c9e3f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,9 @@ + + Latest + + $(MSBuildThisFileDirectory) $(RepoRoot)..\IL2CPU\ diff --git a/Test.sln b/Test.sln index e61441f3a..79a93f29d 100644 --- a/Test.sln +++ b/Test.sln @@ -160,6 +160,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cosmos.Debug.HyperVServer", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cosmos.TestRunner.Full", "Tests\Cosmos.TestRunner.Full\Cosmos.TestRunner.Full.csproj", "{65EE0B97-D3F0-400D-B432-85FF5553C44E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cosmos.TestRunner.TestAdapter", "Tests\Cosmos.TestRunner.TestAdapter\Cosmos.TestRunner.TestAdapter.csproj", "{8574E797-7C48-4F12-B537-4F927BCBA93B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -609,6 +611,14 @@ Global {65EE0B97-D3F0-400D-B432-85FF5553C44E}.Release|Any CPU.Build.0 = Release|Any CPU {65EE0B97-D3F0-400D-B432-85FF5553C44E}.Release|x86.ActiveCfg = Release|Any CPU {65EE0B97-D3F0-400D-B432-85FF5553C44E}.Release|x86.Build.0 = Release|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Debug|x86.ActiveCfg = Debug|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Debug|x86.Build.0 = Debug|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Release|Any CPU.Build.0 = Release|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Release|x86.ActiveCfg = Release|Any CPU + {8574E797-7C48-4F12-B537-4F927BCBA93B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -685,6 +695,7 @@ Global {CCFD198D-4859-462B-9EF7-B305A8B4E6FC} = {0E67EFE8-5944-4F6C-8B47-C5E06D4C79F5} {3421E19D-16C7-4593-9F6B-291ECB86A3EB} = {F7C6CA93-1D02-443C-9C8B-A1988DE0306B} {65EE0B97-D3F0-400D-B432-85FF5553C44E} = {0E67EFE8-5944-4F6C-8B47-C5E06D4C79F5} + {8574E797-7C48-4F12-B537-4F927BCBA93B} = {0E67EFE8-5944-4F6C-8B47-C5E06D4C79F5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4418C803-277E-448F-A0A0-52788FA215AD} diff --git a/Tests/Cosmos.TestRunner.TestAdapter/Cosmos.TestRunner.TestAdapter.csproj b/Tests/Cosmos.TestRunner.TestAdapter/Cosmos.TestRunner.TestAdapter.csproj new file mode 100644 index 000000000..caf68b4a3 --- /dev/null +++ b/Tests/Cosmos.TestRunner.TestAdapter/Cosmos.TestRunner.TestAdapter.csproj @@ -0,0 +1,42 @@ + + + + netcoreapp2.0 + Test adapter for Cosmos kernels. + True + NU5111 + + + + + + + + + + + + False + + True + $(TargetsForTfmSpecificContentInPackage);PackTestAdapter + + + + + + <_GeneratedFiles Include="$(PublishDepsFilePath)" /> + + + + + tools\%(_GeneratedFiles.RecursiveDir)%(_GeneratedFiles.Filename)%(_GeneratedFiles.Extension) + + + tools\%(ResolvedFileToPublish.RelativePath) + + + + + + diff --git a/Tests/Cosmos.TestRunner.TestAdapter/CosmosTestDiscoverer.cs b/Tests/Cosmos.TestRunner.TestAdapter/CosmosTestDiscoverer.cs new file mode 100644 index 000000000..7ef202c13 --- /dev/null +++ b/Tests/Cosmos.TestRunner.TestAdapter/CosmosTestDiscoverer.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace Cosmos.TestRunner.TestAdapter +{ + [FileExtension(".dll")] + [DefaultExecutorUri(CosmosTestExecutor.ExecutorUri)] + public sealed class CosmosTestDiscoverer : ITestDiscoverer + { + private static readonly Uri ExecutorUri = new Uri(CosmosTestExecutor.ExecutorUri); + + public void DiscoverTests( + IEnumerable sources, + IDiscoveryContext discoveryContext, + IMessageLogger logger, + ITestCaseDiscoverySink discoverySink) + { + var testCases = DiscoverTests(sources, discoveryContext, logger); + + foreach (var testCase in testCases) + { + discoverySink.SendTestCase(testCase); + } + } + + internal IEnumerable DiscoverTests( + IEnumerable sources, + IDiscoveryContext discoveryContext, + IMessageLogger logger) + { + var testCases = new List(); + + foreach (var source in sources) + { + try + { + using (var stream = File.OpenRead(source)) + { + using (var peReader = new PEReader(stream)) + { + if (!peReader.HasMetadata) + { + continue; + } + + var metadataReader = peReader.GetMetadataReader(); + + if (!metadataReader.IsAssembly) + { + continue; + } + + // todo: check exported types? + + foreach (var typeHandle in metadataReader.TypeDefinitions) + { + var type = metadataReader.GetTypeDefinition(typeHandle); + + if ((type.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public) + { + var baseTypeHandle = type.BaseType; + + // has to be type reference, as it's not generic + // and it's not declared in the same module + if (baseTypeHandle.Kind == HandleKind.TypeReference) + { + var baseType = metadataReader.GetTypeReference((TypeReferenceHandle)baseTypeHandle); + + var baseTypeNamespace = metadataReader.GetString(baseType.Namespace); + var baseTypeName = metadataReader.GetString(baseType.Name); + + if (String.Equals(baseTypeNamespace, "Cosmos.System", StringComparison.Ordinal) + && String.Equals(baseTypeName, "Kernel", StringComparison.Ordinal) + && baseType.ResolutionScope.Kind == HandleKind.AssemblyReference) + { + var baseTypeAssemblyReference = metadataReader.GetAssemblyReference( + (AssemblyReferenceHandle)baseType.ResolutionScope); + + var baseTypeAssemblyName = metadataReader.GetString(baseTypeAssemblyReference.Name); + + if (String.Equals(baseTypeAssemblyName, "Cosmos.System2", StringComparison.Ordinal)) + { + var typeNamespace = metadataReader.GetString(type.Namespace); + var typeName = metadataReader.GetString(type.Name); + + var assemblyDefinition = metadataReader.GetAssemblyDefinition(); + var assemblyName = metadataReader.GetString(assemblyDefinition.Name); + + var fullyQualifiedName = $"{typeName}, {assemblyName}"; + + if (!String.IsNullOrEmpty(typeNamespace)) + { + fullyQualifiedName = $"{typeNamespace}.{fullyQualifiedName}"; + } + + var testCase = new TestCase(fullyQualifiedName, ExecutorUri, source); + testCases.Add(testCase); + + break; + } + } + } + } + } + } + } + } + catch + { + } + } + + return testCases; + } + } +} diff --git a/Tests/Cosmos.TestRunner.TestAdapter/CosmosTestExecutor.cs b/Tests/Cosmos.TestRunner.TestAdapter/CosmosTestExecutor.cs new file mode 100644 index 000000000..66390b18c --- /dev/null +++ b/Tests/Cosmos.TestRunner.TestAdapter/CosmosTestExecutor.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +using Cosmos.TestRunner.Core; + +namespace Cosmos.TestRunner.TestAdapter +{ + [ExtensionUri(ExecutorUri)] + public sealed class CosmosTestExecutor : ITestExecutor + { + public const string ExecutorUri = "executor://CosmosTestExecutor"; + + private CancellationTokenSource _cancellationTokenSource; + + public void RunTests( + IEnumerable tests, + IRunContext runContext, + IFrameworkHandle frameworkHandle) + { + _cancellationTokenSource = new CancellationTokenSource(); + + foreach (var test in tests) + { + var configuration = new EngineConfiguration(new string[] { test.Source }, runContext); + var testEngine = new Engine(configuration); + + var outputHandler = new TestAdapterOutputHandler(frameworkHandle); + testEngine.SetOutputHandler(outputHandler); + + var testResult = new TestResult(test); + + frameworkHandle.RecordStart(test); + + var kernelTestResult = testEngine.Execute(_cancellationTokenSource.Token).KernelTestResults[0]; + + testResult.Outcome = kernelTestResult.Result ? TestOutcome.Passed : TestOutcome.Failed; + + var messages = new Collection(); + + foreach (var message in outputHandler.Messages) + { + messages.Add(new TestResultMessage(String.Empty, message)); + } + + frameworkHandle.RecordEnd(test, testResult.Outcome); + frameworkHandle.RecordResult(testResult); + } + } + + public void RunTests( + IEnumerable sources, + IRunContext runContext, + IFrameworkHandle frameworkHandle) + { + var discoverer = new CosmosTestDiscoverer(); + var tests = discoverer.DiscoverTests(sources, runContext, null); + + RunTests(tests, runContext, frameworkHandle); + } + + public void Cancel() => _cancellationTokenSource.Cancel(); + } +} diff --git a/Tests/Cosmos.TestRunner.TestAdapter/EngineConfiguration.cs b/Tests/Cosmos.TestRunner.TestAdapter/EngineConfiguration.cs new file mode 100644 index 000000000..31cfceff6 --- /dev/null +++ b/Tests/Cosmos.TestRunner.TestAdapter/EngineConfiguration.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +using Cosmos.Build.Common; +using Cosmos.TestRunner.Core; + +namespace Cosmos.TestRunner.TestAdapter +{ + internal sealed class EngineConfiguration : IEngineConfiguration + { + public int AllowedSecondsInKernel { get; } + public IEnumerable RunTargets { get; } + public bool RunWithGDB { get; } + public bool StartBochsDebugGUI { get; } + + public bool DebugIL2CPU { get; } + public string KernelPkg { get; } + public TraceAssemblies TraceAssembliesLevel { get; } + public bool EnableStackCorruptionChecks { get; } + public StackCorruptionDetectionLevel StackCorruptionDetectionLevel { get; } + + public IEnumerable KernelAssembliesToRun { get; } + + private readonly IRunContext _context; + + public EngineConfiguration( + IEnumerable testKernels, + IRunContext context) + { + KernelAssembliesToRun = testKernels; + + _context = context; + + var settingsXml = _context.RunSettings.SettingsXml; + + if (string.IsNullOrEmpty(settingsXml)) + { + settingsXml = ""; + } + + var doc = XDocument.Parse(settingsXml); + var runConfiguration = doc.Element("RunConfiguration"); + + AllowedSecondsInKernel = GetIntValue(runConfiguration, nameof(AllowedSecondsInKernel), 1200); + + var runTargetsString = GetStringValue(runConfiguration, nameof(RunTargets), "Bochs"); + var runTargets = new List(); + + foreach (var runTargetName in runTargetsString.Split(';')) + { + if (Enum.TryParse(runTargetName, out var runTarget)) + { + runTargets.Add(runTarget); + } + } + + RunTargets = runTargets; + RunWithGDB = GetBoolValue(runConfiguration, nameof(RunWithGDB), false); + StartBochsDebugGUI = GetBoolValue(runConfiguration, nameof(StartBochsDebugGUI), false); + + DebugIL2CPU = GetBoolValue(runConfiguration, nameof(DebugIL2CPU), false); + KernelPkg = GetStringValue(runConfiguration, nameof(KernelPkg), String.Empty); + TraceAssembliesLevel = GetEnumValue(runConfiguration, nameof(KernelPkg), TraceAssemblies.User); + EnableStackCorruptionChecks = GetBoolValue(runConfiguration, nameof(EnableStackCorruptionChecks), true); + StackCorruptionDetectionLevel = GetEnumValue( + runConfiguration, nameof(KernelPkg), StackCorruptionDetectionLevel.AllInstructions); + } + + private static bool GetBoolValue(XElement element, XName name, bool defaultValue) + { + var value = GetStringValue(element, name, null); + + if (Boolean.TryParse(value, out var result)) + { + return result; + } + + return defaultValue; + } + + private static T GetEnumValue(XElement element, XName name, T defaultValue) where T : struct, Enum + { + var value = GetStringValue(element, name, null); + + if (Enum.TryParse(value, true, out var result)) + { + return result; + } + + return defaultValue; + } + + private static int GetIntValue(XElement element, XName name, int defaultValue) + { + var value = GetStringValue(element, name, null); + + if (Int32.TryParse(value, out var result)) + { + return result; + } + + return defaultValue; + } + + private static string GetStringValue(XElement element, XName name, string defaultValue) + { + var childElement = element?.Element(name); + + if (childElement != null) + { + return childElement.Value; + } + + return defaultValue; + } + } +} diff --git a/Tests/Cosmos.TestRunner.TestAdapter/Readme.txt b/Tests/Cosmos.TestRunner.TestAdapter/Readme.txt new file mode 100644 index 000000000..1af5a83be --- /dev/null +++ b/Tests/Cosmos.TestRunner.TestAdapter/Readme.txt @@ -0,0 +1,4 @@ +To debug vstest/test adapter: + +set VSTEST_HOST_DEBUG=1 +dotnet test diff --git a/Tests/Cosmos.TestRunner.TestAdapter/TestAdapterOutputHandler.cs b/Tests/Cosmos.TestRunner.TestAdapter/TestAdapterOutputHandler.cs new file mode 100644 index 000000000..4be69eaa4 --- /dev/null +++ b/Tests/Cosmos.TestRunner.TestAdapter/TestAdapterOutputHandler.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +using Cosmos.TestRunner.Core; + +namespace Cosmos.TestRunner.TestAdapter +{ + internal class TestAdapterOutputHandler : OutputHandlerFullTextBase + { + public IReadOnlyList Messages => _messages; + + private readonly IFrameworkHandle _frameworkHandle; + private readonly List _messages = new List(); + + public TestAdapterOutputHandler(IFrameworkHandle frameworkHandle) + { + _frameworkHandle = frameworkHandle; + } + + protected override void Log(string message) + { + _frameworkHandle.SendMessage(TestMessageLevel.Informational, message); + _messages.Add(message); + } + } +}