From 55ef07ef652a0ba3adbd4e9e4f2a5fd51186ca11 Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Thu, 3 Mar 2016 18:35:03 -0800 Subject: [PATCH] Scheduling improvements Now factors in the maximum windows command line length. It also has improved logging. --- .../Source/RunTests/AssemblyScheduler.cs | 201 ++++++++++++++---- .../Source/RunTests/ProcessTestExecutor.cs | 1 + src/Tools/Source/RunTests/TestRunner.cs | 6 +- 3 files changed, 163 insertions(+), 45 deletions(-) diff --git a/src/Tools/Source/RunTests/AssemblyScheduler.cs b/src/Tools/Source/RunTests/AssemblyScheduler.cs index 5eb6b169bc7..4acfa813acd 100644 --- a/src/Tools/Source/RunTests/AssemblyScheduler.cs +++ b/src/Tools/Source/RunTests/AssemblyScheduler.cs @@ -45,13 +45,123 @@ internal AssemblyInfo(string assemblyPath, bool useHmtl) internal sealed class AssemblyScheduler { - private readonly StringBuilder _builder; + private struct TypeInfo + { + internal readonly string FullName; + internal readonly int MethodCount; + + internal TypeInfo(string fullName, int methodCount) + { + FullName = fullName; + MethodCount = methodCount; + } + } + + private struct Chunk + { + internal readonly string AssemblyPath; + internal readonly int Id; + internal List TypeInfoList; + + internal Chunk(string assemblyPath, int id, List typeInfoList) + { + AssemblyPath = assemblyPath; + Id = id; + TypeInfoList = typeInfoList; + } + } + + private sealed class AssemblyInfoBuilder + { + private readonly List _chunkList = new List(); + private readonly List _assemblyInfoList = new List(); + private readonly StringBuilder _builder = new StringBuilder(); + private readonly string _assemblyPath; + private readonly int _methodLimit; + private readonly bool _useHtml; + private int _currentId; + private List _currentTypeInfoList = new List(); + + private AssemblyInfoBuilder(string assemblyPath, int methodLimit, bool useHtml) + { + _assemblyPath = assemblyPath; + _useHtml = useHtml; + _methodLimit = methodLimit; + } + + internal static void Build(string assemblyPath, int methodLimit, bool useHtml, List typeInfoList, out List chunkList, out List assemblyInfoList) + { + var builder = new AssemblyInfoBuilder(assemblyPath, methodLimit, useHtml); + builder.Build(typeInfoList); + chunkList = builder._chunkList; + assemblyInfoList = builder._assemblyInfoList; + } + + private void Build(List typeInfoList) + { + foreach (var typeInfo in typeInfoList) + { + _currentTypeInfoList.Add(typeInfo); + _builder.Append($@"-class ""{typeInfo.FullName}"" "); + CheckForChunkLimit(done: false); + } + + CheckForChunkLimit(done: true); + } + + private void CheckForChunkLimit(bool done) + { + if (done && _currentTypeInfoList.Count > 0) + { + CreateChunk(); + return; + } + + // One item we have to consider here is the maximum command line length in + // Windows which is 32767 characters (XP is smaller but don't care). Once + // we get close then create a chunk and move on. + if (_currentTypeInfoList.Sum(x => x.MethodCount) >= _methodLimit || + _builder.Length > 25000) + { + CreateChunk(); + return; + } + } + + private void CreateChunk() + { + var assemblyName = Path.GetFileName(_assemblyPath); + var displayName = $"{assemblyName}.{_currentId}"; + var suffix = _useHtml ? "html" : "xml"; + var resultsFileName = $"{assemblyName}.{_currentId}.{suffix}"; + var assemblyInfo = new AssemblyInfo( + _assemblyPath, + displayName, + resultsFileName, + _builder.ToString()); + + _chunkList.Add(new Chunk(_assemblyPath, _currentId, _currentTypeInfoList)); + _assemblyInfoList.Add(assemblyInfo); + + _currentId++; + _currentTypeInfoList = new List(); + _builder.Length = 0; + } + } + + + /// + /// Default number of methods to include per chunk. + /// + internal const int DefaultMethodLimit = 750; + private readonly Options _options; + private readonly int _methodLimit; - internal AssemblyScheduler(Options options) + internal AssemblyScheduler(Options options, int methodLimit = DefaultMethodLimit) { - _builder = new StringBuilder(); _options = options; + _methodLimit = methodLimit; } internal IEnumerable Schedule(IEnumerable assemblyPaths) @@ -67,60 +177,47 @@ internal IEnumerable Schedule(IEnumerable assemblyPaths) public IEnumerable Schedule(string assemblyPath) { - var scheduleList = new List(); - var id = 0; - var count = 0; - foreach (var tuple in GetClassNames(assemblyPath)) + var typeInfoList = GetTypeInfoList(assemblyPath); + var assemblyInfoList = new List(); + var chunkList = new List(); + AssemblyInfoBuilder.Build(assemblyPath, _methodLimit, _options.UseHtml, typeInfoList, out chunkList, out assemblyInfoList); + + Logger.Log($"Assembly Schedule: {Path.GetFileName(assemblyPath)}"); + foreach (var chunk in chunkList) { - count += tuple.Item2; - _builder.Append($@"-class ""{tuple.Item1}"" "); - if (count > 700) + var methodCount = chunk.TypeInfoList.Sum(x => x.MethodCount); + var delta = methodCount - _methodLimit; + Logger.Log($" Chunk: {chunk.Id} method count {methodCount} delta {delta}"); + foreach (var typeInfo in chunk.TypeInfoList) { - scheduleList.Add(CreateAssemblyInfo(assemblyPath, id, _builder.ToString())); - _builder.Length = 0; - count = 0; - id++; + Logger.Log($" {typeInfo.FullName} {typeInfo.MethodCount}"); } } - if (_builder.Length > 0) - { - scheduleList.Add(CreateAssemblyInfo(assemblyPath, id, _builder.ToString())); - _builder.Length = 0; - } - - return scheduleList; + return assemblyInfoList; } - private AssemblyInfo CreateAssemblyInfo(string assemblyPath, int id, string arguments) + private static List GetTypeInfoList(string assemblyPath) { - var assemblyName = Path.GetFileName(assemblyPath); - var displayName = $"{assemblyName}.{id}"; - var suffix = _options.UseHtml ? "html" : "xml"; - var resultsFileName = $"{assemblyName}.{id}.{suffix}"; - return new AssemblyInfo( - assemblyPath, - displayName, - resultsFileName, - arguments); - } - - private List> GetClassNames(string assemblyPath) - { using (var stream = File.OpenRead(assemblyPath)) using (var peReader = new PEReader(stream)) { var metadataReader = peReader.GetMetadataReader(); - return GetClassNames(metadataReader); + return GetTypeInfoList(metadataReader); } } - private List> GetClassNames(MetadataReader reader) + private static List GetTypeInfoList(MetadataReader reader) { - var list = new List>(); + var list = new List(); foreach (var handle in reader.TypeDefinitions) { var type = reader.GetTypeDefinition(handle); + if (!IsValidIdentifier(reader, type.Name)) + { + continue; + } + var methodCount = GetMethodCount(reader, type); if (methodCount == 0) { @@ -130,17 +227,18 @@ private AssemblyInfo CreateAssemblyInfo(string assemblyPath, int id, string argu var namespaceName = reader.GetString(type.Namespace); var typeName = reader.GetString(type.Name); var fullName = $"{namespaceName}.{typeName}"; - list.Add(Tuple.Create(fullName, methodCount)); + list.Add(new TypeInfo(fullName, methodCount)); } // Ensure we get classes back in a deterministic order. - list.Sort((x, y) => x.Item1.CompareTo(y.Item1)); + list.Sort((x, y) => x.FullName.CompareTo(y.FullName)); return list; } - private int GetMethodCount(MetadataReader reader, TypeDefinition type) + private static int GetMethodCount(MetadataReader reader, TypeDefinition type) { - if (TypeAttributes.Public != (type.Attributes & TypeAttributes.Public)) + if (TypeAttributes.Public != (type.Attributes & TypeAttributes.Public) || + TypeAttributes.Abstract == (type.Attributes & TypeAttributes.Abstract)) { return 0; } @@ -149,7 +247,8 @@ private int GetMethodCount(MetadataReader reader, TypeDefinition type) foreach (var handle in type.GetMethods()) { var methodDefinition = reader.GetMethodDefinition(handle); - if (methodDefinition.GetCustomAttributes().Count == 0) + if (methodDefinition.GetCustomAttributes().Count == 0 || + !IsValidIdentifier(reader, methodDefinition.Name)) { continue; } @@ -164,5 +263,21 @@ private int GetMethodCount(MetadataReader reader, TypeDefinition type) return count; } + + private static bool IsValidIdentifier(MetadataReader reader, StringHandle handle) + { + var name = reader.GetString(handle); + for (int i= 0; i < name.Length; i++) + { + switch (name[i]) + { + case '<': + case '>': + return false; + } + } + + return true; + } } } diff --git a/src/Tools/Source/RunTests/ProcessTestExecutor.cs b/src/Tools/Source/RunTests/ProcessTestExecutor.cs index 69bd59209ec..23f040e88bf 100644 --- a/src/Tools/Source/RunTests/ProcessTestExecutor.cs +++ b/src/Tools/Source/RunTests/ProcessTestExecutor.cs @@ -120,6 +120,7 @@ public async Task RunTestAsync(AssemblyInfo assemblyInfo, Cancellati } var commandLine = GetCommandLine(assemblyInfo); + Logger.Log($"Command line {assemblyInfo.DisplayName}: {commandLine}"); var standardOutput = string.Join(Environment.NewLine, processOutput.OutputLines); var errorOutput = string.Join(Environment.NewLine, processOutput.ErrorLines); diff --git a/src/Tools/Source/RunTests/TestRunner.cs b/src/Tools/Source/RunTests/TestRunner.cs index 39f57e4f04d..7a1b67e0f31 100644 --- a/src/Tools/Source/RunTests/TestRunner.cs +++ b/src/Tools/Source/RunTests/TestRunner.cs @@ -37,7 +37,7 @@ internal TestRunner(Options options, ITestExecutor testExecutor) internal async Task RunAllAsync(IEnumerable assemblyInfoList, CancellationToken cancellationToken) { - var max = (int)((double)Environment.ProcessorCount * .8); + var max = Environment.ProcessorCount; var allPassed = true; var cacheCount = 0; var waiting = new Stack(assemblyInfoList); @@ -111,7 +111,9 @@ private void Print(List testResults) foreach (var testResult in testResults) { var color = testResult.Succeeded ? Console.ForegroundColor : ConsoleColor.Red; - ConsoleUtil.WriteLine(color, "{0,-75} {1} {2}{3}", testResult.DisplayName, testResult.Succeeded ? "PASSED" : "FAILED", testResult.Elapsed, testResult.IsResultFromCache ? "*" : ""); + var message = string.Format("{0,-75} {1} {2}{3}", testResult.DisplayName, testResult.Succeeded ? "PASSED" : "FAILED", testResult.Elapsed, testResult.IsResultFromCache ? "*" : ""); + ConsoleUtil.WriteLine(color, message); + Logger.Log(message); } Console.WriteLine("================"); } -- GitLab