@@ -21,29 +21,7 @@ open OpenTelemetry.Trace
2121[<AttributeUsage( AttributeTargets.Class ||| AttributeTargets.Method, AllowMultiple = false ) >]
2222type RunTestCasesInSequenceAttribute () = inherit Attribute()
2323
24- #if ! XUNIT_ EXTRAS
25- /// Installs console support for parallel test runs and conditionally enables optional xUnit customizations.
26- type FSharpXunitFramework ( sink : IMessageSink ) =
27- inherit XunitTestFramework( sink)
28- do
29- // Because xUnit v2 lacks assembly fixture, the next best place to ensure things get called
30- // right at the start of the test run is here in the constructor.
31- // This gets executed once per test assembly.
32- logConfig initialConfig
33- log " FSharpXunitFramework installing TestConsole redirection"
34- TestConsole.install()
35- #if ! NETCOREAPP
36- AssemblyResolver.addResolver ()
37- #endif
38-
39- interface IDisposable with
40- member _.Dispose () =
41- match Environment.GetEnvironmentVariable( " FSHARP_RETAIN_TESTBUILDS" ) with
42- | null -> cleanUpTemporaryDirectoryOfThisTestRun ()
43- | _ -> ()
44- base .Dispose()
45-
46- #else
24+ #if XUNIT_ EXTRAS
4725
4826// To use xUnit means to customize it. The following abomination adds 2 features:
4927// - Capturing full console output individually for each test case, viewable in Test Explorer as test stdout.
@@ -57,7 +35,7 @@ type ConsoleCapturingTestRunner(test, messageBus, testClass, constructorArgument
5735 override this.InvokeTestAsync ( aggregator : ExceptionAggregator ) =
5836 task {
5937 use capture = new TestConsole.ExecutionCapture()
60- use _ = Activity.start test.DisplayName [ ]
38+ use _ = Activity.startNoTags test.DisplayName
6139 let! executionTime = this.BaseInvokeTestMethodAsync aggregator
6240 let output =
6341 seq {
@@ -142,46 +120,55 @@ type CustomTheoryTestCase =
142120 base .Initialize()
143121 testCase.TestMethod <- TestCaseCustomizations.rewriteTestMethod testCase
144122
123+ #endif
124+
145125/// `XunitTestFramework` providing parallel console support and conditionally enabling optional xUnit customizations.
146126type FSharpXunitFramework ( sink : IMessageSink ) =
147127 inherit XunitTestFramework( sink)
148-
149- let traceProvider =
150- Sdk.CreateTracerProviderBuilder()
151- .AddSource( ActivityNames.FscSourceName)
152- .SetResourceBuilder(
153- ResourceBuilder.CreateDefault() .AddService( serviceName= " F#" , serviceVersion = " 1.0.0" ))
154- .AddOtlpExporter()
155- .Build()
156-
157- interface IDisposable with
158- member _.Dispose () =
159- cleanUpTemporaryDirectoryOfThisTestRun ()
160- traceProvider.ForceFlush() |> ignore
161- traceProvider.Dispose()
162- base .Dispose()
163128
164- // Group test run under single activity, to make traces more readable.
165- // Otherwise this overriden method is not necessary and can be removed.
166- override this.CreateExecutor ( assemblyName ) =
129+ override this.CreateExecutor ( assemblyName ) =
167130 { new XunitTestFrameworkExecutor( assemblyName, this.SourceInformationProvider, this.DiagnosticMessageSink) with
168- override _.RunTestCases ( testCases , executionMessageSink , executionOptions ) =
169- use _ = Activity.start $" {assemblyName.Name} {Runtime.InteropServices.RuntimeInformation.FrameworkDescription}" []
131+
132+ // Because xUnit v2 lacks assembly fixture, this is a good place to ensure things get called right at the start of the test run.
133+ // This gets executed once per test assembly.
134+ override x.RunTestCases ( testCases , executionMessageSink , executionOptions ) =
135+
136+ #if ! NETCOREAPP
137+ // We need AssemblyResolver already here, because OpenTelemetry loads some assemblies dynamically.
138+ AssemblyResolver.addResolver ()
139+ #endif
140+
141+ // Configure OpenTelemetry export. Traces can be viewed in Jaeger or other compatible tools.
142+ use tracerProvider =
143+ OpenTelemetry.Sdk.CreateTracerProviderBuilder()
144+ .AddSource( ActivityNames.FscSourceName)
145+ .ConfigureResource( fun r -> r.AddService( " F#" ) |> ignore)
146+ .AddOtlpExporter( fun o ->
147+ // Empirical values to ensure no traces are lost and no significant delay at the end of test run.
148+ o.TimeoutMilliseconds <- 200
149+ o.BatchExportProcessorOptions.MaxQueueSize <- 16384
150+ o.BatchExportProcessorOptions.ScheduledDelayMilliseconds <- 100
151+ )
152+ .Build()
170153
171- // Because xUnit v2 lacks assembly fixture, the next best place to ensure things get called
172- // right at the start of the test run is here or in the FSharpXunitFramework constructor.
173- // This gets executed once per test assembly.
174- printfn $" Running tests in {assemblyName.Name} with XUNIT_EXTRAS"
175154 logConfig initialConfig
176155 log " Installing TestConsole redirection"
177156 TestConsole.install()
157+
158+ begin
159+ use _ = Activity.startNoTags $" RunTests_{assemblyName.Name} {Runtime.InteropServices.RuntimeInformation.FrameworkDescription}"
160+ // We can't just call base.RunTestCases here, because it's implementation is async void.
161+ use runner = new XunitTestAssemblyRunner ( x.TestAssembly, testCases, x.DiagnosticMessageSink, executionMessageSink, executionOptions)
162+ runner.RunAsync() .Wait()
163+ end
178164
179- #if ! NETCOREAPP
180- AssemblyResolver.addResolver ()
181- #endif
182- base .RunTestCases( testCases, executionMessageSink, executionOptions)
165+ tracerProvider.ForceFlush() |> ignore
166+
167+ cleanUpTemporaryDirectoryOfThisTestRun ()
183168 }
184169
170+ #if XUNIT_ EXTRAS
171+ // Rewrites discovered test cases to support extra parallelization and capturing console as test output.
185172 override this.CreateDiscoverer ( assemblyInfo ) =
186173 { new XunitTestFrameworkDiscoverer( assemblyInfo, this.SourceInformationProvider, this.DiagnosticMessageSink) with
187174 override _.FindTestsForType ( testClass , includeSourceInformation , messageBus , options ) =
0 commit comments