File: TestDiscovery.cs
Web Access
Project: src\src\Tools\PrepareTests\PrepareTests.csproj (PrepareTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.IO.Pipes;
using System.Threading.Tasks;
using System.Threading;
using System.Text;
 
namespace PrepareTests;
internal class TestDiscovery
{
    private static readonly object s_lock = new();
 
    public static bool RunDiscovery(string repoRootDirectory, string dotnetPath, bool isUnix)
    {
        var binDirectory = Path.Combine(repoRootDirectory, "artifacts", "bin");
        var assemblies = GetAssemblies(binDirectory, isUnix);
        var testDiscoveryWorkerFolder = Path.Combine(binDirectory, "TestDiscoveryWorker");
        var (dotnetCoreWorker, dotnetFrameworkWorker) = GetWorkers(binDirectory);
 
        Console.WriteLine($"Found {assemblies.Count} test assemblies");
 
        var success = true;
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        Parallel.ForEach(assemblies, assembly =>
        {
            var workerPath = assembly.Contains("net472")
                ? dotnetFrameworkWorker
                : dotnetCoreWorker;
 
            var (workerSucceeded, output) = RunWorker(dotnetPath, workerPath, assembly);
            lock (s_lock)
            {
                Console.WriteLine(output);
                success &= workerSucceeded;
            }
        });
        stopwatch.Stop();
 
        if (success)
        {
            Console.WriteLine($"Discovered tests in {stopwatch.Elapsed}");
        }
        else
        {
            Console.WriteLine($"Test discovery failed");
        }
 
        return success;
    }
 
    static (string tfm, string configuration) GetTfmAndConfiguration()
    {
        var dir = Path.GetDirectoryName(typeof(TestDiscovery).Assembly.Location);
        var tfm = Path.GetFileName(dir)!;
        var configuration = Path.GetFileName(Path.GetDirectoryName(dir))!;
        return (tfm, configuration);
    }
 
    static (string dotnetCoreWorker, string dotnetFrameworkWorker) GetWorkers(string binDirectory)
    {
        var (tfm, configuration) = GetTfmAndConfiguration();
        var testDiscoveryWorkerFolder = Path.Combine(binDirectory, "TestDiscoveryWorker");
        return (Path.Combine(testDiscoveryWorkerFolder, configuration, tfm, "TestDiscoveryWorker.dll"),
                Path.Combine(testDiscoveryWorkerFolder, configuration, "net472", "TestDiscoveryWorker.exe"));
    }
 
    static (bool Succeeded, string Output) RunWorker(string dotnetPath, string pathToWorker, string pathToAssembly)
    {
        var worker = new Process();
        var arguments = new StringBuilder();
        if (pathToWorker.EndsWith("dll"))
        {
            arguments.Append($"exec {pathToWorker}");
            worker.StartInfo.FileName = dotnetPath;
        }
        else
        {
            worker.StartInfo.FileName = pathToWorker;
        }
 
        var pathToOutput = Path.Combine(Path.GetDirectoryName(pathToAssembly)!, "testlist.json");
        arguments.Append($" --assembly {pathToAssembly} --out {pathToOutput}");
 
        var output = new StringBuilder();
        worker.StartInfo.Arguments = arguments.ToString();
        worker.StartInfo.UseShellExecute = false;
        worker.StartInfo.RedirectStandardOutput = true;
        worker.OutputDataReceived += (sender, e) => output.Append(e.Data);
        worker.Start();
        worker.BeginOutputReadLine();
        worker.WaitForExit();
        var success = worker.ExitCode == 0;
        worker.Close();
 
        return (success, output.ToString());
    }
 
    private static List<string> GetAssemblies(string binDirectory, bool isUnix)
    {
        var unitTestAssemblies = Directory.GetFiles(binDirectory, "*UnitTests.dll", SearchOption.AllDirectories);
        var integrationTestAssemblies = Directory.GetFiles(binDirectory, "*IntegrationTests.dll", SearchOption.AllDirectories);
        var assemblies = unitTestAssemblies.Concat(integrationTestAssemblies).Where(ShouldInclude);
        return assemblies.ToList();
 
        bool ShouldInclude(string path)
        {
            if (isUnix)
            {
                // Our unix build will build net framework dlls for multi-targeted projects.
                // These are not valid testing on unix and discovery will throw if we try.
                return Path.GetFileName(Path.GetDirectoryName(path)) != "net472";
            }
 
            return true;
        }
    }
}