public static void ShaderSetAutoDiscovery()
        {
            ToolChain toolChain = ToolChain.Get(ToolFeatures.ToCompiled);

            if (toolChain == null)
            {
                throw new RequiredToolFeatureMissingException("No tool chain supporting compilation was found!");
            }

            Compilation                        compilation      = TestUtil.GetCompilation();
            LanguageBackend                    backend          = toolChain.CreateBackend(compilation);
            ShaderGenerator                    sg               = new ShaderGenerator(compilation, backend);
            ShaderGenerationResult             generationResult = sg.GenerateShaders();
            IReadOnlyList <GeneratedShaderSet> hlslSets         = generationResult.GetOutput(backend);

            Assert.Equal(4, hlslSets.Count);
            GeneratedShaderSet set = hlslSets[0];

            Assert.Equal("VertexAndFragment", set.Name);

            CompileResult result = toolChain.Compile(set.VertexShaderCode, Stage.Vertex, "VS");

            Assert.False(result.HasError, result.ToString());

            result = toolChain.Compile(set.FragmentShaderCode, Stage.Fragment, "FS");
            Assert.False(result.HasError, result.ToString());
        }
Пример #2
0
 public static BuildCommand Create(ToolChain chain, string alias, string pluginSubFolder = "")
 {
     return(new BuildCommand()
     {
         Alias = alias, ToolChain = chain, PluginSubFolder = pluginSubFolder
     });
 }
Пример #3
0
 /// <summary>
 /// Initializes a new instance of the <see cref="TestSet"/> class.
 /// </summary>
 /// <param name="testSets">The test sets.</param>
 /// <param name="toolChain">The tool chain (if any).</param>
 public TestSet(TestSets testSets, ToolChain toolChain = null)
 {
     TestSets  = testSets;
     Name      = toolChain?.GraphicsBackend.ToString() ?? "CPU";
     ToolChain = toolChain;
     Backend   = toolChain?.CreateBackend(testSets.Compilation);
 }
 public DotsRuntimeCSharpProgramConfiguration(CSharpCodeGen csharpCodegen, CodeGen cppCodegen,
                                              //The stevedore global manifest will override DownloadableCsc.Csc72 artifacts and use Csc73
                                              ToolChain nativeToolchain, ScriptingBackend scriptingBackend, string identifier, bool enableUnityCollectionsChecks, NativeProgramFormat executableFormat = null) : base(csharpCodegen, DownloadableCsc.Csc72, HostPlatform.IsWindows ? (DebugFormat)DebugFormat.Pdb : DebugFormat.PortablePdb, nativeToolchain.Architecture is x86Architecture ? nativeToolchain.Architecture : null)
 {
     NativeProgramConfiguration = new DotsRuntimeNativeProgramConfiguration(cppCodegen, nativeToolchain, identifier, this, executableFormat: executableFormat);
     Identifier = identifier;
     EnableUnityCollectionsChecks = enableUnityCollectionsChecks;
     ScriptingBackend             = scriptingBackend;
 }
Пример #5
0
 private static NPath Copy(NPath from, NPath to, ToolChain toolchain, string subFolderDir)
 {
     if (subFolderDir != string.Empty)
     {
         to = new NPath($"{pluginDir}/{subFolderDir}").Combine(to.FileName);
     }
     else
     {
         to = new NPath($"{pluginDir}/{toolchain.LegacyPlatformIdentifier}").Combine(to.FileName);
     }
     CopyTool.Instance().Setup(to, from);
     return(to);
 }
Пример #6
0
        private void TestCompile(GraphicsBackend graphicsBackend, string vsName, string fsName, string csName = null)
        {
            Compilation compilation = TestUtil.GetCompilation();
            ToolChain   toolChain   = ToolChain.Require(ToolFeatures.ToCompiled, graphicsBackend);

            LanguageBackend backend = toolChain.CreateBackend(compilation);
            ShaderGenerator sg      = new ShaderGenerator(compilation, backend, vsName, fsName, csName);

            ShaderGenerationResult generationResult = sg.GenerateShaders();

            IReadOnlyList <GeneratedShaderSet> sets = generationResult.GetOutput(backend);

            Assert.Equal(1, sets.Count);
            GeneratedShaderSet set         = sets[0];
            ShaderModel        shaderModel = set.Model;

            List <CompileResult> results = new List <CompileResult>();

            if (!string.IsNullOrWhiteSpace(vsName))
            {
                ShaderFunction vsFunction = shaderModel.GetFunction(vsName);
                string         vsCode     = set.VertexShaderCode;

                results.Add(toolChain.Compile(vsCode, Stage.Vertex, vsFunction.Name));
            }
            if (!string.IsNullOrWhiteSpace(fsName))
            {
                ShaderFunction fsFunction = shaderModel.GetFunction(fsName);
                string         fsCode     = set.FragmentShaderCode;
                results.Add(toolChain.Compile(fsCode, Stage.Fragment, fsFunction.Name));
            }
            if (!string.IsNullOrWhiteSpace(csName))
            {
                ShaderFunction csFunction = shaderModel.GetFunction(csName);
                string         csCode     = set.ComputeShaderCode;
                results.Add(toolChain.Compile(csCode, Stage.Compute, csFunction.Name));
            }

            // Collate results
            StringBuilder builder = new StringBuilder();

            foreach (CompileResult result in results)
            {
                if (result.HasError)
                {
                    builder.AppendLine(result.ToString());
                }
            }

            Assert.True(builder.Length < 1, builder.ToString());
        }
Пример #7
0
    private static void BuildStarterKitPlugin()
    {
        var np = new NativeProgram("MARSXRSubsystem");

        np.Sources.Add("Source");
        np.IncludeDirectories.Add("External/Unity");

        var toolchains = new ToolChain[]
        {
            new WindowsToolchain(new Lazy <WindowsSdk>(() => WindowsSdk.Locatorx64.UserDefaultOrLatest).Value),
            new MacToolchain(new Lazy <MacSdk>(() => MacSdk.Locatorx64.UserDefaultOrLatest).Value),
            //new AndroidNdkToolchain(new Lazy<AndroidNdk>(() => (AndroidNdk) ToolChain.Store.Android().r16b().Arm64().Sdk).Value),
            //new AndroidNdkToolchain(new Lazy<AndroidNdk>(() => (AndroidNdk) ToolChain.Store.Android().r16b().Armv7().Sdk).Value),
            //new IOSToolchain(new Lazy<IOSSdk>(() => IOSSdk.LocatorArm64.UserDefaultOrLatest).Value),
        };

        np.OutputName.Add(s => s.Platform is AndroidPlatform, $"lib{np.Name}");

        foreach (var toolchain in toolchains)
        {
            var name = toolchain.Platform.Name.ToLower();
            if (!toolchain.CanBuild)
            {
                Console.WriteLine($"Can't build {toolchain.ToString()}");
                continue;
            }

            var nativeProgramConfiguration = new NativeProgramConfiguration(CodeGen.Debug, toolchain, lump: false);

            //we want to build the nativeprogram into an executable. each toolchain can provide different ways of "linking" (static, dynamic, executable are default ones)
            var format = toolchain is IOSToolchain ? toolchain.StaticLibraryFormat : toolchain.DynamicLibraryFormat;

            //and here the magic happens, the nativeprogram gets setup for the specific configuration we just made.
            var folder       = name;
            var deployFolder = new NPath("build").Combine(folder).Combine(toolchain.Architecture.DisplayName);
            var output       = np.SetupSpecificConfiguration(nativeProgramConfiguration, format).DeployTo(deployFolder);

            var metaFolder       = new NPath("Meta/").Combine(folder).Combine(toolchain.Architecture.DisplayName);
            var metaFile         = metaFolder.Combine("plugin." + output.Path.Extension + ".meta");
            var deployedMetaFile = deployFolder.Combine(output.Path.FileName + ".meta");
            CopyMetaFileWithNewGUID(metaFile, deployedMetaFile);

            var alias = name;
            Backend.Current.AddAliasDependency(alias, output.Path);
            Backend.Current.AddAliasDependency(alias, deployedMetaFile);
        }
    }
 public DotsRuntimeCSharpProgramConfiguration(
     CSharpCodeGen csharpCodegen,
     CodeGen cppCodegen,
     ToolChain nativeToolchain,
     ScriptingBackend scriptingBackend,
     TargetFramework targetFramework,
     string identifier,
     bool enableUnityCollectionsChecks,
     bool enableManagedDebugging,
     bool waitForManagedDebugger,
     bool multiThreadedJobs,
     DotsConfiguration dotsConfiguration,
     bool enableProfiler,
     bool useBurst,
     IEnumerable <string> defines = null,
     NPath finalOutputDirectory   = null)
     : base(
         csharpCodegen,
         null,
         DebugFormat.PortablePdb,
         nativeToolchain.Architecture is IntelArchitecture ? nativeToolchain.Architecture : null)
 {
     NativeProgramConfiguration = new DotsRuntimeNativeProgramConfiguration(
         cppCodegen,
         nativeToolchain,
         identifier,
         this);
     _identifier = identifier;
     EnableUnityCollectionsChecks = enableUnityCollectionsChecks;
     DotsConfiguration            = dotsConfiguration;
     MultiThreadedJobs            = multiThreadedJobs;
     EnableProfiler         = enableProfiler;
     UseBurst               = useBurst;
     EnableManagedDebugging = enableManagedDebugging;
     WaitForManagedDebugger = waitForManagedDebugger;
     ScriptingBackend       = scriptingBackend;
     TargetFramework        = targetFramework;
     Defines = defines?.ToList();
     FinalOutputDirectory = finalOutputDirectory;
 }
Пример #9
0
        public void PartialFiles()
        {
            ToolChain toolChain = ToolChain.Get(ToolFeatures.ToCompiled);

            if (toolChain == null)
            {
                throw new RequiredToolFeatureMissingException("No tool chain supporting compilation was found!");
            }

            Compilation     compilation = TestUtil.GetCompilation();
            LanguageBackend backend     = toolChain.CreateBackend(compilation);
            ShaderGenerator sg          = new ShaderGenerator(compilation, backend, "TestShaders.PartialVertex.VertexShaderFunc");

            ShaderGenerationResult             genResult = sg.GenerateShaders();
            IReadOnlyList <GeneratedShaderSet> sets      = genResult.GetOutput(backend);

            Assert.Equal(1, sets.Count);
            GeneratedShaderSet set         = sets[0];
            ShaderModel        shaderModel = set.Model;
            string             vsCode      = set.VertexShaderCode;
            CompileResult      result      = toolChain.Compile(vsCode, Stage.Vertex, "VertexShaderFunc");

            Assert.False(result.HasError, result.ToString());
        }
 public AndroidStaticLinker(ToolChain toolchain) : base(toolchain)
 {
 }
Пример #11
0
    public static NPath SetupBurst(NPath burstPackage, DotNetAssembly inputAssembly, NPath responseFile, ToolChain toolChain)
    {
        var bcl = new DotNetRunnableProgram(new DotNetAssembly(burstPackage.Combine(".Runtime/bcl.exe"), Framework.Framework471));

        var targetFile = inputAssembly.Path.Parent.Combine($"burst_output.{toolChain.CppCompiler.ObjectExtension}");
        var inputs     = Unity.BuildTools.EnumerableExtensions.Append(inputAssembly.RecursiveRuntimeDependenciesIncludingSelf.SelectMany(a => a.Paths), responseFile);

        Backend.Current.AddAction(
            "Burst",
            targetFiles: new[] { targetFile },
            inputs: inputs.ToArray(),
            executableStringFor: bcl.InvocationString,
            commandLineArguments: new[] { $"--assembly-folder={inputAssembly.Path.Parent}", $"--output={targetFile}", "--keep-intermediate-files", $"@{responseFile.ToString(SlashMode.Native)}" },
            allowUnexpectedOutput: false,
            allowedOutputSubstrings: new[] { "Link succesful", "Method:" });
        return(targetFile);
    }
Пример #12
0
 private static NPath ChangeExtension(string extension, NPath builtProgram, ToolChain toolchain, string subFolderDir)
 {
     return(Copy(builtProgram, builtProgram.ChangeExtension(extension), toolchain, subFolderDir));
 }
Пример #13
0
 public ITool Accept(ToolChain toolChain, Job job, IInput input) =>
 toolChain.BuildVMJobTool(job, (IVMInput)input);
Пример #14
0
        public void AllSetsCompile()
        {
            Compilation compilation = TestUtil.GetCompilation();

            // Get all available tool chains.
            LanguageBackend[] backends = TestUtil.GetAllBackends(compilation, ToolFeatures.ToCompiled);

            ShaderGenerator        sg = new ShaderGenerator(compilation, backends);
            ShaderGenerationResult generationResult = sg.GenerateShaders();

            string spacer1 = new string('=', 80);
            string spacer2 = new string('-', 80);

            bool failed = false;

            foreach (LanguageBackend backend in backends)
            {
                ToolChain toolChain = ToolChain.Get(backend);
                IReadOnlyList <GeneratedShaderSet> sets = generationResult.GetOutput(backend);
                _output.WriteLine(spacer1);
                _output.WriteLine($"Generated shader sets for {toolChain.Name} backend.");

                foreach (GeneratedShaderSet set in sets)
                {
                    _output.WriteLine(string.Empty);
                    _output.WriteLine(spacer2);
                    _output.WriteLine(string.Empty);
                    CompileResult result;

                    if (set.VertexShaderCode != null)
                    {
                        result = toolChain.Compile(set.VertexShaderCode, Stage.Vertex, set.VertexFunction.Name);
                        if (result.HasError)
                        {
                            _output.WriteLine($"Failed to compile Vertex Shader from set \"{set.Name}\"!");
                            _output.WriteLine(result.ToString());
                            failed = true;
                        }
                        else
                        {
                            _output.WriteLine($"Compiled Vertex Shader from set \"{set.Name}\"!");
                        }
                    }

                    if (set.FragmentFunction != null)
                    {
                        result = toolChain.Compile(set.FragmentShaderCode, Stage.Fragment, set.FragmentFunction.Name);
                        if (result.HasError)
                        {
                            _output.WriteLine($"Failed to compile Fragment Shader from set \"{set.Name}\"!");
                            _output.WriteLine(result.ToString());
                            failed = true;
                        }
                        else
                        {
                            _output.WriteLine($"Compiled Fragment Shader from set \"{set.Name}\"!");
                        }
                    }

                    if (set.ComputeFunction != null)
                    {
                        // TODO The skipped shaders are not included in the auto discovered shaders, leaving this here for completeness.
                        if (backend is GlslEs300Backend)
                        {
                            string fullname = set.ComputeFunction.DeclaringType + "." + set.ComputeFunction.Name + "_";
                            if (s_glslesSkippedShaders.Contains(fullname))
                            {
                                continue;
                            }
                        }

                        result = toolChain.Compile(set.ComputeShaderCode, Stage.Compute, set.ComputeFunction.Name);
                        if (result.HasError)
                        {
                            _output.WriteLine($"Failed to compile Compute Shader from set \"{set.Name}\"!");
                            _output.WriteLine(result.ToString());
                            failed = true;
                        }
                        else
                        {
                            _output.WriteLine($"Compiled Compute Shader from set \"{set.Name}\"!");
                        }
                    }
                }

                _output.WriteLine(string.Empty);
            }

            Assert.False(failed);
        }
Пример #15
0
 private static NPath PostProcessDefault(NPath builtProgram, ToolChain toolchain, string subFolderDir)
 {
     return(Copy(builtProgram, builtProgram, toolchain, subFolderDir));
 }
 public DotsRuntimeNativeProgramConfiguration(CodeGen codeGen, ToolChain toolChain, string identifier, DotsRuntimeCSharpProgramConfiguration cSharpConfig, NativeProgramFormat executableFormat = null) : base(codeGen, toolChain, false)
 {
     Identifier        = identifier;
     CSharpConfig      = cSharpConfig;
     _executableFormat = executableFormat;
 }
Пример #17
0
 public ITool Accept(ToolChain toolChain, Job job, IInput input) =>
 toolChain.BuildLexJobTool(job, (ILexInput)input);
Пример #18
0
 public ITool Accept(ToolChain toolChain, Job job, IInput input) =>
 toolChain.BuildParseJobTool(job, (IParseInput)input);
Пример #19
0
 public ITool Accept(ToolChain toolChain, Job job, IInput input) =>
 toolChain.BuildCodeGenJobTool(job, (ICodeGenInput)input);
Пример #20
0
 public static LanguageBackend[] GetAllBackends(Compilation compilation, ToolFeatures features = ToolFeatures.Transpilation)
 => ToolChain.Requires(features, false).Select(t => t.CreateBackend(compilation))
 .ToArray();
Пример #21
0
        public static void BuildProject(string folderLocation, string folderDivider)
        {
            DataItems gameData = new DataItems();

#if BuildFromVS
            //switch from the passed in folder to the Visual Studio file
            string oldLocation = folderLocation;
            folderLocation = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.FullName;
#endif

            string path = Directory.GetCurrentDirectory();

            gameData.eventList.Add(new EventLog("Calling AL.dll at at : " + path));
            gameData.eventList.Add(new EventLog(""));


            gameData.eventList.Add(new EventLog("Processing AdventureData.xml at : " + folderLocation + folderDivider + "Source" + folderDivider + "AdventureData.xml"));
            gameData.eventList.Add(new EventLog(""));

            gameData.eventList.Add(new EventLog("Compile XML data to adventure data:"));
            gameData.eventList.Add(new EventLog());

            gameData.folderLocation = folderLocation;
            gameData.debugMode      = false;
            gameData.folderDivider  = folderDivider;

            //get the XML input document
            XElement adventureCode = XElement.Load(gameData.folderLocation + folderDivider + "Source" + folderDivider + "AdventureData.xml");

            //parse the XML
            if (!XMLParser.ParseXML(adventureCode, gameData))
            {
                return;
            }
            ;

            Tokeniser.PopulateTokens.PopulateTokenList();

#if BuildFromVS
            gameData.folderLocation = oldLocation;
#endif

            try
            {
                File.Delete(gameData.folderLocation + folderDivider + "EventLog.txt");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            gameData.eventList.Add(new EventLog());
            gameData.eventList.Add(new EventLog("Writing base data to files"));
            //write verbs, adverbs and nouns
            if (!WriteDataToFiles.WriteWordsToFile(gameData))
            {
                return;
            }
            ;

            //write messages to file (including index)
            if (!WriteDataToFiles.WriteMessagesToFile(gameData))
            {
                return;
            }

            //write objects to file
            if (!WriteDataToFiles.WriteObjectsToFile(gameData))
            {
                return;
            }

            WriteDataToFiles.WriteVariablesToOutput(gameData);

            //write locations to file
            if (!WriteDataToFiles.WriteLocationsToFile(gameData))
            {
                return;
            }

            //WriteWalkthrough
            if (!WriteDataToFiles.WriteWalkthrough(gameData))
            {
                return;
            }

            //merge the user code into the source file
            if (!CreateBBCBasicFile.CreateOutputProgram(gameData))
            {
                return;
            }
            ;

            //write out to text only file
            if (!CreateBBCBasicFile.WriteProgramToFile(gameData))
            {
                return;
            }
            ;

            //write out WriteTokenisedLines
            if (!WriteDataToFiles.WriteTokenisedLines(gameData))
            {
                return;
            }
            ;

            gameData.eventList.Add(new EventLog());
            gameData.eventList.Add(new EventLog("Data compiled to: " + gameData.outputFile));
            gameData.eventList.Add(new EventLog());

            //run tokeniser
            if (!ToolChain.Tokeniser(gameData))
            {
                return;
            }

            //run SSD creation(if present)
            if (!ToolChain.SSDCreation(gameData))
            {
                return;
            }

            gameData.eventList.Add(new EventLog());
            gameData.eventList.Add(new EventLog("File creation completed."));
            gameData.eventList.Add(new EventLog());

            WriteEventLog.WriteLogToFile(gameData);

            //run BeebEm (if present)
            if (!ToolChain.RunBeebEm(gameData))
            {
                return;
            }
        }
Пример #22
0
 public ITool Accept(ToolChain toolChain, Job job, IInput input) =>
 toolChain.BuildBackendJobTool(job, (IBackendInput)input);
 public IOSAppMainModuleLinker(ToolChain toolChain) : base(toolChain)
 {
 }
Пример #24
0
        private void TestBuiltins()
        {
            // Find all backends that can create a headless graphics device on this system.
            IReadOnlyList <ToolChain> toolChains = ToolChain.Requires(ToolFeatures.HeadlessGraphicsDevice, false);

            if (toolChains.Count < 1)
            {
                throw new RequiredToolFeatureMissingException(
                          $"At least one tool chain capable of creating headless graphics devices is required for this test!");
            }

            /*
             * Auto-generate C# code for testing methods.
             */
            IReadOnlyList <MethodInfo> methods = null;
            Mappings    mappings;
            Compilation compilation;

            using (new TestTimer(_output, () => $"Generating C# shader code to test {methods.Count} methods"))
            {
                // Get all the methods we wish to test
                methods  = MethodsToTest.ToArray();
                mappings = CreateMethodTestCompilation(methods, out compilation);

                // Note, you could use compilation.Emit(...) at this point to compile the auto-generated code!
                // however, for now we'll invoke methods directly rather than executing the C# code that has been
                // generated, as loading emitted code into a test is currently much more difficult.
            }

            // Allocate enough space to store the result sets for each backend!
            TestSets testSets = null;

            using (new TestTimer(
                       _output,
                       () =>
                       $"Generating random test data ({(mappings.BufferSize * testSets.TestLoops).ToMemorySize()}) for {testSets.TestLoops} iterations of {mappings.Methods} methods")
                   )
            {
                testSets = new TestSets(toolChains, compilation, mappings, TestLoops, MinMantissa, MaxMantissa);
            }

            /*
             * Transpile shaders
             */

            ShaderGenerationResult generationResult;

            using (new TestTimer(
                       _output,
                       t =>
                       $"Generated shader sets for {string.Join(", ", toolChains.Select(tc => tc.Name))} backends in {t * 1000:#.##}ms.")
                   )
            {
                ShaderGenerator sg = new ShaderGenerator(
                    compilation,
                    testSets.Select(t => t.Backend).Where(b => b != null).ToArray(),
                    null,
                    null,
                    "ComputeShader.CS");

                generationResult = sg.GenerateShaders();
            }

            /*
             * Loop through each backend to run tests.
             */
            bool first = true;

            using (new TestTimer(_output, "Executing all tests on all backends"))
            {
                foreach (TestSet testSet in testSets)
                {
                    _output.WriteLine(string.Empty);
                    if (first)
                    {
                        // This is the first test set, so we use Space1 instead of Spacer 2.
                        first = false;
                        _output.WriteLine(TestUtil.Spacer1);
                    }
                    else
                    {
                        _output.WriteLine(TestUtil.Spacer2);
                    }

                    _output.WriteLine(String.Empty);

                    testSet.Execute(generationResult, "CS", _output);
                }

                _output.WriteLine(string.Empty);
            }

            _output.WriteLine(string.Empty);
            _output.WriteLine(TestUtil.Spacer1);
            _output.WriteLine(string.Empty);

            Assert.True(testSets.Count(t => t.Executed) > 1,
                        "At least 2 test sets are required for comparison.");

            /*
             * Finally, evaluate differences between results
             */
            IReadOnlyList <(MethodMap MethodMap, IReadOnlyList <Failure> Failures)> failures;

            using (new TestTimer(_output, "Analysing results for failures"))
            {
                failures = testSets.GetFailures(FloatEpsilon)
                           .Select(kvp => (MethodMap: kvp.Key, Failures: kvp.Value))
                           .OrderByDescending(kvp => kvp.Failures.Count)
                           .ToArray();
            }

            if (!failures.Any())
            {
                _output.WriteLine("No failures detected!");
                return;
            }

            _output.WriteLine(
                $"{failures.Count} methods had failures out of {mappings.Methods} ({100.0 * failures.Count / mappings.Methods:#.##}%).");

            _output.WriteLine(string.Empty);

            // Get pointer array
            string lastMethodName = null;

            foreach ((MethodMap methodMap, IReadOnlyList <Failure> methodFailures) in failures)
            {
                if (lastMethodName != methodMap.Method.Name)
                {
                    if (lastMethodName != null)
                    {
                        // Seperate methods of different names with spacer 1
                        _output.WriteLine(string.Empty);
                        _output.WriteLine(TestUtil.Spacer1);
                        _output.WriteLine(string.Empty);
                    }

                    lastMethodName = methodMap.Method.Name;
                }
                else
                {
                    _output.WriteLine(string.Empty);
                    _output.WriteLine(TestUtil.Spacer2);
                    _output.WriteLine(string.Empty);
                }

                int failureCount = methodFailures.Count;
                _output.WriteLine(
                    $"{TestUtil.GetUnicodePieChart((double)failureCount / testSets.TestLoops)} {methodMap.Signature} failed {failureCount}/{testSets.TestLoops} ({failureCount * 100.0 / testSets.TestLoops:#.##}%).");

                // Output examples!
                int example = 0;
                foreach (Failure failure in methodFailures)
                {
                    _output.WriteLine(string.Empty);
                    if (example++ >= FailureExamples)
                    {
                        _output.WriteLine("…");
                        break;
                    }

                    _output.WriteLine(failure.ToString());
                }
            }

            _output.WriteLine(string.Empty);
            _output.WriteLine(TestUtil.Spacer2);
            _output.WriteLine(string.Empty);
        }