Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Commit 6bbba6f

Browse files
wilsonawilsona
authored andcommitted
Initial commit
1 parent c6323cd commit 6bbba6f

24 files changed

+1412
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,4 @@ paket-files/
250250
# JetBrains Rider
251251
.idea/
252252
*.sln.iml
253+
/OrderedTestFileGenerator/smoke.orderedtest

OrderedTestFileGenerator.sln

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 14
4+
VisualStudioVersion = 14.0.25420.1
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderedTestFileGenerator", "OrderedTestFileGenerator\OrderedTestFileGenerator.csproj", "{C54E176F-E7D6-4216-818A-04EB40FD74C1}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderedTestFileGeneratorTests", "OrderedTestFileGeneratorTests\OrderedTestFileGeneratorTests.csproj", "{DBD13439-DFDC-4954-A640-46703DF7E1CC}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{C54E176F-E7D6-4216-818A-04EB40FD74C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{C54E176F-E7D6-4216-818A-04EB40FD74C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{C54E176F-E7D6-4216-818A-04EB40FD74C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{C54E176F-E7D6-4216-818A-04EB40FD74C1}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{DBD13439-DFDC-4954-A640-46703DF7E1CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{DBD13439-DFDC-4954-A640-46703DF7E1CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{DBD13439-DFDC-4954-A640-46703DF7E1CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{DBD13439-DFDC-4954-A640-46703DF7E1CC}.Release|Any CPU.Build.0 = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
EndGlobal
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
5+
</startup>
6+
</configuration>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
10+
namespace OrderedTestFileGenerator
11+
{
12+
static class AssemblyExtensions
13+
{
14+
public static IEnumerable<Type> TestClassTypes(this Assembly assembly)
15+
{
16+
return assembly.GetTypes().Where(t => t.GetCustomAttributes<TestClassAttribute>().Any());
17+
}
18+
19+
public static IEnumerable<MethodInfo> TestMethods(this Type type)
20+
{
21+
return type.GetMethods().Where(m => m.GetCustomAttributes<TestMethodAttribute>().Any());
22+
}
23+
24+
public static IEnumerable<MethodInfo> AllTestMethods(this Assembly assembly)
25+
{
26+
return assembly.TestClassTypes().SelectMany(x => x.TestMethods());
27+
}
28+
29+
public static bool IsIgnored(this MethodInfo methodInfo)
30+
{
31+
return methodInfo.GetCustomAttributes<IgnoreAttribute>().Any();
32+
}
33+
34+
public static IEnumerable<string> Categories(this MethodInfo methodInfo)
35+
{
36+
return methodInfo.GetCustomAttributes<TestCategoryAttribute>().SelectMany(a => a.TestCategories);
37+
}
38+
39+
public static FileSystemInfo FilePath(this Assembly assembly)
40+
{
41+
return new FileInfo(new Uri(assembly.CodeBase).LocalPath);
42+
}
43+
44+
public static string QualifiedName(this MethodInfo methodInfo)
45+
{
46+
return methodInfo.ReflectedType.FullName + "." + methodInfo.Name;
47+
}
48+
}
49+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
7+
namespace OrderedTestFileGenerator
8+
{
9+
class AssemblyParser
10+
{
11+
public IEnumerable<TestDefinition> AllTests(FileSystemInfo assemblyToParse)
12+
{
13+
Assembly assembly = Assembly.LoadFile(assemblyToParse.FullName);
14+
return AllTests(assembly);
15+
}
16+
17+
public IEnumerable<TestDefinition> AllTests(Assembly assemblyToParse)
18+
{
19+
FileSystemInfo assemblyPath = assemblyToParse.FilePath();
20+
21+
return from methodInfo in assemblyToParse.AllTestMethods()
22+
where !methodInfo.IsIgnored()
23+
select new TestDefinition
24+
{
25+
QualifiedName = methodInfo.QualifiedName(),
26+
MethodName = methodInfo.Name,
27+
Categories = methodInfo.Categories(),
28+
AssemblyFile = assemblyPath,
29+
};
30+
}
31+
}
32+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace OrderedTestFileGenerator
9+
{
10+
class CategoryParser
11+
{
12+
private HashSet<string> allCategories = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
13+
14+
public void AddCategories(IEnumerable<string> categories)
15+
{
16+
if (categories == null) return;
17+
foreach (string category in categories)
18+
{
19+
allCategories.Add(category);
20+
}
21+
}
22+
23+
public void AddCategoryFile(FileInfo categoryFilePath)
24+
{
25+
var categories = File.ReadAllLines(categoryFilePath.FullName);
26+
AddCategories(categories);
27+
}
28+
29+
public int Count
30+
{
31+
get { return allCategories.Count; }
32+
}
33+
34+
public IEnumerable<string> Categories
35+
{
36+
get { return allCategories; }
37+
}
38+
}
39+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace OrderedTestFileGenerator
8+
{
9+
enum ExitCodes
10+
{
11+
OK = 0,
12+
InvalidArgs = 1,
13+
Help = 2
14+
}
15+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using CommandLine;
7+
8+
namespace OrderedTestFileGenerator
9+
{
10+
class InputOptions
11+
{
12+
[OptionArray('a', "assemblypaths", Required = true, HelpText = "The file paths to the assembly DLLs containing the mstests.")]
13+
public string[] AssemblyFilePathsRaw { get; set; }
14+
15+
public IList<FileInfo> AssemblyFilePaths
16+
{
17+
get
18+
{
19+
if (AssemblyFilePathsRaw == null) return null;
20+
var paths = AssemblyFilePathsRaw.Select(x => TryParseFileInfo(x)).ToList();
21+
return paths.Any(x => x == null) ? null : paths;
22+
}
23+
}
24+
25+
[Option('o', "outputpath", Required = true, HelpText = "The file path be to used for the generated Ordered Test file.")]
26+
public string OutputFilePathRaw { get; set; }
27+
28+
public FileInfo OutputFilePath
29+
{
30+
get { return TryParseFileInfo(OutputFilePathRaw); }
31+
}
32+
33+
[OptionList('c', "categories", Separator = ';', HelpText = "The sequence of categories for ordered tests (semi-colon separated). Either categories or categoriesfile required.")]
34+
public IList<string> Categories { get; set; }
35+
36+
[Option('x', "categoriesfile", HelpText = "The file path of a linebreak separated list of categories.")]
37+
public string CategoriesFilePathRaw { get; set; }
38+
39+
public FileInfo CategoriesFilePath
40+
{
41+
get { return TryParseFileInfo(CategoriesFilePathRaw); }
42+
}
43+
44+
[Option('p', "appendorphans", DefaultValue = false, HelpText = "Specify to append tests which don't match any of the specified categories to the end of the ordered test file.")]
45+
public bool AppendOrphans { get; set; }
46+
47+
public string GetHelp()
48+
{
49+
return new StringBuilder()
50+
.AppendLine("Usage:")
51+
.AppendLine("-a The file paths to the assembly DLLs containing the mstests. Required.")
52+
.AppendLine("-o The file path be to used for the generated Ordered Test file. Required.")
53+
.AppendLine("Categories (at least one must be specified):")
54+
.AppendLine("-c The sequence of categories for ordered tests (semi-colon separated).")
55+
.AppendLine("-x The file path of a linebreak separated list of categories.")
56+
.AppendLine()
57+
.AppendLine("-p Specify to append tests which don't match any of the specified categories to the end of the ordered test file.")
58+
.AppendLine()
59+
60+
.ToString();
61+
}
62+
63+
public string GetErrors()
64+
{
65+
StringBuilder sb = new StringBuilder();
66+
if (!IsValidAssemblyFilePaths)
67+
{
68+
sb.AppendLine("- Invalid assembly path(s)");
69+
}
70+
if (!IsValidOutputFilePath)
71+
{
72+
sb.AppendLine("- Invalid output path");
73+
}
74+
if (!IsValidCategoryFilePath)
75+
{
76+
sb.AppendLine("- Invalid categories path");
77+
}
78+
else if (!IsValidCategoryOption)
79+
{
80+
sb.AppendLine("- No categories specified");
81+
}
82+
83+
if (sb.Length != 0) sb.Insert(0, "Errors:" + Environment.NewLine);
84+
return sb.ToString();
85+
}
86+
87+
internal bool IsValidAssemblyFilePaths
88+
{
89+
get { return AssemblyFilePaths != null && AssemblyFilePaths.Count != 0 && AssemblyFilePaths.All(x => x.Exists); }
90+
}
91+
92+
internal bool IsValidOutputFilePath
93+
{
94+
get { return OutputFilePath != null && OutputFilePath.Exists; }
95+
}
96+
97+
internal bool IsValidCategoryFilePath
98+
{
99+
get { return CategoriesFilePath == null || CategoriesFilePath.Exists; }
100+
}
101+
102+
internal bool IsValidCategoryOption
103+
{
104+
get { return CategoriesFilePath != null || (Categories != null && Categories.Count != 0); }
105+
}
106+
107+
internal FileInfo TryParseFileInfo(string fullName)
108+
{
109+
if (string.IsNullOrWhiteSpace(fullName)) return null;
110+
try
111+
{
112+
return new FileInfo(fullName);
113+
}
114+
catch (NotSupportedException)
115+
{
116+
return null;
117+
}
118+
}
119+
}
120+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using System.Xml.Linq;
8+
9+
namespace OrderedTestFileGenerator
10+
{
11+
class OrderedTestFile
12+
{
13+
private FileSystemInfo outputFile;
14+
15+
public OrderedTestFile(FileSystemInfo outputFile)
16+
{
17+
this.outputFile = outputFile;
18+
Id = Guid.NewGuid();
19+
}
20+
21+
public Guid Id { get; private set; }
22+
23+
public void Generate(IEnumerable<TestDefinition> tests)
24+
{
25+
var outputXml = GenerateInternal(tests);
26+
outputXml.Save(outputFile.FullName);
27+
}
28+
29+
internal XDocument GenerateInternal(IEnumerable<TestDefinition> tests)
30+
{
31+
XNamespace testNamespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010";
32+
XDocument outputXml = new XDocument
33+
(new XDeclaration("1.0", "UTF-8", null),
34+
new XElement(testNamespace + "OrderedTest",
35+
new XAttribute("name", Path.GetFileNameWithoutExtension(outputFile.Name)),
36+
new XAttribute("storage", outputFile.FullName),
37+
new XAttribute("id", Id),
38+
new XElement(testNamespace + "TestLinks",
39+
from testDefinition in tests
40+
select new XElement(testNamespace + "TestLink",
41+
new XAttribute("id", testDefinition.Id),
42+
new XAttribute("name", testDefinition.MethodName),
43+
new XAttribute("storage", testDefinition.AssemblyFile.FullName)
44+
))));
45+
return outputXml;
46+
}
47+
48+
public FileSystemInfo File
49+
{
50+
get { return outputFile; }
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)