diff --git a/.gitattributes b/.gitattributes index badb96a3e..52d4937c4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,7 @@ *.ps1 text *.yml text -*.sln text +*.slnx text *.sln.DotSettings text *.props text @@ -14,4 +14,3 @@ *.csproj text *.cs text diff=csharp *.fs text -*.nuspec text diff --git a/.vscode/launch.json b/.vscode/launch.json index ccf63665d..1d129f556 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,9 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/artifacts/bin/Fixie.Tests/debug_net8.0/Fixie.Tests.dll", + "program": "${workspaceFolder}/src/artifacts/bin/Fixie.Tests/debug/Fixie.Tests.dll", "args": [], - "cwd": "${workspaceFolder}/src/artifacts/bin/Fixie.Tests/debug_net8.0", + "cwd": "${workspaceFolder}/src/artifacts/bin/Fixie.Tests/debug", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", "stopAtEntry": false diff --git a/build.ps1 b/build.ps1 index 66442e9a9..be9d4f435 100644 --- a/build.ps1 +++ b/build.ps1 @@ -19,5 +19,4 @@ step { dotnet $fixie *.Tests -c Release --no-build } if ($pack) { step { dotnet pack src/Fixie -o packages -c Release --no-build --nologo } step { dotnet pack src/Fixie.Console -o packages -c Release --no-build --nologo } - step { dotnet pack src/Fixie.TestAdapter -o packages -c Release --no-build --nologo } } \ No newline at end of file diff --git a/build/Fixie.props b/build/Fixie.props index 1d320bed2..c15dae3fe 100644 --- a/build/Fixie.props +++ b/build/Fixie.props @@ -1,13 +1,4 @@  - - true - true - - - - - - \ No newline at end of file diff --git a/build/Fixie.targets b/build/Fixie.targets index b60021b10..8578e15e9 100644 --- a/build/Fixie.targets +++ b/build/Fixie.targets @@ -3,7 +3,6 @@ Exe - false diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5fadd5eba..abbaf02ac 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -19,7 +19,7 @@ true true snupkg - 4.1 + 5.0 diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 3638c728b..db2eaebdc 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,7 +4,6 @@ - diff --git a/src/Fixie.Console/Fixie.Console.csproj b/src/Fixie.Console/Fixie.Console.csproj index e78f48987..e472485b3 100644 --- a/src/Fixie.Console/Fixie.Console.csproj +++ b/src/Fixie.Console/Fixie.Console.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 true fixie `dotnet fixie` console test runner for the Fixie test framework. diff --git a/src/Fixie.TestAdapter/DebuggerAttachmentFailure.cs b/src/Fixie.TestAdapter/DebuggerAttachmentFailure.cs deleted file mode 100644 index 8f69dd48f..000000000 --- a/src/Fixie.TestAdapter/DebuggerAttachmentFailure.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Fixie.TestAdapter; - -class DebuggerAttachmentFailure -{ - public string Message { get; } - public Exception ThirdPartyTestHostException { get; } - - public DebuggerAttachmentFailure(Exception thirdPartyTestHostException) - { - ThirdPartyTestHostException = thirdPartyTestHostException; - Message = UserGuidanceMessage(thirdPartyTestHostException); - } - - static string UserGuidanceMessage(Exception thirdPartyTestHostException) - { - var host = TryGetTestHost(out var testHost) - ? $"{Environment.NewLine}{testHost}{Environment.NewLine}" - : ""; - - return $@"Fixie attempted to run your test assembly -under the active debugger session, but the -third-party test host {host}failed to honor the request to attach to the -test assembly process. The run continued -without the debugger so that this message -could be reported. - - -In order to debug your test, bypass your -test runner's unimplemented ""Debug"" -option using one of the following two -approaches. - -If your operating system supports -Debugger.Launch(): - - 1. Add the following line at the start - of your test: - - System.Diagnostics.Debugger.Launch(); - - 2. Use your test runner's ""Run"" option - to start the test instead of using - its unimplemented ""Debug"" option. - - 3. Make a selection in the resulting - debugger session dialog. - - -If your operating system does not support -Debugger.Launch(): - - 1. Note that Fixie test projects are in - fact normal console applications that - run their own tests. - - 2. Instead of using your test runner, - run your test project under your - development environment's debugger - as a normal console application. - - -When filing a bug report to your specific -third-party test runner's organization, -please include the following exception -thrown by their implementation of the VSTest -API. Test runners are meant to implement -IFrameworkHandle.LaunchProcessWithDebuggerAttached -since they have control over the active -debugger session. - - -{thirdPartyTestHostException.Message}"; - } - - static bool TryGetTestHost([NotNullWhen(true)] out string? testHost) - { - try - { - testHost = Environment.GetCommandLineArgs().First(); - } - catch - { - testHost = null; - } - - return testHost != null; - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/Fixie.TestAdapter.csproj b/src/Fixie.TestAdapter/Fixie.TestAdapter.csproj deleted file mode 100644 index f1cdc343c..000000000 --- a/src/Fixie.TestAdapter/Fixie.TestAdapter.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - - $(NuspecProperties);id=$(PackageId) - $(NuspecProperties);version=$(PackageVersion) - $(NuspecProperties);authors=$(Authors) - $(NuspecProperties);description=$(Description) - $(NuspecProperties);copyright=$(Copyright) - - - - - net8.0;net9.0 - Visual Studio integration for the Fixie test framework. - Fixie.TestAdapter.nuspec - true - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Fixie.TestAdapter/Fixie.TestAdapter.nuspec b/src/Fixie.TestAdapter/Fixie.TestAdapter.nuspec deleted file mode 100644 index 0c19b1625..000000000 --- a/src/Fixie.TestAdapter/Fixie.TestAdapter.nuspec +++ /dev/null @@ -1,41 +0,0 @@ - - - - - $id$ - $version$ - $authors$ - $authors$ - false - README.md - MIT - https://fixie.github.io - icon.png - $description$ - $copyright$ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Fixie.TestAdapter/InternalsVisibleTo.cs b/src/Fixie.TestAdapter/InternalsVisibleTo.cs deleted file mode 100644 index f5102558f..000000000 --- a/src/Fixie.TestAdapter/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly:InternalsVisibleTo("Fixie.Tests")] \ No newline at end of file diff --git a/src/Fixie.TestAdapter/LoggingExtensions.cs b/src/Fixie.TestAdapter/LoggingExtensions.cs deleted file mode 100644 index ca70cd3b2..000000000 --- a/src/Fixie.TestAdapter/LoggingExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Fixie.Internal; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - -namespace Fixie.TestAdapter; - -static class LoggingExtensions -{ - public static void Info(this IMessageLogger logger, string message) - => logger.SendMessage(TestMessageLevel.Informational, message); - - public static void Error(this IMessageLogger logger, string message) - => logger.SendMessage(TestMessageLevel.Error, message); - - public static void Error(this IMessageLogger logger, Exception exception) - => logger.SendMessage(TestMessageLevel.Error, exception.ToString()); - - public static void Version(this IMessageLogger logger) - => logger.Info(Framework.Version); -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/RunnerException.cs b/src/Fixie.TestAdapter/RunnerException.cs deleted file mode 100644 index df0b79f4b..000000000 --- a/src/Fixie.TestAdapter/RunnerException.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text; -using Fixie.Internal; - -namespace Fixie.TestAdapter; - -class RunnerException : Exception -{ - public RunnerException(Exception exception) - : base(exception.ToString()) - { - } - - public RunnerException(PipeMessage.Exception exception) - : base(new StringBuilder() - .AppendLine() - .AppendLine() - .AppendLine(exception.Message) - .AppendLine() - .AppendLine(exception.Type) - .AppendLine(exception.StackTrace) - .ToString()) - { - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/SourceLocation.cs b/src/Fixie.TestAdapter/SourceLocation.cs deleted file mode 100644 index a508be5a0..000000000 --- a/src/Fixie.TestAdapter/SourceLocation.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Fixie.TestAdapter; - -class SourceLocation -{ - public SourceLocation(string codeFilePath, int lineNumber) - { - CodeFilePath = codeFilePath; - LineNumber = lineNumber; - } - - public string CodeFilePath { get; } - public int LineNumber { get; } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/TestAssembly.cs b/src/Fixie.TestAdapter/TestAssembly.cs deleted file mode 100644 index 4253711db..000000000 --- a/src/Fixie.TestAdapter/TestAssembly.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - -namespace Fixie.TestAdapter; - -static class TestAssembly -{ - public static bool IsTestAssembly(string assemblyPath) - { - string[] fixieAssemblies = - [ - "Fixie.dll", "Fixie.TestAdapter.dll" - ]; - - if (fixieAssemblies.Contains(Path.GetFileName(assemblyPath))) - return false; - - var folderPath = new FileInfo(assemblyPath).Directory!.FullName; - - return File.Exists(Path.Combine(folderPath, "Fixie.dll")); - } - - public static int? TryGetExitCode(this Process? process) - { - if (process != null && process.WaitForExit(5000)) - return process.ExitCode; - - return null; - } - - public static Process StartDiscovery(string assemblyPath) - { - return Run(assemblyPath); - } - - public static Process? StartExecution(string assemblyPath, IFrameworkHandle frameworkHandle, - out DebuggerAttachmentFailure? attachmentFailure) - { - attachmentFailure = null; - - if (Debugger.IsAttached) - return RunAttemptingDebuggerAttachment(assemblyPath, frameworkHandle, out attachmentFailure); - - return Run(assemblyPath); - } - - static Process Run(string assemblyPath) - { - return Start(new ProcessStartInfo("dotnet", [assemblyPath]) - { - WorkingDirectory = WorkingDirectory(assemblyPath), - UseShellExecute = false - }); - } - - static Process? RunAttemptingDebuggerAttachment(string assemblyPath, IFrameworkHandle frameworkHandle, - out DebuggerAttachmentFailure? attachmentFailure) - { - attachmentFailure = null; - - // LaunchProcessWithDebuggerAttached sends a request back - // to the third-party test runner process which started - // this TestAdapter's host process. That test runner - // process does not know about environment variables - // created so far by this TestAdapter. That test runner - // cannot reliably resolve the meaning of bare commands - // like `dotnet` to the full file path of the corresponding - // executable. To ensure the test runner process can - // successfully honor the request, we must explicitly - // pass along new environment variables and resolve the - // full path for the `dotnet` executable. - - var environmentVariables = new Dictionary - { - ["FIXIE_NAMED_PIPE"] = Environment.GetEnvironmentVariable("FIXIE_NAMED_PIPE") - }; - - var filePath = FindDotnet(); - - try - { - frameworkHandle - .LaunchProcessWithDebuggerAttached( - filePath, - WorkingDirectory(assemblyPath), - Serialize([assemblyPath]), - environmentVariables); - - return null; - } - catch (Exception thirdPartyTestHostException) - { - attachmentFailure = new DebuggerAttachmentFailure(thirdPartyTestHostException); - - frameworkHandle.Error(thirdPartyTestHostException); - - return Run(assemblyPath); - } - } - - static string WorkingDirectory(string assemblyPath) - { - return Path.GetDirectoryName(Path.GetFullPath(assemblyPath))!; - } - - static Process Start(ProcessStartInfo startInfo) - { - var process = new Process - { - StartInfo = startInfo - }; - - if (process.Start()) - return process; - - throw new Exception("Failed to start process: " + startInfo.FileName); - } - - static string FindDotnet() - { - var fileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet"; - - var folderPath = Environment - .GetEnvironmentVariable("PATH")? - .Split(Path.PathSeparator) - .FirstOrDefault(path => File.Exists(Path.Combine(path.Trim(), fileName))); - - if (folderPath == null) - throw new Exception( - $"Could not locate {fileName} when searching the PATH environment variable. " + - "Verify that you have installed the .NET SDK."); - - return Path.Combine(folderPath.Trim(), fileName); - } - - /// - /// Serialize the given string[] to a single string, so that when used as a ProcessStartInfo.Arguments - /// value, the process's Main method will receive the original string[]. - /// - /// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ - /// See https://stackoverflow.com/a/6040946 for the regex approach used here. - /// - public static string Serialize(string[] arguments) - => string.Join(" ", arguments.Select(Quote)); - - static string Quote(string argument) - { - //For each substring of zero or more \ followed by " - //replace it with twice as many \ followed by \" - var s = Regex.Replace(argument, @"(\\*)" + '"', @"$1$1\" + '"'); - - //When an argument ends in \ double the number of \ at the end. - s = Regex.Replace(s, @"(\\+)$", @"$1$1"); - - //Now that the content has been escaped, surround the value in quotes. - return '"' + s + '"'; - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/TestProcessExitException.cs b/src/Fixie.TestAdapter/TestProcessExitException.cs deleted file mode 100644 index 80c8ca129..000000000 --- a/src/Fixie.TestAdapter/TestProcessExitException.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Fixie.TestAdapter; - -public class TestProcessExitException : Exception -{ - const int StackOverflowExitCode = -1073741571; - - public TestProcessExitException(int? exitCode) - : base(GetMessage(exitCode)) - { - } - - static string GetMessage(int? exitCode) - { - const string exitedUnexpectedly = "The test assembly process exited unexpectedly"; - - if (exitCode != null) - { - var withExitCode = $"with exit code {exitCode}"; - - return exitCode == StackOverflowExitCode - ? $"{exitedUnexpectedly} {withExitCode}, indicating the test threw a StackOverflowException." - : $"{exitedUnexpectedly} {withExitCode}."; - } - - return $"{exitedUnexpectedly}."; - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/VsDiscoveryRecorder.cs b/src/Fixie.TestAdapter/VsDiscoveryRecorder.cs deleted file mode 100644 index 9e97481a6..000000000 --- a/src/Fixie.TestAdapter/VsDiscoveryRecorder.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Fixie.Internal; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - -namespace Fixie.TestAdapter; - -class VsDiscoveryRecorder -{ - readonly IMessageLogger log; - readonly ITestCaseDiscoverySink discoverySink; - readonly string assemblyPath; - readonly SourceLocationProvider sourceLocationProvider; - - public VsDiscoveryRecorder(IMessageLogger log, ITestCaseDiscoverySink discoverySink, string assemblyPath) - { - this.log = log; - this.discoverySink = discoverySink; - this.assemblyPath = assemblyPath; - - sourceLocationProvider = new SourceLocationProvider(assemblyPath); - } - - public void Record(PipeMessage.TestDiscovered testDiscovered) - { - var test = testDiscovered.Test; - - SourceLocation? sourceLocation = null; - - try - { - sourceLocationProvider.TryGetSourceLocation(test, out sourceLocation); - } - catch (Exception exception) - { - log.Error(exception.ToString()); - } - - var discoveredTest = new TestCase(test, VsTestExecutor.Uri, assemblyPath) - { - DisplayName = test - }; - - if (sourceLocation != null) - { - discoveredTest.CodeFilePath = sourceLocation.CodeFilePath; - discoveredTest.LineNumber = sourceLocation.LineNumber; - } - - discoverySink.SendTestCase(discoveredTest); - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/VsExecutionRecorder.cs b/src/Fixie.TestAdapter/VsExecutionRecorder.cs deleted file mode 100644 index c279ed7a0..000000000 --- a/src/Fixie.TestAdapter/VsExecutionRecorder.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Fixie.Internal; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using static System.Environment; - -namespace Fixie.TestAdapter; - -using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; - -class VsExecutionRecorder -{ - readonly ITestExecutionRecorder log; - readonly string assemblyPath; - - public VsExecutionRecorder(ITestExecutionRecorder log, string assemblyPath) - { - this.log = log; - this.assemblyPath = assemblyPath; - } - - public void Record(PipeMessage.TestStarted message) - { - var testCase = ToVsTestCase(message.Test); - - log.RecordStart(testCase); - } - - public void Record(PipeMessage.TestSkipped result) - { - Record(result, x => - { - x.Outcome = TestOutcome.Skipped; - x.ErrorMessage = result.Reason; - }); - } - - public void Record(PipeMessage.TestPassed result) - { - Record(result, x => - { - x.Outcome = TestOutcome.Passed; - }); - } - - public void Record(PipeMessage.TestFailed result) - { - Record(result, x => - { - x.Outcome = TestOutcome.Failed; - x.ErrorMessage = result.Reason.Message; - x.ErrorStackTrace = result.Reason.Type + - NewLine + - result.Reason.StackTrace; - }); - } - - void Record(PipeMessage.TestCompleted result, Action customize) - { - var testCase = ToVsTestCase(result.Test); - - var testResult = new TestResult(testCase) - { - DisplayName = result.TestCase, - Duration = TimeSpan.FromMilliseconds(result.DurationInMilliseconds), - ComputerName = MachineName - }; - - customize(testResult); - - log.RecordResult(testResult); - } - - TestCase ToVsTestCase(string test) - { - return new TestCase(test, VsTestExecutor.Uri, assemblyPath); - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/VsTestDiscoverer.cs b/src/Fixie.TestAdapter/VsTestDiscoverer.cs deleted file mode 100644 index 20c9d1a31..000000000 --- a/src/Fixie.TestAdapter/VsTestDiscoverer.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.IO.Pipes; -using Fixie.Internal; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using static Fixie.TestAdapter.TestAssembly; - -namespace Fixie.TestAdapter; - -[DefaultExecutorUri(VsTestExecutor.Id)] -[FileExtension(".exe")] -[FileExtension(".dll")] -class VsTestDiscoverer : ITestDiscoverer -{ - public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger log, ITestCaseDiscoverySink discoverySink) - { - try - { - log.Version(); - - foreach (var assemblyPath in sources) - DiscoverTests(log, discoverySink, assemblyPath); - } - catch (Exception exception) - { - throw new RunnerException(exception); - } - } - - static void DiscoverTests(IMessageLogger log, ITestCaseDiscoverySink discoverySink, string assemblyPath) - { - if (!IsTestAssembly(assemblyPath)) - { - log.Info("Skipping " + assemblyPath + " because it is not a test assembly."); - return; - } - - log.Info("Processing " + assemblyPath); - - var pipeName = Guid.NewGuid().ToString(); - Environment.SetEnvironmentVariable("FIXIE_NAMED_PIPE", pipeName); - - using (var pipeStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte)) - using (var pipe = new TestAdapterPipe(pipeStream)) - using (var process = StartDiscovery(assemblyPath)) - { - pipeStream.WaitForConnection(); - - pipe.Send(); - - var recorder = new VsDiscoveryRecorder(log, discoverySink, assemblyPath); - - while (true) - { - var messageType = pipe.ReceiveMessageType(); - - if (messageType == typeof(PipeMessage.TestDiscovered).FullName) - { - var testDiscovered = pipe.Receive(); - recorder.Record(testDiscovered); - } - else if (messageType == typeof(PipeMessage.Exception).FullName) - { - var exception = pipe.Receive(); - throw new RunnerException(exception); - } - else if (messageType == typeof(PipeMessage.EndOfPipe).FullName) - { - var endOfPipe = pipe.Receive(); - break; - } - else if (!string.IsNullOrEmpty(messageType)) - { - var body = pipe.ReceiveMessageBody(); - log.Error($"The test runner received an unexpected message of type {messageType}: {body}"); - } - else - { - throw new TestProcessExitException(process.TryGetExitCode()); - } - } - } - } -} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/VsTestExecutor.cs b/src/Fixie.TestAdapter/VsTestExecutor.cs deleted file mode 100644 index 6fd67ba75..000000000 --- a/src/Fixie.TestAdapter/VsTestExecutor.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.IO.Pipes; -using Fixie.Internal; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using static Fixie.TestAdapter.TestAssembly; - -namespace Fixie.TestAdapter; - -[ExtensionUri(Id)] -public class VsTestExecutor : ITestExecutor -{ - public const string Id = "executor://fixie.testadapter/"; - public static readonly Uri Uri = new(Id); - - /// - /// This method was originally intended to be called by VsTest when running - /// all tests. However, VsTest no longer appears to call this method, instead - /// favoring its overload with all individual tests specified as if they were - /// all selected by the user. The stated reason is for performance, but in - /// fact requires far larger messages to be passed between processes and far - /// more cross-referencing of test names within each specific test framework - /// at execution time. This overload is maintained optimistically and for - /// protection in the event that VsTest changes back to the more efficient - /// approach. - /// - public void RunTests(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle) - { - ArgumentNullException.ThrowIfNull(sources); - ArgumentNullException.ThrowIfNull(frameworkHandle); - - try - { - IMessageLogger log = frameworkHandle; - - log.Version(); - - HandlePoorVsTestImplementationDetails(runContext, frameworkHandle); - - var executeTests = new PipeMessage.ExecuteTests - { - Filter = [] - }; - - foreach (var assemblyPath in sources) - RunTests(log, frameworkHandle, assemblyPath, executeTests); - } - catch (Exception exception) - { - throw new RunnerException(exception); - } - } - - /// - /// This method was originally intended to be called by VsTest only when running - /// a selected subset of previously discovered tests. However, VsTest appears to - /// call this method even in the event the user is running *all* tests, with all - /// individual tests specified in the input as if all were individually selected - /// by the user. The stated reason is for performance, but in fact requires far - /// larger messages to be passed between processes and far more cross-referencing - /// of test names within each specific test framework at execution time. Still, - /// this overload is functionally correct even when all tests are passed to it. - /// - public void RunTests(IEnumerable? tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) - { - ArgumentNullException.ThrowIfNull(tests); - ArgumentNullException.ThrowIfNull(frameworkHandle); - - try - { - IMessageLogger log = frameworkHandle; - - log.Version(); - - HandlePoorVsTestImplementationDetails(runContext, frameworkHandle); - - var assemblyGroups = tests.GroupBy(tc => tc.Source); - - foreach (var assemblyGroup in assemblyGroups) - { - var assemblyPath = assemblyGroup.Key; - - var executeTests = new PipeMessage.ExecuteTests - { - Filter = assemblyGroup.Select(x => x.FullyQualifiedName).ToArray() - }; - - RunTests(log, frameworkHandle, assemblyPath, executeTests); - } - } - catch (Exception exception) - { - throw new RunnerException(exception); - } - } - - public void Cancel() { } - - static void RunTests(IMessageLogger log, IFrameworkHandle frameworkHandle, string assemblyPath, PipeMessage.ExecuteTests executeTests) - { - if (!IsTestAssembly(assemblyPath)) - { - log.Info("Skipping " + assemblyPath + " because it is not a test assembly."); - return; - } - - log.Info("Processing " + assemblyPath); - - var pipeName = Guid.NewGuid().ToString(); - Environment.SetEnvironmentVariable("FIXIE_NAMED_PIPE", pipeName); - - using (var pipeStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte)) - using (var pipe = new TestAdapterPipe(pipeStream)) - using (var process = StartExecution(assemblyPath, frameworkHandle, out var attachmentFailure)) - { - pipeStream.WaitForConnection(); - - pipe.Send(executeTests); - - var recorder = new VsExecutionRecorder(frameworkHandle, assemblyPath); - - PipeMessage.TestStarted? lastTestStarted = null; - - while (true) - { - var messageType = pipe.ReceiveMessageType(); - - if (messageType == typeof(PipeMessage.TestStarted).FullName) - { - var message = pipe.Receive(); - lastTestStarted = message; - recorder.Record(message); - } - else if (messageType == typeof(PipeMessage.TestSkipped).FullName) - { - var testResult = pipe.Receive(); - recorder.Record(testResult); - } - else if (messageType == typeof(PipeMessage.TestPassed).FullName) - { - var testResult = pipe.Receive(); - recorder.Record(testResult); - } - else if (messageType == typeof(PipeMessage.TestFailed).FullName) - { - var testResult = pipe.Receive(); - recorder.Record(testResult); - } - else if (messageType == typeof(PipeMessage.Exception).FullName) - { - var exception = pipe.Receive(); - throw new RunnerException(exception); - } - else if (messageType == typeof(PipeMessage.EndOfPipe).FullName) - { - var endOfPipe = pipe.Receive(); - break; - } - else if (!string.IsNullOrEmpty(messageType)) - { - var body = pipe.ReceiveMessageBody(); - log.Error($"The test runner received an unexpected message of type {messageType}: {body}"); - } - else - { - var exception = new TestProcessExitException(process.TryGetExitCode()); - - if (lastTestStarted != null) - { - recorder.Record(new PipeMessage.TestFailed - { - Test = lastTestStarted.Test, - TestCase = lastTestStarted.Test, - Reason = new PipeMessage.Exception(exception), - DurationInMilliseconds = 0 - }); - } - - throw exception; - } - } - - if (attachmentFailure != null) - { - var exception = attachmentFailure.ThirdPartyTestHostException; - - var reason = new PipeMessage.Exception - { - Type = exception.GetType().FullName!, - Message = attachmentFailure.Message, - StackTrace = exception.StackTrace! - }; - - foreach (var selectedTest in executeTests.Filter) - { - recorder.Record(new PipeMessage.TestFailed - { - Test = selectedTest, - TestCase = selectedTest, - Reason = reason, - DurationInMilliseconds = 0 - }); - } - } - } - } - - static void HandlePoorVsTestImplementationDetails(IRunContext? runContext, IFrameworkHandle frameworkHandle) - { - if (runContext?.KeepAlive == true) - frameworkHandle.EnableShutdownAfterTestRun = true; - } -} \ No newline at end of file diff --git a/src/Fixie.Tests/CaseNameTests.cs b/src/Fixie.Tests/CaseNameTests.cs index 5e0dfabe1..5bb965c87 100644 --- a/src/Fixie.Tests/CaseNameTests.cs +++ b/src/Fixie.Tests/CaseNameTests.cs @@ -36,13 +36,9 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasCh foreach (var c in new[] {'\"', '"', '\''}) await Run(test, c); - foreach (var c in new[] {'\\', '\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v'}) + foreach (var c in new[] {'\\', '\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\e'}) await Run(test, c); - #if NET9_0_OR_GREATER - await Run(test, '\e'); - #endif - foreach (var c in new[] {'\u0000', '\u0085', '\u2028', '\u2029', '\u263A'}) await Run(test, c); @@ -55,20 +51,11 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasCh foreach (var c in new[] {'\U00000000', '\U00000085', '\U00002028', '\U00002029', '\U0000263A'}) await Run(test, c); - foreach (var c in UnicodeEscapedCharacters() - #if NET9_0_OR_GREATER - .Where(c => c != '\e') - #endif - ) + foreach (var c in UnicodeEscapedCharacters()) await Run(test, c); }); var unicodeEscapeExpectations = UnicodeEscapedCharacters() - - #if NET9_0_OR_GREATER - .Where(c => c != '\e') - #endif - .Select(c => $""" CharParametersTestClass.Char('\u{(int)c:X4}') """); @@ -87,10 +74,7 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasCh "CharParametersTestClass.Char('\\r')", "CharParametersTestClass.Char('\\t')", "CharParametersTestClass.Char('\\v')", - - #if NET9_0_OR_GREATER "CharParametersTestClass.Char('\\e')", - #endif "CharParametersTestClass.Char('\\0')", "CharParametersTestClass.Char('\\u0085')", @@ -131,11 +115,7 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasSt { await Run(test, " \' ' \" "); await Run(test, " \\ \0 \a \b "); - await Run(test, " \f \n \r \t \v "); - - #if NET9_0_OR_GREATER - await Run(test, " \e "); - #endif + await Run(test, " \f \n \r \t \v \e "); await Run(test, " \u0000 \u0085 \u2028 \u2029 \u263A "); await Run(test, " \x0000 \x000 \x00 \x0 "); @@ -143,11 +123,6 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasSt await Run(test, " \U00000000 \U00000085 \U00002028 \U00002029 \U0000263A "); foreach (var c in UnicodeEscapedCharacters().Chunk(5) - - #if NET9_0_OR_GREATER - .Select(chunk => chunk.Where(c => c != '\e')) - #endif - .Select(chunk => string.Join(' ', chunk))) await Run(test, c); }); @@ -155,11 +130,7 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasSt ShouldHaveNames(output, "StringParametersTestClass.String(\" ' ' \\\" \")", "StringParametersTestClass.String(\" \\\\ \\0 \\a \\b \")", - "StringParametersTestClass.String(\" \\f \\n \\r \\t \\v \")", - - #if NET9_0_OR_GREATER - "StringParametersTestClass.String(\" \\e \")", - #endif + "StringParametersTestClass.String(\" \\f \\n \\r \\t \\v \\e \")", "StringParametersTestClass.String(\" \\0 \\u0085 \\u2028 \\u2029 ☺ \")", "StringParametersTestClass.String(\" \\0 \\0 \\0 \\0 \")", @@ -169,24 +140,19 @@ public async Task ShouldIncludeEscapeSequencesInNameWhenTheUnderlyingMethodHasSt "StringParametersTestClass.String(\"\\u0006 \\u000E \\u000F \\u0010 \\u0011\")", "StringParametersTestClass.String(\"\\u0012 \\u0013 \\u0014 \\u0015 \\u0016\")", - #if NET8_0 - "StringParametersTestClass.String(\"\\u0017 \\u0018 \\u0019 \\u001A \\u001B\")", - #else - "StringParametersTestClass.String(\"\\u0017 \\u0018 \\u0019 \\u001A\")", - #endif - - "StringParametersTestClass.String(\"\\u001C \\u001D \\u001E \\u001F \\u007F\")", - "StringParametersTestClass.String(\"\\u0080 \\u0081 \\u0082 \\u0083 \\u0084\")", - "StringParametersTestClass.String(\"\\u0085 \\u0086 \\u0087 \\u0088 \\u0089\")", - "StringParametersTestClass.String(\"\\u008A \\u008B \\u008C \\u008D \\u008E\")", - "StringParametersTestClass.String(\"\\u008F \\u0090 \\u0091 \\u0092 \\u0093\")", - "StringParametersTestClass.String(\"\\u0094 \\u0095 \\u0096 \\u0097 \\u0098\")", - "StringParametersTestClass.String(\"\\u0099 \\u009A \\u009B \\u009C \\u009D\")", - "StringParametersTestClass.String(\"\\u009E \\u009F \\u0085 \\u00A0 \\u1680\")", - "StringParametersTestClass.String(\"\\u2000 \\u2001 \\u2002 \\u2003 \\u2004\")", - "StringParametersTestClass.String(\"\\u2005 \\u2006 \\u2007 \\u2008 \\u2009\")", - "StringParametersTestClass.String(\"\\u200A \\u2028 \\u2029 \\u202F \\u205F\")", - "StringParametersTestClass.String(\"\\u3000\")" + "StringParametersTestClass.String(\"\\u0017 \\u0018 \\u0019 \\u001A \\u001C\")", + + "StringParametersTestClass.String(\"\\u001D \\u001E \\u001F \\u007F \\u0080\")", + "StringParametersTestClass.String(\"\\u0081 \\u0082 \\u0083 \\u0084 \\u0085\")", + "StringParametersTestClass.String(\"\\u0086 \\u0087 \\u0088 \\u0089 \\u008A\")", + "StringParametersTestClass.String(\"\\u008B \\u008C \\u008D \\u008E \\u008F\")", + "StringParametersTestClass.String(\"\\u0090 \\u0091 \\u0092 \\u0093 \\u0094\")", + "StringParametersTestClass.String(\"\\u0095 \\u0096 \\u0097 \\u0098 \\u0099\")", + "StringParametersTestClass.String(\"\\u009A \\u009B \\u009C \\u009D \\u009E\")", + "StringParametersTestClass.String(\"\\u009F \\u0085 \\u00A0 \\u1680 \\u2000\")", + "StringParametersTestClass.String(\"\\u2001 \\u2002 \\u2003 \\u2004 \\u2005\")", + "StringParametersTestClass.String(\"\\u2006 \\u2007 \\u2008 \\u2009 \\u200A\")", + "StringParametersTestClass.String(\"\\u2028 \\u2029 \\u202F \\u205F \\u3000\")" ); } @@ -298,7 +264,8 @@ static IEnumerable UnicodeEscapedCharacters() // '\uHHHH' hex escape sequences. for (char c = '\u0001'; c <= '\u0006'; c++) yield return c; - for (char c = '\u000E'; c <= '\u001F'; c++) yield return c; + for (char c = '\u000E'; c <= '\u001A'; c++) yield return c; + for (char c = '\u001C'; c <= '\u001F'; c++) yield return c; yield return '\u007F'; for (char c = '\u0080'; c <= '\u009F'; c++) yield return c; diff --git a/src/Fixie.Tests/Console/CommandLineTests.cs b/src/Fixie.Tests/Console/CommandLineTests.cs index 7030350bb..ed860cf28 100644 --- a/src/Fixie.Tests/Console/CommandLineTests.cs +++ b/src/Fixie.Tests/Console/CommandLineTests.cs @@ -7,11 +7,11 @@ public class CommandLineTests public void ShouldPartitionRunnerArgumentsFromCustomArguments() { CommandLine.Partition([ - "Example.Tests", "--configuration", "Release", "--framework", "net8.0", + "Example.Tests", "--configuration", "Release", "--framework", "net9.0", "--", "customA", "customB", "customC" ], out var runnerArguments, out var customArguments); - runnerArguments.ShouldMatch(["Example.Tests", "--configuration", "Release", "--framework", "net8.0"]); + runnerArguments.ShouldMatch(["Example.Tests", "--configuration", "Release", "--framework", "net9.0"]); customArguments.ShouldMatch(["customA", "customB", "customC"]); CommandLine.Partition(["Example.Tests", "--", "custom"], out runnerArguments, out customArguments); diff --git a/src/Fixie.Tests/Fixie.Tests.csproj b/src/Fixie.Tests/Fixie.Tests.csproj index 3cf5bdaf9..360dd718a 100644 --- a/src/Fixie.Tests/Fixie.Tests.csproj +++ b/src/Fixie.Tests/Fixie.Tests.csproj @@ -3,7 +3,7 @@ - net8.0;net9.0 + net9.0 @@ -13,7 +13,6 @@ - diff --git a/src/Fixie.Tests/Internal/PipeMessageSerializationTests.cs b/src/Fixie.Tests/Internal/PipeMessageSerializationTests.cs deleted file mode 100644 index 55a6e7118..000000000 --- a/src/Fixie.Tests/Internal/PipeMessageSerializationTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Fixie.Internal; -using Fixie.Tests.Reports; - -namespace Fixie.Tests.Internal; - -public class PipeMessageSerializationTests : MessagingTests -{ - public void ShouldSerializeDiscoverTestsMessage() - { - Expect(new PipeMessage.DiscoverTests(), "{}"); - } - - public void ShouldSerializeExecuteTestsMessage() - { - Expect(new PipeMessage.ExecuteTests { Filter = [] }, - "{\"Filter\":[]}"); - - Expect(new PipeMessage.ExecuteTests { Filter = - [ - TestClass + ".Pass", - GenericTestClass + ".ShouldBeString" - ] }, - "{\"Filter\":[\"Fixie.Tests.Reports.MessagingTests\\u002BSampleTestClass.Pass\",\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\"]}"); - } - - public void ShouldSerializeTestDiscoveredMessage() - { - Expect(new PipeMessage.TestDiscovered { Test = TestClass + ".Pass" }, - "{\"Test\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleTestClass.Pass\"}"); - } - - public void ShouldSerializeTestStartedMessage() - { - Expect(new PipeMessage.TestStarted { Test = TestClass + ".Pass" }, - "{\"Test\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleTestClass.Pass\"}"); - } - - public void ShouldSerializeTestSkippedMessage() - { - Expect(new PipeMessage.TestSkipped - { - Reason = "⚠ Skipped!", - Test = GenericTestClass + ".ShouldBeString", - TestCase = GenericTestClass + ".ShouldBeString(123)", - DurationInMilliseconds = 123.456d - }, - "{\"Reason\":\"\\u26A0 Skipped!\",\"Test\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\",\"TestCase\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\\u003CSystem.Int32\\u003E(123)\",\"DurationInMilliseconds\":123.456}"); - } - - public void ShouldSerializeTestPassedMessage() - { - Expect(new PipeMessage.TestPassed - { - Test = GenericTestClass + ".ShouldBeString", - TestCase = GenericTestClass + ".ShouldBeString(123)", - DurationInMilliseconds = 123.456d - }, - "{\"Test\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\",\"TestCase\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\\u003CSystem.Int32\\u003E(123)\",\"DurationInMilliseconds\":123.456}"); - } - - public void ShouldSerializeTestFailedMessage() - { - var at = At("ShouldBeString[T](T genericArgument)").Replace("\\", "\\\\"); - - Expect(new PipeMessage.TestFailed - { - Reason = new PipeMessage.Exception - { - Type = "Fixie.Assertions.AssertException", - Message = "Expected: System.String\nActual: System.Int32", - StackTrace = At("ShouldBeString[T](T genericArgument)") - }, - Test = GenericTestClass + ".ShouldBeString", - TestCase = GenericTestClass + ".ShouldBeString(123)", - DurationInMilliseconds = 123.456d - }, - "{\"Reason\":{\"Type\":\"Fixie.Assertions.AssertException\",\"Message\":\"Expected: System.String\\nActual: System.Int32\",\"StackTrace\":\"" + at + "\"},\"Test\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\",\"TestCase\":\"Fixie.Tests.Reports.MessagingTests\\u002BSampleGenericTestClass.ShouldBeString\\u003CSystem.Int32\\u003E(123)\",\"DurationInMilliseconds\":123.456}"); - } - - public void ShouldSerializeExceptionMessage() - { - var at = At("ShouldBeString[T](T genericArgument)").Replace("\\", "\\\\"); - - Expect(new PipeMessage.Exception - { - Type = "Fixie.Assertions.AssertException", - Message = "Expected: System.String\nActual: System.Int32", - StackTrace = At("ShouldBeString[T](T genericArgument)") - }, - "{\"Type\":\"Fixie.Assertions.AssertException\",\"Message\":\"Expected: System.String\\nActual: System.Int32\",\"StackTrace\":\"" + at + "\"}"); - } - - public void ShouldSerializeEndOfPipeMessage() - { - Expect(new PipeMessage.EndOfPipe(), "{}"); - } - - public void ShouldThrowForNullDeserialization() - { - var nullMessage = () => PipeMessage.Deserialize("null"); - nullMessage.ShouldThrow("Message of type Fixie.Internal.PipeMessage+TestFailed was unexpectedly null."); - } - - static void Expect(TMessage message, string expectedJson) - { - PipeMessage.Serialize(PipeMessage.Deserialize(expectedJson)).ShouldBe(expectedJson); - PipeMessage.Serialize(message).ShouldBe(expectedJson); - } -} \ No newline at end of file diff --git a/src/Fixie.Tests/TestAdapter/SourceLocationProviderTests.cs b/src/Fixie.Tests/Internal/SourceLocationProviderTests.cs similarity index 98% rename from src/Fixie.Tests/TestAdapter/SourceLocationProviderTests.cs rename to src/Fixie.Tests/Internal/SourceLocationProviderTests.cs index fc4c742c1..cbb430697 100644 --- a/src/Fixie.Tests/TestAdapter/SourceLocationProviderTests.cs +++ b/src/Fixie.Tests/Internal/SourceLocationProviderTests.cs @@ -1,7 +1,7 @@ -using Fixie.TestAdapter; +using Fixie.Internal; using static Fixie.Tests.Utility; -namespace Fixie.Tests.TestAdapter; +namespace Fixie.Tests.Internal; public class SourceLocationProviderTests { diff --git a/src/Fixie.Tests/TestAdapter/SourceLocationSamples.cs b/src/Fixie.Tests/Internal/SourceLocationSamples.cs similarity index 97% rename from src/Fixie.Tests/TestAdapter/SourceLocationSamples.cs rename to src/Fixie.Tests/Internal/SourceLocationSamples.cs index 57b47a603..abae0c284 100644 --- a/src/Fixie.Tests/TestAdapter/SourceLocationSamples.cs +++ b/src/Fixie.Tests/Internal/SourceLocationSamples.cs @@ -1,4 +1,4 @@ -namespace Fixie.Tests.TestAdapter; +namespace Fixie.Tests.Internal; // Avoid changes in this file that would move the well-known line numbers indicated in comments. // Otherwise, these comments and the associated assertions would need to be updated together diff --git a/src/Fixie.Tests/TestAdapter/TestCaseMappingAssertions.cs b/src/Fixie.Tests/TestAdapter/TestCaseMappingAssertions.cs deleted file mode 100644 index f0bf8689d..000000000 --- a/src/Fixie.Tests/TestAdapter/TestCaseMappingAssertions.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; - -namespace Fixie.Tests.TestAdapter; - -public static class TestCaseMappingAssertions -{ - public static void ShouldBeDiscoveryTimeTest(this TestCase test, string expectedFullyQualifiedName, string expectedSource) - { - ShouldHaveIdentity(test, expectedFullyQualifiedName, expectedSource); - - ShouldUseDefaultsForUnmappedProperties(test); - - ShouldHaveSourceLocation(test); - } - - public static void ShouldBeDiscoveryTimeTestMissingSourceLocation(this TestCase test, string expectedFullyQualifiedName, string expectedSource) - { - ShouldHaveIdentity(test, expectedFullyQualifiedName, expectedSource); - - ShouldUseDefaultsForUnmappedProperties(test); - - ShouldNotHaveSourceLocation(test); - } - - public static void ShouldBeExecutionTimeTest(this TestCase test, string expectedFullyQualifiedName, string expectedSource) - { - ShouldHaveIdentity(test, expectedFullyQualifiedName, expectedSource); - - ShouldUseDefaultsForUnmappedProperties(test); - - //Source locations are a discovery-time concern. - ShouldNotHaveSourceLocation(test); - } - - static void ShouldHaveIdentity(TestCase test, string expectedFullyQualifiedName, string expectedSource) - { - test.FullyQualifiedName.ShouldBe(expectedFullyQualifiedName); - test.DisplayName.ShouldBe(test.FullyQualifiedName); - test.Source.ShouldBe(expectedSource); - } - - static void ShouldUseDefaultsForUnmappedProperties(TestCase test) - { - test.Traits.ToArray().ShouldMatch([]); - test.ExecutorUri.ToString().ShouldBe("executor://fixie.testadapter/"); - } - - static void ShouldHaveSourceLocation(TestCase test) - { - test.CodeFilePath.ShouldNotBeNull(); - test.CodeFilePath.EndsWith("MessagingTests.cs").ShouldBe(true); - test.LineNumber.ShouldSatisfy(x => x > 0); - } - - static void ShouldNotHaveSourceLocation(TestCase test) - { - test.CodeFilePath.ShouldBe(null); - test.LineNumber.ShouldBe(-1); - } -} \ No newline at end of file diff --git a/src/Fixie.Tests/TestAdapter/VsDiscoveryRecorderTests.cs b/src/Fixie.Tests/TestAdapter/VsDiscoveryRecorderTests.cs deleted file mode 100644 index 7fcbb26d1..000000000 --- a/src/Fixie.Tests/TestAdapter/VsDiscoveryRecorderTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Fixie.Internal; -using Fixie.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using Fixie.Tests.Reports; -using static System.IO.Directory; - -namespace Fixie.Tests.TestAdapter; - -public class VsDiscoveryRecorderTests : MessagingTests -{ - public void ShouldMapDiscoveredTestsToVsTestDiscoverySink() - { - var assemblyPath = typeof(MessagingTests).Assembly.Location; - - var log = new StubMessageLogger(); - var discoverySink = new StubTestCaseDiscoverySink(); - - var vsDiscoveryRecorder = new VsDiscoveryRecorder(log, discoverySink, assemblyPath); - - RecordAnticipatedPipeMessages(vsDiscoveryRecorder); - - log.Messages.ShouldMatch([]); - - discoverySink.TestCases.ItemsShouldSatisfy([ - x => x.ShouldBeDiscoveryTimeTest(TestClass + ".Fail", assemblyPath), - x => x.ShouldBeDiscoveryTimeTest(TestClass + ".FailByAssertion", assemblyPath), - x => x.ShouldBeDiscoveryTimeTestMissingSourceLocation(TestClass + ".Pass", assemblyPath), - x => x.ShouldBeDiscoveryTimeTest(TestClass + ".Skip", assemblyPath), - x => x.ShouldBeDiscoveryTimeTest(GenericTestClass + ".ShouldBeString", assemblyPath) - ]); - } - - public void ShouldDefaultSourceLocationPropertiesWhenSourceInspectionThrows() - { - const string invalidAssemblyPath = "assembly.path.dll"; - - var log = new StubMessageLogger(); - var discoverySink = new StubTestCaseDiscoverySink(); - - var discoveryRecorder = new VsDiscoveryRecorder(log, discoverySink, invalidAssemblyPath); - - RecordAnticipatedPipeMessages(discoveryRecorder); - - var expectedError = - $"Error: {typeof(FileNotFoundException).FullName}: " + - $"Could not find file '{Path.Combine(GetCurrentDirectory(), invalidAssemblyPath)}'."; - log.Messages.ItemsShouldSatisfy([ - x => x.Contains(expectedError).ShouldBe(true), - x => x.Contains(expectedError).ShouldBe(true), - x => x.Contains(expectedError).ShouldBe(true), - x => x.Contains(expectedError).ShouldBe(true), - x => x.Contains(expectedError).ShouldBe(true) - ]); - - discoverySink.TestCases.ItemsShouldSatisfy([ - x => x.ShouldBeDiscoveryTimeTestMissingSourceLocation(TestClass + ".Fail", invalidAssemblyPath), - x => x.ShouldBeDiscoveryTimeTestMissingSourceLocation(TestClass + ".FailByAssertion", invalidAssemblyPath), - x => x.ShouldBeDiscoveryTimeTestMissingSourceLocation(TestClass + ".Pass", invalidAssemblyPath), - x => x.ShouldBeDiscoveryTimeTestMissingSourceLocation(TestClass + ".Skip", invalidAssemblyPath), - x => x.ShouldBeDiscoveryTimeTestMissingSourceLocation(GenericTestClass + ".ShouldBeString", invalidAssemblyPath) - ]); - } - - void RecordAnticipatedPipeMessages(VsDiscoveryRecorder vsDiscoveryRecorder) - { - vsDiscoveryRecorder.Record(new PipeMessage.TestDiscovered - { - Test = TestClass + ".Fail" - }); - - vsDiscoveryRecorder.Record(new PipeMessage.TestDiscovered - { - Test = TestClass + ".FailByAssertion" - }); - - vsDiscoveryRecorder.Record(new PipeMessage.TestDiscovered - { - Test = TestClass + ".Pass" - }); - - vsDiscoveryRecorder.Record(new PipeMessage.TestDiscovered - { - Test = TestClass + ".Skip" - }); - - vsDiscoveryRecorder.Record(new PipeMessage.TestDiscovered - { - Test = GenericTestClass + ".ShouldBeString" - }); - } - - class StubMessageLogger : IMessageLogger - { - public List Messages { get; } = []; - - public void SendMessage(TestMessageLevel testMessageLevel, string message) - => Messages.Add($"{testMessageLevel}: {message}"); - } - - class StubTestCaseDiscoverySink : ITestCaseDiscoverySink - { - public List TestCases { get; } = []; - - public void SendTestCase(TestCase discoveredTest) - => TestCases.Add(discoveredTest); - } -} \ No newline at end of file diff --git a/src/Fixie.Tests/TestAdapter/VsExecutionRecorderTests.cs b/src/Fixie.Tests/TestAdapter/VsExecutionRecorderTests.cs deleted file mode 100644 index b03110660..000000000 --- a/src/Fixie.Tests/TestAdapter/VsExecutionRecorderTests.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Fixie.TestAdapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using Fixie.Internal; -using Fixie.Tests.Reports; -using static System.Environment; - -namespace Fixie.Tests.TestAdapter; - -using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; - -public class VsExecutionRecorderTests : MessagingTests -{ - public void ShouldMapMessagesToVsTestExecutionRecorder() - { - const string assemblyPath = "assembly.path.dll"; - var recorder = new StubExecutionRecorder(); - - var vsExecutionRecorder = new VsExecutionRecorder(recorder, assemblyPath); - - RecordAnticipatedPipeMessages(vsExecutionRecorder); - - var messages = recorder.Messages; - - messages.Count.ShouldBe(13); - - foreach (var message in messages) - { - if (message is TestResult result) - { - result.Traits.ToArray().ShouldMatch([]); - result.Attachments.ShouldMatch([]); - result.ComputerName.ShouldBe(MachineName); - } - } - - var failStart = (TestCase)messages[0]; - var fail = (TestResult)messages[1]; - - var failByAssertionStart = (TestCase)messages[2]; - var failByAssertion = (TestResult)messages[3]; - - var passStart = (TestCase)messages[4]; - var pass = (TestResult)messages[5]; - - var skip = (TestResult)messages[6]; - - var shouldBeStringPassAStart = (TestCase)messages[7]; - var shouldBeStringPassA = (TestResult)messages[8]; - - var shouldBeStringPassBStart = (TestCase)messages[9]; - var shouldBeStringPassB = (TestResult)messages[10]; - - var shouldBeStringFailStart = (TestCase)messages[11]; - var shouldBeStringFail = (TestResult)messages[12]; - - failStart.ShouldBeExecutionTimeTest(TestClass + ".Fail", assemblyPath); - - fail.TestCase.ShouldBeExecutionTimeTest(TestClass+".Fail", assemblyPath); - fail.TestCase.DisplayName.ShouldBe(TestClass+".Fail"); - fail.Outcome.ShouldBe(TestOutcome.Failed); - fail.ErrorMessage.ShouldBe("'Fail' failed!"); - fail.ErrorStackTrace - .ShouldBeStackTrace(["Fixie.Tests.FailureException", At("Fail()")]); - fail.DisplayName.ShouldBe(TestClass+".Fail"); - fail.Messages.ShouldMatch([]); - fail.Duration.ShouldBe(TimeSpan.FromMilliseconds(102)); - - failByAssertionStart.ShouldBeExecutionTimeTest(TestClass + ".FailByAssertion", assemblyPath); - - failByAssertion.TestCase.ShouldBeExecutionTimeTest(TestClass+".FailByAssertion", assemblyPath); - failByAssertion.TestCase.DisplayName.ShouldBe(TestClass+".FailByAssertion"); - failByAssertion.Outcome.ShouldBe(TestOutcome.Failed); - failByAssertion.ErrorMessage.ShouldBe(xShouldBe2ButWas1); - failByAssertion.ErrorStackTrace - .ShouldBeStackTrace(["Fixie.Assertions.AssertException", At("FailByAssertion()")]); - failByAssertion.DisplayName.ShouldBe(TestClass+".FailByAssertion"); - failByAssertion.Messages.ShouldMatch([]); - failByAssertion.Duration.ShouldBe(TimeSpan.FromMilliseconds(103)); - - passStart.ShouldBeExecutionTimeTest(TestClass + ".Pass", assemblyPath); - - pass.TestCase.ShouldBeExecutionTimeTest(TestClass+".Pass", assemblyPath); - pass.TestCase.DisplayName.ShouldBe(TestClass+".Pass"); - pass.Outcome.ShouldBe(TestOutcome.Passed); - pass.ErrorMessage.ShouldBe(null); - pass.ErrorStackTrace.ShouldBe(null); - pass.DisplayName.ShouldBe(TestClass+".Pass"); - pass.Messages.ShouldMatch([]); - pass.Duration.ShouldBe(TimeSpan.FromMilliseconds(104)); - - skip.TestCase.ShouldBeExecutionTimeTest(TestClass+".Skip", assemblyPath); - skip.TestCase.DisplayName.ShouldBe(TestClass+".Skip"); - skip.Outcome.ShouldBe(TestOutcome.Skipped); - skip.ErrorMessage.ShouldBe("⚠ Skipped with attribute."); - skip.ErrorStackTrace.ShouldBe(null); - skip.DisplayName.ShouldBe(TestClass+".Skip"); - skip.Messages.ShouldMatch([]); - skip.Duration.ShouldBe(TimeSpan.Zero); - - shouldBeStringPassAStart.ShouldBeExecutionTimeTest(GenericTestClass + ".ShouldBeString", assemblyPath); - - shouldBeStringPassA.TestCase.ShouldBeExecutionTimeTest(GenericTestClass+".ShouldBeString", assemblyPath); - shouldBeStringPassA.TestCase.DisplayName.ShouldBe(GenericTestClass+".ShouldBeString"); - shouldBeStringPassA.Outcome.ShouldBe(TestOutcome.Passed); - shouldBeStringPassA.ErrorMessage.ShouldBe(null); - shouldBeStringPassA.ErrorStackTrace.ShouldBe(null); - shouldBeStringPassA.DisplayName.ShouldBe(GenericTestClass+".ShouldBeString(\"A\")"); - shouldBeStringPassA.Messages.ShouldMatch([]); - shouldBeStringPassA.Duration.ShouldBe(TimeSpan.FromMilliseconds(105)); - - shouldBeStringPassBStart.ShouldBeExecutionTimeTest(GenericTestClass + ".ShouldBeString", assemblyPath); - - shouldBeStringPassB.TestCase.ShouldBeExecutionTimeTest(GenericTestClass+".ShouldBeString", assemblyPath); - shouldBeStringPassB.TestCase.DisplayName.ShouldBe(GenericTestClass+".ShouldBeString"); - shouldBeStringPassB.Outcome.ShouldBe(TestOutcome.Passed); - shouldBeStringPassB.ErrorMessage.ShouldBe(null); - shouldBeStringPassB.ErrorStackTrace.ShouldBe(null); - shouldBeStringPassB.DisplayName.ShouldBe(GenericTestClass+".ShouldBeString(\"B\")"); - shouldBeStringPassB.Messages.ShouldMatch([]); - shouldBeStringPassB.Duration.ShouldBe(TimeSpan.FromMilliseconds(106)); - - shouldBeStringFailStart.ShouldBeExecutionTimeTest(GenericTestClass + ".ShouldBeString", assemblyPath); - - shouldBeStringFail.TestCase.ShouldBeExecutionTimeTest(GenericTestClass+".ShouldBeString", assemblyPath); - shouldBeStringFail.TestCase.DisplayName.ShouldBe(GenericTestClass+".ShouldBeString"); - shouldBeStringFail.Outcome.ShouldBe(TestOutcome.Failed); - shouldBeStringFail.ErrorMessage.ShouldBe(genericArgumentShouldMatchStringButWasInt); - shouldBeStringFail.ErrorStackTrace - .ShouldBeStackTrace([ - "Fixie.Assertions.AssertException", - At("ShouldBeString[T](T genericArgument)") - ]); - shouldBeStringFail.DisplayName.ShouldBe(GenericTestClass+".ShouldBeString(123)"); - shouldBeStringFail.Messages.ShouldMatch([]); - shouldBeStringFail.Duration.ShouldBe(TimeSpan.FromMilliseconds(107)); - } - - void RecordAnticipatedPipeMessages(VsExecutionRecorder vsExecutionRecorder) - { - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestStarted - { - Test = TestClass + ".Fail" - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestFailed - { - Test = TestClass + ".Fail", - TestCase = TestClass + ".Fail", - DurationInMilliseconds = 102, - Reason = new PipeMessage.Exception - { - Type = "Fixie.Tests.FailureException", - Message = "'Fail' failed!", - StackTrace = At("Fail()") - } - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestStarted - { - Test = TestClass + ".FailByAssertion" - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestFailed - { - Test = TestClass + ".FailByAssertion", - TestCase = TestClass + ".FailByAssertion", - DurationInMilliseconds = 103, - Reason = new PipeMessage.Exception - { - Type = "Fixie.Assertions.AssertException", - Message = xShouldBe2ButWas1, - StackTrace = At("FailByAssertion()") - } - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestStarted - { - Test = TestClass + ".Pass" - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestPassed - { - Test = TestClass+".Pass", - TestCase = TestClass+".Pass", - DurationInMilliseconds = 104 - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestSkipped - { - Test =TestClass+".Skip", - TestCase = TestClass+".Skip", - DurationInMilliseconds = 0, - Reason = "⚠ Skipped with attribute." - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestStarted - { - Test = GenericTestClass + ".ShouldBeString" - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestPassed - { - Test = GenericTestClass+".ShouldBeString", - TestCase = GenericTestClass+".ShouldBeString(\"A\")", - DurationInMilliseconds = 105 - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestStarted - { - Test = GenericTestClass + ".ShouldBeString" - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestPassed - { - Test = GenericTestClass+".ShouldBeString", - TestCase = GenericTestClass+".ShouldBeString(\"B\")", - DurationInMilliseconds = 106 - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestStarted - { - Test = GenericTestClass + ".ShouldBeString" - })); - - vsExecutionRecorder.Record(Deserialized(new PipeMessage.TestFailed - { - Test = GenericTestClass+".ShouldBeString", - TestCase = GenericTestClass+".ShouldBeString(123)", - DurationInMilliseconds = 107, - Reason = new PipeMessage.Exception - { - Type = "Fixie.Assertions.AssertException", - Message = genericArgumentShouldMatchStringButWasInt, - StackTrace = At("ShouldBeString[T](T genericArgument)") - } - })); - } - - static T Deserialized(T original) - { - // Because the inter-process communication between the VsTest process - // and the test assembly process is not exercised in these single-process - // tests, put a given sample message through the same serialization round - // trip that would be applied at runtime, in order to detect data loss. - - return PipeMessage.Deserialize(PipeMessage.Serialize(original)); - } - - class StubExecutionRecorder : ITestExecutionRecorder - { - public List Messages { get; } = []; - - public void RecordStart(TestCase testCase) - => Messages.Add(testCase); - - public void RecordResult(TestResult testResult) - => Messages.Add(testResult); - - public void SendMessage(TestMessageLevel testMessageLevel, string message) - => throw new NotImplementedException(); - - public void RecordEnd(TestCase testCase, TestOutcome outcome) - => throw new NotImplementedException(); - - public void RecordAttachments(IList attachmentSets) - => throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/Fixie.Tests/Utility.cs b/src/Fixie.Tests/Utility.cs index de1af9c1b..7c63a49ee 100644 --- a/src/Fixie.Tests/Utility.cs +++ b/src/Fixie.Tests/Utility.cs @@ -9,13 +9,7 @@ public static class Utility public static TestEnvironment GetTestEnvironment(TextWriter console) => new(typeof(TestProject).Assembly, null, console, customArguments: []); - public const string TargetFrameworkVersion = - #if NET8_0 - "8.0" - #elif NET9_0 - "9.0" - #endif - ; + public const string TargetFrameworkVersion = "9.0"; public static string FullName() { diff --git a/src/Fixie.sln b/src/Fixie.sln deleted file mode 100644 index d1cddc28c..000000000 --- a/src/Fixie.sln +++ /dev/null @@ -1,50 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34309.116 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fixie", "Fixie\Fixie.csproj", "{54B62C1C-3C2A-41A6-9D19-008F814D1791}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fixie.Tests", "Fixie.Tests\Fixie.Tests.csproj", "{79891344-547E-4F8D-959B-07684D09322D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fixie.TestAdapter", "Fixie.TestAdapter\Fixie.TestAdapter.csproj", "{50D2B103-EF46-4438-9720-08C93B6EF714}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fixie.Console", "Fixie.Console\Fixie.Console.csproj", "{04605B99-6D68-4BC5-8239-DC2EF4498B6F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9F2B08BE-2199-4F4F-95BD-1E7F19CFFF61}" - ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - ..\nuget.config = ..\nuget.config - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {54B62C1C-3C2A-41A6-9D19-008F814D1791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54B62C1C-3C2A-41A6-9D19-008F814D1791}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54B62C1C-3C2A-41A6-9D19-008F814D1791}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54B62C1C-3C2A-41A6-9D19-008F814D1791}.Release|Any CPU.Build.0 = Release|Any CPU - {79891344-547E-4F8D-959B-07684D09322D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79891344-547E-4F8D-959B-07684D09322D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79891344-547E-4F8D-959B-07684D09322D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79891344-547E-4F8D-959B-07684D09322D}.Release|Any CPU.Build.0 = Release|Any CPU - {50D2B103-EF46-4438-9720-08C93B6EF714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50D2B103-EF46-4438-9720-08C93B6EF714}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50D2B103-EF46-4438-9720-08C93B6EF714}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50D2B103-EF46-4438-9720-08C93B6EF714}.Release|Any CPU.Build.0 = Release|Any CPU - {04605B99-6D68-4BC5-8239-DC2EF4498B6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04605B99-6D68-4BC5-8239-DC2EF4498B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04605B99-6D68-4BC5-8239-DC2EF4498B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04605B99-6D68-4BC5-8239-DC2EF4498B6F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {03391B3B-84B6-495B-AE0C-32C802774390} - EndGlobalSection -EndGlobal diff --git a/src/Fixie.slnx b/src/Fixie.slnx new file mode 100644 index 000000000..dd192d84b --- /dev/null +++ b/src/Fixie.slnx @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Fixie/Fixie.csproj b/src/Fixie/Fixie.csproj index 837d7e56f..ad2c06922 100644 --- a/src/Fixie/Fixie.csproj +++ b/src/Fixie/Fixie.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0 + net9.0 Ergonomic Testing for .NET true README.md @@ -15,4 +15,8 @@ + + + + \ No newline at end of file diff --git a/src/Fixie/Internal/CaseNameBuilder.cs b/src/Fixie/Internal/CaseNameBuilder.cs index 1950d1a21..cfe944972 100644 --- a/src/Fixie/Internal/CaseNameBuilder.cs +++ b/src/Fixie/Internal/CaseNameBuilder.cs @@ -76,11 +76,7 @@ static string Escape(char ch, Literal literal) => '\v' => @"\v", '\f' => @"\f", '\r' => @"\r", - - #if NET9_0_OR_GREATER '\e' => @"\e", - #endif - ' ' => " ", '\"' => literal == Literal.String ? @"\""" : char.ToString(ch), '\'' => literal == Literal.Character ? @"\'" : char.ToString(ch), diff --git a/src/Fixie/Internal/EntryPoint.cs b/src/Fixie/Internal/EntryPoint.cs index a687bc4c6..7262d9981 100644 --- a/src/Fixie/Internal/EntryPoint.cs +++ b/src/Fixie/Internal/EntryPoint.cs @@ -1,5 +1,4 @@ -using System.IO.Pipes; -using System.Reflection; +using System.Reflection; using Fixie.Reports; using static System.Environment; using static Fixie.Internal.Maybe; @@ -25,64 +24,13 @@ public static async Task Main(Assembly assembly, string[] customArguments) try { - var pipeName = GetEnvironmentVariable("FIXIE_NAMED_PIPE"); + var reports = DefaultReports(environment).ToArray(); - if (pipeName == null) - { - var reports = DefaultReports(environment).ToArray(); + var pattern = GetEnvironmentVariable("FIXIE_TESTS_PATTERN"); - var pattern = GetEnvironmentVariable("FIXIE_TESTS_PATTERN"); - - return pattern == null - ? (int) await Run(environment, reports, async runner => await runner.Run()) - : (int) await Run(environment, reports, async runner => await runner.Run(new TestPattern(pattern))); - } - - using var pipeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut); - using var pipe = new TestAdapterPipe(pipeStream); - - pipeStream.Connect(); - pipeStream.ReadMode = PipeTransmissionMode.Byte; - - var testAdapterReport = new TestAdapterReport(pipe); - - var exitCode = ExitCode.Success; - - try - { - var messageType = pipe.ReceiveMessageType(); - - if (messageType == typeof(PipeMessage.DiscoverTests).FullName) - { - var discoverTests = pipe.Receive(); - await DiscoverMethods(environment, testAdapterReport); - } - else if (messageType == typeof(PipeMessage.ExecuteTests).FullName) - { - var executeTests = pipe.Receive(); - - var reports = new IReport[] { testAdapterReport }; - - exitCode = executeTests.Filter.Length == 0 - ? await Run(environment, reports, async runner => await runner.Run()) - : await Run(environment, reports, async runner => await runner.Run([..executeTests.Filter])); - } - else - { - var body = pipe.ReceiveMessageBody(); - throw new Exception($"Test assembly received unexpected message of type {messageType}: {body}"); - } - } - catch (Exception exception) - { - pipe.Send(exception); - } - finally - { - pipe.Send(); - } - - return (int)exitCode; + return pattern == null + ? (int) await Run(environment, reports, async runner => await runner.Run()) + : (int) await Run(environment, reports, async runner => await runner.Run(new TestPattern(pattern))); } catch (Exception exception) { @@ -93,12 +41,6 @@ public static async Task Main(Assembly assembly, string[] customArguments) } } - static async Task DiscoverMethods(TestEnvironment environment, TestAdapterReport testAdapterReport) - { - var runner = new Runner(environment, testAdapterReport); - await runner.Discover(); - } - static async Task Run(TestEnvironment environment, IReport[] reports, Func> run) { var runner = new Runner(environment, reports); diff --git a/src/Fixie/Internal/PipeMessage.cs b/src/Fixie/Internal/PipeMessage.cs deleted file mode 100644 index dea8a91da..000000000 --- a/src/Fixie/Internal/PipeMessage.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using Fixie.Reports; - -namespace Fixie.Internal; - -static class PipeMessage -{ - public static string Serialize(TMessage message) - { - return JsonSerializer.Serialize(message); - } - - public static TMessage Deserialize(string json) - { - var message = JsonSerializer.Deserialize(json); - - if (message == null) - throw new System.Exception($"Message of type {typeof(TMessage).FullName} was unexpectedly null."); - - return message; - } - - public class DiscoverTests { } - - public class ExecuteTests - { - public required string[] Filter { get; init; } - } - - public class TestDiscovered - { - public required string Test { get; init; } - } - - public class TestStarted - { - public required string Test { get; init; } - } - - public abstract class TestCompleted - { - public required string Test { get; init; } - public required string TestCase { get; init; } - public required double DurationInMilliseconds { get; init; } - } - - public class TestSkipped : TestCompleted - { - public required string Reason { get; init; } - } - - public class TestPassed : TestCompleted - { - } - - public class TestFailed : TestCompleted - { - public required Exception Reason { get; init; } - } - - public class Exception - { - public Exception() - { - } - - [SetsRequiredMembers] - public Exception(System.Exception exception) - { - Type = exception.GetType().FullName!; - Message = exception.Message; - StackTrace = exception.StackTraceSummary(); - } - - public required string Type { get; init; } - public required string Message { get; init; } - public required string StackTrace { get; init; } - } - - public class EndOfPipe { } -} \ No newline at end of file diff --git a/src/Fixie/Internal/SourceLocation.cs b/src/Fixie/Internal/SourceLocation.cs new file mode 100644 index 000000000..440ec7b0c --- /dev/null +++ b/src/Fixie/Internal/SourceLocation.cs @@ -0,0 +1,7 @@ +namespace Fixie.Internal; + +class SourceLocation +{ + public required string CodeFilePath { get; init; } + public required int LineNumber { get; init; } +} \ No newline at end of file diff --git a/src/Fixie.TestAdapter/SourceLocationProvider.cs b/src/Fixie/Internal/SourceLocationProvider.cs similarity index 95% rename from src/Fixie.TestAdapter/SourceLocationProvider.cs rename to src/Fixie/Internal/SourceLocationProvider.cs index 71c9e63bb..21106aa2e 100644 --- a/src/Fixie.TestAdapter/SourceLocationProvider.cs +++ b/src/Fixie/Internal/SourceLocationProvider.cs @@ -4,7 +4,7 @@ using Mono.Cecil.Cil; using Mono.Cecil.Rocks; -namespace Fixie.TestAdapter; +namespace Fixie.Internal; class SourceLocationProvider { @@ -80,7 +80,11 @@ static Dictionary MethodLocations(TypeDefinition type) var sequencePoint = FirstOrDefaultSequencePoint(method); if (sequencePoint != null) - return new SourceLocation(sequencePoint.Document.Url, sequencePoint.StartLine); + return new SourceLocation + { + CodeFilePath = sequencePoint.Document.Url, + LineNumber = sequencePoint.StartLine + }; return null; } diff --git a/src/Fixie/Internal/TestAdapterPipe.cs b/src/Fixie/Internal/TestAdapterPipe.cs deleted file mode 100644 index 7ff5b1685..000000000 --- a/src/Fixie/Internal/TestAdapterPipe.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.IO.Pipes; -using System.Text; - -namespace Fixie.Internal; - -class TestAdapterPipe : IDisposable -{ - readonly StreamReader reader; - readonly StreamWriter writer; - - public TestAdapterPipe(PipeStream pipeStream) - { - // Normal attempts to call dispose on this reader - // and writer cause the underlying PipeStream to - // throw ObjectDisposedException when the original - // PipeStream's 'using' block ends. Here we allow - // the reader and writer to be disposed of, ignoring - // the PipeStream itself by leaving that open. - // Then, the original using block for the PipeStream - // is trusted to own that cleanup. - - reader = new StreamReader(pipeStream, leaveOpen: true); - writer = new StreamWriter(pipeStream, leaveOpen: true); - } - - const string EndOfMessage = "--End of Message--"; - - public string? ReceiveMessageType() - { - return reader.ReadLine(); - } - - public TMessage Receive() - { - return PipeMessage.Deserialize(ReceiveMessageBody()); - } - - public string ReceiveMessageBody() - { - var lines = new StringBuilder(); - - while (true) - { - var line = reader.ReadLine(); - - if (line == null || line == EndOfMessage) - break; - - lines.AppendLine(line); - } - - return lines.ToString(); - } - - public void Send() where TMessage: new() - { - Send(new TMessage()); - } - - public void Send(Exception exception) - { - Send(new PipeMessage.Exception(exception)); - } - - public void Send(TMessage message) - { - var messageType = typeof(TMessage).FullName!; - - writer.WriteLine(messageType); - writer.WriteLine(PipeMessage.Serialize(message)); - writer.WriteLine(EndOfMessage); - writer.Flush(); - } - - public void Dispose() - { - reader.Dispose(); - writer.Dispose(); - } -} \ No newline at end of file diff --git a/src/Fixie/InternalsVisibleTo.cs b/src/Fixie/InternalsVisibleTo.cs index 0cc608e5c..f5102558f 100644 --- a/src/Fixie/InternalsVisibleTo.cs +++ b/src/Fixie/InternalsVisibleTo.cs @@ -1,4 +1,3 @@ using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Fixie.Tests")] -[assembly:InternalsVisibleTo("Fixie.TestAdapter")] \ No newline at end of file +[assembly:InternalsVisibleTo("Fixie.Tests")] \ No newline at end of file diff --git a/src/Fixie/Reports/TestAdapterReport.cs b/src/Fixie/Reports/TestAdapterReport.cs deleted file mode 100644 index f626a720f..000000000 --- a/src/Fixie/Reports/TestAdapterReport.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Fixie.Internal; - -namespace Fixie.Reports; - -class TestAdapterReport(TestAdapterPipe pipe) : - IHandler, - IHandler, - IHandler, - IHandler, - IHandler -{ - public Task Handle(TestDiscovered message) - { - pipe.Send(new PipeMessage.TestDiscovered - { - Test = message.Test - }); - - return Task.CompletedTask; - } - - public Task Handle(TestStarted message) - { - pipe.Send(new PipeMessage.TestStarted - { - Test = message.Test - }); - - return Task.CompletedTask; - } - - public Task Handle(TestSkipped message) - { - pipe.Send(new PipeMessage.TestSkipped - { - Test = message.Test, - TestCase = message.TestCase, - DurationInMilliseconds = message.Duration.TotalMilliseconds, - Reason = message.Reason - }); - - return Task.CompletedTask; - } - - public Task Handle(TestPassed message) - { - pipe.Send(new PipeMessage.TestPassed - { - Test = message.Test, - TestCase = message.TestCase, - DurationInMilliseconds = message.Duration.TotalMilliseconds - }); - - return Task.CompletedTask; - } - - public Task Handle(TestFailed message) - { - pipe.Send(new PipeMessage.TestFailed - { - Test = message.Test, - TestCase = message.TestCase, - DurationInMilliseconds = message.Duration.TotalMilliseconds, - Reason = new PipeMessage.Exception(message.Reason) - }); - - return Task.CompletedTask; - } -} \ No newline at end of file