-
Notifications
You must be signed in to change notification settings - Fork 786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parallelize tests #17872
base: main
Are you sure you want to change the base?
Parallelize tests #17872
Conversation
|
decc8cb
to
278e2ec
Compare
Thanks for your endurance, Jakub 💪 |
@psfinaki I will need some help with this Source-Build error: At this moment this is very stable locally but will also probably need testing on other machines than mine :) What's left to do is to tune this for stability in CI. I've been trying different things and timing runs. The most glaring problem is the I also noticed Linux run is constantly low on memory, this is unrelated as it happens on main, too. For this reason I set |
@majocha the error is weird, nothing comes to my mind right away. Let's rebase and rerun and see if it's still happening... Sorry, I know this is somewhat lame, it's just that SourceBuild is a Linux thing and it's not trivial to debug its issues locally. As for cooling things down - I also noticed this today, thanks for addressing this. What else do you think we can split from this PR into some separate ones? |
There are some small further fixes, maybe also the whole console handling does not really depend on parallel execution. Somewhat related thing I have on my mind recently is to implement a |
1c17ac7
to
0ed0442
Compare
Thanks! Rebasing did help. |
Yeah console handling would be probably good to isolate if possible.
Just for my understanding, what would this add on top of the current results the PR achieves? |
This would be an experiment for another PR, but basically, I don't like all that copying to temp dirs that I added in recent PRs. |
Right, yeah, I see. No it's worth playing with, although given that we don't touch these tests too much, it's probably worth seriously investing into only if it starts yielding reasonable performance fruits. |
tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs
Outdated
Show resolved
Hide resolved
Awesome, thanks for splitting that - we will take a look there shortly :) |
I don't know what's up with the _GetRestoreSettingsPerFramework error, though. |
I renamed the parallelization disabling collection to Another thing is Service Tests were deparallelized in code and I missed it for all this time: fsharp/tests/FSharp.Compiler.Service.Tests/AssemblyInfo.fs Lines 1 to 7 in fb69e58
So this is now unblocked, should shave another minute or half. If the CI can't manage this, I'll throttle it back. |
MacOS seems to crash the test host now... in the current state of things, are the logs properly blameable now? We can use this as an exercise :) |
@@ -214,7 +214,8 @@ function Test() { | |||
projectname=$(basename -- "$testproject") | |||
projectname="${projectname%.*}" | |||
testlogpath="$artifacts_dir/TestResults/$configuration/${projectname}_$targetframework.xml" | |||
args="test \"$testproject\" --no-restore --no-build -c $configuration -f $targetframework --test-adapter-path . --logger \"xunit;LogFilePath=$testlogpath\" --blame --results-directory $artifacts_dir/TestResults/$configuration -p:vstestusemsbuildoutput=false" | |||
args="test \"$testproject\" --no-restore --no-build -c $configuration -f $targetframework --test-adapter-path . --logger \"xunit;LogFilePath=$testlogpath\" --blame-hang-timeout 5minutes --results-directory $artifacts_dir/TestResults/$configuration -p:vstestusemsbuildoutput=false" | |||
args+=" -- xUnit.MaxParallelThreads=4" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sooo it looks like build.ps1
is doing something else on this topic, but regardless - shouldn't this be driven by xunit.runner.json files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be ideal, but this throttling is just for the sake of the CI. Locally it runs ok full speed. I mean if you do dotnet test
and not use this script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To add, this was tuned purely by experiment, IIRC without this throttling CI on Linux or Mac was running out of memory. The reason it is different in Build.ps1 is that I had easier time working with powershell on Windows. I'm frankly afraid of touching this bash script 😄
Yes, blame-hang-timeout works as intended: I'll exclude this GC test. Edit:
|
if capture.OutText |> TestFramework.outputPassed then | ||
res | ||
else | ||
failwith $"Results looked correct, but 'TEST PASSED OK' was not printed. Result: %A{s}" | ||
failwith $"Results looked correct, but 'TEST PASSED OK' was not printed." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess results look useless in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure now what CompilationResult.Success
means in case of script evaluation but this is another story altogether.
tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncModule.fs
Outdated
Show resolved
Hide resolved
tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncModule.fs
Outdated
Show resolved
Hide resolved
Standard output is now printed to the console only in case of test failure:
This particular test case needs to be deparallelized possibly because Checker's |
Enable running xUnit tests in parallel.
To use xUnit means to customize it. Two optional features added:
Running collection and theory cases in parallel based on https://www.meziantou.net/parallelize-test-cases-execution-in-xunit.htm
By default xUnit's unit of parallelization is test collection. Test cases in a collection run in sequence. Also, by default each class/module constitutes a collection. We have a lot of test cases in large modules and large theories that were bottlenecked by this.
This customization enables parallelism in such cases. It can be reverted to default for a particular module with
[<RunTestCasesInSequence>]
attribute.Relevant issue is on the roadmap for xUnit v3, so this will probably become unnecessary in the future.
Console streams captured universally and redirected to xUnit's output mechanism, which means you can just do printfn in a test case and it goes to the respective output.
This can be inspected in the IDE and in case of failure is printed out when testing from the command line.
The default way in xUnit is to use ITestOutputHelper. This is very unwieldy, because it requires placing test cases in a class with a constructor, and then threading the injected output helper into any function that wants to output text. We have many tests in modules not classes, and many of the tests are using a lot of utility functions. Adjusting it all to use ITestOutputHelper is not feasible. OTOH just outputting with
printfn
is unobtrusive, natural and works well with interactive prototyping of test cases.This customization will probably become unnecessary with xUnit v3.
The above customizations are not required for the test suite to work correctly. They are contained to the XunitHelpers.fs file and enabled with conditional compilation using
XUNIT_EXTRAS
constant defined in FSharp.Test.Utilities.fsprojSome local run times:
Test summary: total: 4489, failed: 0, succeeded: 4258, skipped: 231, duration: 199.0s
Test summary: total: 579, failed: 0, succeeded: 579, skipped: 0, duration: 41.9s
Test summary: total: 12963, failed: 0, succeeded: 12694, skipped: 269, duration: 253.3s
Some considerations to make this work and keep it working
To run tests in parallel we must deal with global resources and global state accessed by the test cases.
Out of proc:
Tests running as separate processes are sharing the file system. We must make sure they execute in their own temporary directories and don't overwrite any hardcoded paths. This is already done, mostly in separate PR.
Hosted:
Many tests use hosted compiler and
FsiEvaluationSession
, sharing global resources and global state within the runner process:FileSystem
global mutable of the file system shim - few tests that mutate it, must be excluded from parallelization.Environment.CurrentDirectory
- many tests executing in hosted session were doing a variation ofFile.WriteAllText("test.ok", "ok")
all in the current directory i.e.bin
, leading to conflicts. This is replaced with a threadsafe mechanism.DependencyManager
, excluded from parallelization for now.Async
default cancellation token - few tests doingAsync.CancelDefaultToken()
must be excluded from parallelization.--times
option - tests excluded from parallelization.ConcurrentDictionary
. This seems no longer a problem, contained using some exclusions from parallelization.I'll ad to the above list if I recall anything else.
Problems:
Tests depending on tight timing, orchestrating stuff by combinations of
Thread.Sleep
,Async.Sleep
and wait timeouts.These are mostly excluded from parallelization, some attempts at fixing things were made.
Obscure compiler bugs revealed in this PR:
Internal error: value cannot be null
this mostly happens in coreClr, one time, sometimes a few times during the test run.Error creating evaluation session
because of NRE somewhere inTcImports.BuildNonFrameworkTcImports
. This is more rare but may be related to the above.These were related to some concurrency issues; modyfing
frameworkTcImportsCache
without lock and a bug in custom lazy implementation in il.fs. Hopefully both fixed now.Running in parallel:
Xunit runners are configured with mostly default parallelization settings.
dotnet test .\FSharp.sln -c Release -f net9.0
will run all discovered test assemblies in parallel as soon as they're built.This can be limited with the
-m
switch. For example,dotnet test -m:2 .\FSharp.Compiler.Service.sln
will limit the test run to at most 2 simultaneous processes. Still, each test host process runs its test collections in parallel.
Some test collections are excluded form parallelization with
[<Collection(nameof NotThreadSafeResourceCollection)>]
attribute.Running in the IDE with "Run tests in parallel" enabled will respect
xunit.runner.json
settings and the above exclusions.TODO:
BUILDING_USING_DOTNET
scenario (Attempt to make FCS solution build without arcade and with the SDK specified in global.json #14677)