diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index e7f1f98d93a..6e53a2a3f89 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -59,8 +59,6 @@ module AssemblyResolver = match found() with | None -> Unchecked.defaultof | Some name -> Assembly.Load(name) ) - - do addResolver() #endif type ExecutionOutcome = diff --git a/tests/FSharp.Test.Utilities/TestConsole.fs b/tests/FSharp.Test.Utilities/TestConsole.fs index 654b74ce49d..efb2f7fe270 100644 --- a/tests/FSharp.Test.Utilities/TestConsole.fs +++ b/tests/FSharp.Test.Utilities/TestConsole.fs @@ -11,23 +11,22 @@ module TestConsole = type private RedirectingTextReader() = inherit TextReader() let holder = AsyncLocal<_>() - do holder.Value <- TextReader.Null - - override _.Peek() = holder.Value.Peek() - override _.Read() = holder.Value.Read() - member _.Value = holder.Value - member _.Set (reader: TextReader) = holder.Value <- reader + member _.Reader + with get() = holder.Value |> ValueOption.defaultValue TextReader.Null + and set v = holder.Value <- ValueSome v + override this.Peek() = this.Reader.Peek() + override this.Read() = this.Reader.Read() /// Redirects writes performed on different async execution contexts to the relevant TextWriter held by AsyncLocal. type private RedirectingTextWriter() = inherit TextWriter() - let holder = AsyncLocal() - do holder.Value <- TextWriter.Null + let holder = AsyncLocal<_>() + member _.Writer + with get() = holder.Value |> ValueOption.defaultValue TextWriter.Null + and set v = holder.Value <- ValueSome v override _.Encoding = Encoding.UTF8 - override _.Write(value: char) = holder.Value.Write(value) - member _.Value = holder.Value - member _.Set (writer: TextWriter) = holder.Value <- writer + override this.Write(value: char) = this.Writer.Write(value) let private localIn = new RedirectingTextReader() let private localOut = new RedirectingTextWriter() @@ -41,12 +40,12 @@ module TestConsole = // Taps into the redirected console stream. type private CapturingWriter(redirecting: RedirectingTextWriter) as this = inherit StringWriter() - let wrapped = redirecting.Value - do redirecting.Set this + let wrapped = redirecting.Writer + do redirecting.Writer <- this override _.Encoding = Encoding.UTF8 override _.Write(value: char) = wrapped.Write(value); base.Write(value) override _.Dispose (disposing: bool) = - redirecting.Set wrapped + redirecting.Writer <- wrapped base.Dispose(disposing: bool) /// Captures console streams for the current async execution context. @@ -77,9 +76,9 @@ module TestConsole = string error type ProvideInput(input: string) = - let oldIn = localIn.Value + let oldIn = localIn.Reader do - new StringReader(input) |> localIn.Set + localIn.Reader <- new StringReader(input) interface IDisposable with - member this.Dispose (): unit = localIn.Set oldIn + member this.Dispose (): unit = localIn.Reader <- oldIn diff --git a/tests/FSharp.Test.Utilities/XunitHelpers.fs b/tests/FSharp.Test.Utilities/XunitHelpers.fs index 6f57efef4a4..70308361fd4 100644 --- a/tests/FSharp.Test.Utilities/XunitHelpers.fs +++ b/tests/FSharp.Test.Utilities/XunitHelpers.fs @@ -190,33 +190,45 @@ type OpenTelemetryExport(testRunName, enable) = member this.Dispose() = for p in providers do p.Dispose() +// In some situations, VS can invoke CreateExecutor and RunTestCases many times during testhost lifetime. +// For example when executing "run until failure" command in Test Explorer. +// However, we want to ensure that OneTimeSetup is called only once per test run. +module OneTimeSetup = + + let init = + lazy + #if !NETCOREAPP + // We need AssemblyResolver already here, because OpenTelemetry loads some assemblies dynamically. + log "Adding AssemblyResolver" + AssemblyResolver.addResolver () + #endif + log "Overriding cache capacity" + Cache.OverrideCapacityForTesting() + log "Installing TestConsole redirection" + TestConsole.install() + + logConfig initialConfig + + let EnsureInitialized() = + // Ensure that the initialization is done only once per test run. + init.Force() + /// `XunitTestFramework` providing parallel console support and conditionally enabling optional xUnit customizations. type FSharpXunitFramework(sink: IMessageSink) = inherit XunitTestFramework(sink) + + do OneTimeSetup.EnsureInitialized() override this.CreateExecutor (assemblyName) = { new XunitTestFrameworkExecutor(assemblyName, this.SourceInformationProvider, this.DiagnosticMessageSink) with // Because xUnit v2 lacks assembly fixture, this is a good place to ensure things get called right at the start of the test run. - // This gets executed once per test assembly. override x.RunTestCases(testCases, executionMessageSink, executionOptions) = - #if !NETCOREAPP - // We need AssemblyResolver already here, because OpenTelemetry loads some assemblies dynamically. - AssemblyResolver.addResolver () - #endif - - // Override cache capacity to reduce memory usage in CI. - Cache.OverrideCapacityForTesting() - let testRunName = $"RunTests_{assemblyName.Name} {Runtime.InteropServices.RuntimeInformation.FrameworkDescription}" - use _ = new OpenTelemetryExport(testRunName, Environment.GetEnvironmentVariable("FSHARP_OTEL_EXPORT") <> null) - - logConfig initialConfig - log "Installing TestConsole redirection" - TestConsole.install() - + use _ = new OpenTelemetryExport(testRunName, Environment.GetEnvironmentVariable("FSHARP_OTEL_EXPORT") <> null) + begin use _ = Activity.startNoTags testRunName // We can't just call base.RunTestCases here, because it's implementation is async void. diff --git a/tests/fsharp/single-test.fs b/tests/fsharp/single-test.fs index 5869e27aee0..af220c091b4 100644 --- a/tests/fsharp/single-test.fs +++ b/tests/fsharp/single-test.fs @@ -9,6 +9,8 @@ open FSharp.Compiler.IO let testConfig = testConfig __SOURCE_DIRECTORY__ +let log = printfn + type Permutation = #if NETCOREAPP | FSC_NETCORE of optimized: bool * buildOnly: bool diff --git a/tests/fsharp/tests.fs b/tests/fsharp/tests.fs index 0dc078e506c..c8cbfdab257 100644 --- a/tests/fsharp/tests.fs +++ b/tests/fsharp/tests.fs @@ -27,6 +27,8 @@ let FSI = FSI_NETFX #endif // ^^^^^^^^^^^^ To run these tests in F# Interactive , 'build net40', then send this chunk, then evaluate body of a test ^^^^^^^^^^^^ +let log = printfn + module CoreTests = diff --git a/tests/scripts/scriptlib.fsx b/tests/scripts/scriptlib.fsx index 3f93a93b82c..e9b6df19b91 100644 --- a/tests/scripts/scriptlib.fsx +++ b/tests/scripts/scriptlib.fsx @@ -75,9 +75,12 @@ module Scripting = let deleteDirectory output = if Directory.Exists output then - Directory.Delete(output, true) - - let log format = printfn format + Directory.Delete(output, true) + + // Capture the original stdout for logging. + let private originalOut = stdout + // When used during test run, log will always output to the original stdout of the testhost, instead of the test output. + let log format = fprintfn originalOut format type FilePath = string