Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'test/Mono.Linker.Tests/TestCasesRunner/TestCaseCollector.cs')
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/TestCaseCollector.cs163
1 files changed, 163 insertions, 0 deletions
diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCollector.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCollector.cs
new file mode 100644
index 000000000..5c4f4dbc9
--- /dev/null
+++ b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCollector.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Mono.Cecil;
+using Mono.Linker.Tests.TestCases;
+using Mono.Linker.Tests.Extensions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.TestCasesRunner {
+ public class TestCaseCollector {
+ private readonly NPath _rootDirectory;
+ private readonly NPath _testCaseAssemblyPath;
+
+ public TestCaseCollector (string rootDirectory, string testCaseAssemblyPath)
+ : this (rootDirectory.ToNPath (), testCaseAssemblyPath.ToNPath ())
+ {
+ }
+
+ public TestCaseCollector (NPath rootDirectory, NPath testCaseAssemblyPath)
+ {
+ _rootDirectory = rootDirectory;
+ _testCaseAssemblyPath = testCaseAssemblyPath;
+ }
+
+ public IEnumerable<TestCase> Collect ()
+ {
+ return Collect (AllSourceFiles ());
+ }
+
+ public TestCase Collect (NPath sourceFile)
+ {
+ return Collect (new [] { sourceFile }).First ();
+ }
+
+ public IEnumerable<TestCase> Collect (IEnumerable<NPath> sourceFiles)
+ {
+ _rootDirectory.DirectoryMustExist ();
+ _testCaseAssemblyPath.FileMustExist ();
+
+ using (var caseAssemblyDefinition = AssemblyDefinition.ReadAssembly (_testCaseAssemblyPath.ToString ())) {
+ foreach (var file in sourceFiles) {
+ TestCase testCase;
+ if (CreateCase (caseAssemblyDefinition, file, out testCase))
+ yield return testCase;
+ }
+ }
+ }
+
+ public IEnumerable<NPath> AllSourceFiles ()
+ {
+ _rootDirectory.DirectoryMustExist ();
+
+ foreach (var file in _rootDirectory.Files ("*.cs")) {
+ yield return file;
+ }
+
+ foreach (var subDir in _rootDirectory.Directories ()) {
+ if (subDir.FileName == "bin" || subDir.FileName == "obj" || subDir.FileName == "Properties")
+ continue;
+
+ foreach (var file in subDir.Files ("*.cs", true)) {
+
+ var relativeParents = file.RelativeTo(_rootDirectory);
+ // Magic : Anything in a directory named Dependencies is assumed to be a dependency to a test case
+ // and never a test itself
+ // This makes life a little easier when writing these supporting files as it removes some constraints you would previously have
+ // had to follow such as ensuring a class exists that matches the file name and putting [NotATestCase] on that class
+ if (relativeParents.RecursiveParents.Any(p => p.Elements.Any() && p.FileName == "Dependencies"))
+ continue;
+
+ // Magic: Anything in a directory named Individual is expected to be ran by it's own [Test] rather than as part of [TestCaseSource]
+ if (relativeParents.RecursiveParents.Any(p => p.Elements.Any() && p.FileName == "Individual"))
+ continue;
+
+ yield return file;
+ }
+ }
+ }
+
+ public TestCase CreateIndividualCase (Type testCaseType)
+ {
+ _rootDirectory.DirectoryMustExist ();
+ _testCaseAssemblyPath.FileMustExist ();
+
+ var pathRelativeToAssembly = $"{testCaseType.FullName.Substring (testCaseType.Module.Name.Length - 3).Replace ('.', '/')}.cs";
+ var fullSourcePath = _rootDirectory.Combine (pathRelativeToAssembly).FileMustExist ();
+
+ using (var caseAssemblyDefinition = AssemblyDefinition.ReadAssembly (_testCaseAssemblyPath.ToString ()))
+ {
+ TestCase testCase;
+ if (!CreateCase (caseAssemblyDefinition, fullSourcePath, out testCase))
+ throw new ArgumentException ($"Could not create a test case for `{testCaseType}`. Ensure the namespace matches it's location on disk");
+
+ return testCase;
+ }
+ }
+
+ private bool CreateCase (AssemblyDefinition caseAssemblyDefinition, NPath sourceFile, out TestCase testCase)
+ {
+ var potentialCase = new TestCase (sourceFile, _rootDirectory, _testCaseAssemblyPath);
+
+ var typeDefinition = FindTypeDefinition (caseAssemblyDefinition, potentialCase);
+
+ testCase = null;
+
+ if (typeDefinition == null) {
+ Console.WriteLine ($"Could not find the matching type for test case {sourceFile}. Ensure the file name and class name match");
+ return false;
+ }
+
+ if (typeDefinition.HasAttribute (nameof (NotATestCaseAttribute))) {
+ return false;
+ }
+
+ // Verify the class as a static main method
+ var mainMethod = typeDefinition.Methods.FirstOrDefault (m => m.Name == "Main");
+
+ if (mainMethod == null) {
+ Console.WriteLine ($"{typeDefinition} in {sourceFile} is missing a Main() method");
+ return false;
+ }
+
+ if (!mainMethod.IsStatic) {
+ Console.WriteLine ($"The Main() method for {typeDefinition} in {sourceFile} should be static");
+ return false;
+ }
+
+ testCase = potentialCase;
+ return true;
+ }
+
+ private static TypeDefinition FindTypeDefinition (AssemblyDefinition caseAssemblyDefinition, TestCase testCase)
+ {
+ var typeDefinition = caseAssemblyDefinition.MainModule.GetType (testCase.ReconstructedFullTypeName);
+
+ // For all of the Test Cases, the full type name we constructed from the directory structure will be correct and we can successfully find
+ // the type from GetType.
+ if (typeDefinition != null)
+ return typeDefinition;
+
+ // However, some of types are supporting types rather than test cases. and may not follow the standardized naming scheme of the test cases
+ // We still need to be able to locate these type defs so that we can parse some of the metadata on them.
+ // One example, Unity run's into this with it's tests that require a type UnityEngine.MonoBehaviours to exist. This tpe is defined in it's own
+ // file and it cannot follow our standardized naming directory & namespace naming scheme since the namespace must be UnityEngine
+ foreach (var type in caseAssemblyDefinition.MainModule.Types) {
+ // Let's assume we should never have to search for a test case that has no namespace. If we don't find the type from GetType, then o well, that's not a test case.
+ if (string.IsNullOrEmpty (type.Namespace))
+ continue;
+
+ if (type.Name == testCase.Name) {
+ // This isn't foolproof, but let's do a little extra vetting to make sure the type we found corresponds to the source file we are
+ // processing.
+ if (!testCase.SourceFile.ReadAllText ().Contains ($"namespace {type.Namespace}"))
+ continue;
+
+ return type;
+ }
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file