diff --git a/azure-pipelines-PR.yml b/azure-pipelines-PR.yml
index 3c13a6899bf..2ebe6fb1f8d 100644
--- a/azure-pipelines-PR.yml
+++ b/azure-pipelines-PR.yml
@@ -270,10 +270,10 @@ stages:
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
- testResultsFormat: 'VSTest'
+ testResultsFormat: 'XUnit'
testRunTitle: WindowsNoRealsig_testCoreclr
mergeTestResults: true
- testResultsFiles: '*.trx'
+ testResultsFiles: '*.xml'
searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/Release'
condition: succeededOrFailed()
@@ -317,10 +317,10 @@ stages:
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
- testResultsFormat: 'VSTest'
+ testResultsFormat: 'XUnit'
testRunTitle: WindowsNoRealsig_testDesktop
mergeTestResults: true
- testResultsFiles: '*.trx'
+ testResultsFiles: '*.xml'
searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/Release'
condition: succeededOrFailed()
continueOnError: true
@@ -465,10 +465,10 @@ stages:
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
- testResultsFormat: 'VSTest'
+ testResultsFormat: 'XUnit'
testRunTitle: WindowsCompressedMetadata $(_testKind) $(transparentCompiler)
mergeTestResults: true
- testResultsFiles: '*.trx'
+ testResultsFiles: '*.xml'
searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_configuration)'
continueOnError: true
condition: succeededOrFailed()
@@ -510,8 +510,16 @@ stages:
continueOnError: true
condition: failed()
- # Windows With Compressed Metadata Desktop
+ # Windows With Compressed Metadata Desktop (split into 3 batches)
- job: WindowsCompressedMetadata_Desktop
+ strategy:
+ matrix:
+ Batch1:
+ batchNumber: 1
+ Batch2:
+ batchNumber: 2
+ Batch3:
+ batchNumber: 3
variables:
- name: XUNIT_LOGS
value: $(Build.SourcesDirectory)\artifacts\TestResults\Release
@@ -523,65 +531,20 @@ stages:
timeoutInMinutes: 120
steps:
- - checkout: self
- clean: true
-
- - script: eng\CIBuildNoPublish.cmd -compressallmetadata -configuration Release -testDesktop
- env:
- FSharp_CacheEvictionImmediate: true
- DOTNET_DbgEnableMiniDump: 1
- DOTNET_DbgMiniDumpType: 3 # Triage dump, 1 for mini, 2 for Heap, 3 for triage, 4 for full. Don't use 4 unless you know what you're doing.
- DOTNET_DbgMiniDumpName: $(Build.SourcesDirectory)\artifacts\log\Release\$(Build.BuildId)-%e-%p-%t.dmp
- NativeToolsOnMachine: true
- displayName: Build / Test
-
- - task: PublishTestResults@2
- displayName: Publish Test Results
- inputs:
- testResultsFormat: 'VSTest'
- testRunTitle: WindowsCompressedMetadata testDesktop
- mergeTestResults: true
- testResultsFiles: '*.trx'
- searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/Release'
- continueOnError: true
- condition: succeededOrFailed()
-
- - task: PublishBuildArtifacts@1
- displayName: Publish BinLog
- continueOnError: true
- inputs:
- PathToPublish: '$(Build.SourcesDirectory)\artifacts\log/Release\Build.VisualFSharp.sln.binlog'
- ArtifactName: 'Windows testDesktop binlogs'
- ArtifactType: Container
- parallel: true
- - task: PublishBuildArtifacts@1
- displayName: Publish Dumps
- condition: failed()
- continueOnError: true
- inputs:
- PathToPublish: '$(Build.SourcesDirectory)\artifacts\log\Release'
- ArtifactName: 'Windows testDesktop process dumps'
- ArtifactType: Container
- parallel: true
- - task: PublishBuildArtifacts@1
- displayName: Publish Test Logs
- inputs:
- PathtoPublish: '$(Build.SourcesDirectory)\artifacts\TestResults\Release'
- ArtifactName: 'Windows testDesktop test logs'
- publishLocation: Container
- continueOnError: true
- condition: always()
- - script: dotnet build $(Build.SourcesDirectory)/eng/DumpPackageRoot/DumpPackageRoot.csproj
- displayName: Dump NuGet cache contents
- condition: failed()
- - task: PublishBuildArtifacts@1
- displayName: Publish NuGet cache contents
- inputs:
- PathtoPublish: '$(Build.SourcesDirectory)\artifacts\NugetPackageRootContents'
- ArtifactName: 'NuGetPackageContents Windows testDesktop'
- publishLocation: Container
- continueOnError: true
- condition: failed()
+ - template: /eng/templates/batched-test-steps.yml
+ parameters:
+ buildCommand: eng\CIBuildNoPublish.cmd -compressallmetadata -configuration Release -testDesktopBatch $(batchNumber)
+ buildEnv:
+ FSharp_CacheEvictionImmediate: true
+ DOTNET_DbgEnableMiniDump: 1
+ DOTNET_DbgMiniDumpType: 3
+ DOTNET_DbgMiniDumpName: $(Build.SourcesDirectory)\artifacts\log\Release\$(Build.BuildId)-%e-%p-%t.dmp
+ NativeToolsOnMachine: true
+ testRunTitlePrefix: 'WindowsCompressedMetadata testDesktop'
+ artifactNamePrefix: 'Windows testDesktop'
+ publishBinLog: true
+ binLogPath: '$(Build.SourcesDirectory)\artifacts\log/Release\Build.VisualFSharp.sln.binlog'
+ publishDumps: true
# Mock official build
- job: MockOfficial
@@ -611,9 +574,9 @@ stages:
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
- testResultsFormat: 'VSTest'
+ testResultsFormat: 'XUnit'
testRunTitle: Linux
- testResultsFiles: '*.trx'
+ testResultsFiles: '*.xml'
mergeTestResults: true
searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)'
continueOnError: true
@@ -638,8 +601,16 @@ stages:
continueOnError: true
condition: failed()
- # MacOS
+ # MacOS (split into 3 batches)
- job: MacOS
+ strategy:
+ matrix:
+ Batch1:
+ batchNumber: 1
+ Batch2:
+ batchNumber: 2
+ Batch3:
+ batchNumber: 3
pool:
vmImage: macos-latest
timeoutInMinutes: 120
@@ -647,41 +618,13 @@ stages:
- name: _SignType
value: Test
steps:
- - checkout: self
- clean: true
- - script: ./eng/cibuild.sh --configuration $(_BuildConfig) --testcoreclr
- env:
- COMPlus_DefaultStackSize: 1000000
- displayName: Build / Test
- - task: PublishTestResults@2
- displayName: Publish Test Results
- inputs:
- testResultsFormat: 'VSTest'
- testResultsFiles: '*.trx'
- testRunTitle: MacOS
- mergeTestResults: true
- searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)'
- continueOnError: true
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: Publish Test Logs
- inputs:
- PathtoPublish: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)'
- ArtifactName: 'MacOS $(_BuildConfig) test logs'
- publishLocation: Container
- continueOnError: true
- condition: failed()
- - script: dotnet build $(Build.SourcesDirectory)/eng/DumpPackageRoot/DumpPackageRoot.csproj
- displayName: Dump NuGet cache contents
- condition: failed()
- - task: PublishBuildArtifacts@1
- displayName: Publish NuGet cache contents
- inputs:
- PathtoPublish: '$(Build.SourcesDirectory)/artifacts/NugetPackageRootContents'
- ArtifactName: 'NuGetPackageContents Mac'
- publishLocation: Container
- continueOnError: true
- condition: failed()
+ - template: /eng/templates/batched-test-steps.yml
+ parameters:
+ buildCommand: ./eng/cibuild.sh --configuration $(_BuildConfig) --testcoreclrbatch $(batchNumber)
+ buildEnv:
+ COMPlus_DefaultStackSize: 1000000
+ testRunTitlePrefix: 'MacOS'
+ artifactNamePrefix: 'MacOS'
# End to end build
- job: EndToEndBuildTests
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 170d26397e3..e4f0c294362 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -152,8 +152,8 @@ extends:
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
- testResultsFormat: 'VSTest'
- testResultsFiles: '*.trx'
+ testResultsFormat: 'XUnit'
+ testResultsFiles: '*.xml'
searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)'
continueOnError: true
condition: ne(variables['SkipTests'], 'true')
diff --git a/eng/Build.ps1 b/eng/Build.ps1
index 770e9e88c8a..5951d2c2989 100644
--- a/eng/Build.ps1
+++ b/eng/Build.ps1
@@ -49,6 +49,7 @@ param (
[switch]$dontUseGlobalNuGetCache = $false,
[switch]$warnAsError = $true,
[switch][Alias('test')]$testDesktop,
+ [string]$testDesktopBatch = "",
[switch]$testCoreClr,
[switch]$testCambridge,
[switch]$testCompiler,
@@ -121,6 +122,7 @@ function Print-Usage() {
Write-Host " -testCompilerService Run FSharpCompilerService unit tests"
Write-Host " -testCompilerComponentTests Run FSharpCompilerService component tests"
Write-Host " -testDesktop Run tests against full .NET Framework"
+ Write-Host " -testDesktopBatch <1|2|3> Run a specific batch of the desktop test split (implies -testDesktop)"
Write-Host " -testCoreClr Run tests against CoreCLR"
Write-Host " -testFSharpCore Run FSharpCore unit tests"
Write-Host " -testIntegration Run F# integration tests"
@@ -193,6 +195,10 @@ function Process-Arguments() {
$script:testEditor = $True
}
+ if ($script:testDesktopBatch -ne "") {
+ $script:testDesktop = $True
+ }
+
if ([System.Boolean]::Parse($script:officialSkipTests)) {
$script:testAll = $False
$script:testAllButIntegration = $False
@@ -374,14 +380,10 @@ function TestUsingMSBuild([string] $testProject, [string] $targetFramework, [str
# MTP requires --solution flag for .sln files
$testTarget = if ($testProject.EndsWith('.sln')) { "--solution ""$testProject""" } else { "--project ""$testProject""" }
- # For solutions, omit --report-xunit-trx-filename so each test assembly generates a unique .trx file.
- # With a static filename, all assemblies overwrite the same file and only the last one's results survive.
- if ($testProject.EndsWith('.sln')) {
- $reportArgs = "--report-xunit-trx"
- } else {
- $testLogFileName = "${projectName}_${targetFramework}.trx"
- $reportArgs = "--report-xunit-trx --report-xunit-trx-filename ""$testLogFileName"""
- }
+ # Xunit XML report via XunitXml.TestLogger with CI-friendly filenames
+ $jobName = if ($env:SYSTEM_JOBNAME) { $env:SYSTEM_JOBNAME } else { "local" }
+ $xunitLogFileName = "{assembly}.{framework}.${jobName}.xml"
+ $reportArgs = "--report-spekt-xunit --report-spekt-xunit-filename ""$xunitLogFileName"""
$test_args = "test $testTarget -c $configuration -f $targetFramework $reportArgs --results-directory ""$testResultsDir"" /bl:$testBinLogPath"
# MTP HangDump extension replaces VSTest --blame-hang-timeout
@@ -605,7 +607,23 @@ try {
}
if ($testDesktop) {
- TestUsingMSBuild -testProject "$RepoRoot\FSharp.sln" -targetFramework $script:desktopTargetFramework
+ if ($testDesktopBatch -ne "") {
+ $dotnetPath = InitializeDotNetCli
+ $dotnetExe = Join-Path $dotnetPath "dotnet.exe"
+ $splitScript = Join-Path $RepoRoot "eng\tests\TestSplit.fsx"
+ $splitOutput = & $dotnetExe fsi $splitScript $testDesktopBatch desktop
+ if ($LASTEXITCODE -ne 0) { throw "TestSplit.fsx failed with exit code $LASTEXITCODE" }
+ foreach ($line in $splitOutput) {
+ if ($line -match '^dotnet test (\S+) --no-build -c Release\s*(.*)$') {
+ $proj = $Matches[1] -replace '/', '\'
+ $projPath = Join-Path $RepoRoot $proj
+ $settings = $Matches[2].Trim()
+ TestUsingMSBuild -testProject $projPath -targetFramework $script:desktopTargetFramework -settings $settings
+ }
+ }
+ } else {
+ TestUsingMSBuild -testProject "$RepoRoot\FSharp.sln" -targetFramework $script:desktopTargetFramework
+ }
}
if ($testFSharpCore) {
diff --git a/eng/Versions.props b/eng/Versions.props
index 2d4131bd00b..b96d89e5c09 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -161,6 +161,7 @@
13.0.3
3.2.2
3.2.2
+ 8.0.0
diff --git a/eng/build.sh b/eng/build.sh
index 06df1423012..6be36adfa26 100755
--- a/eng/build.sh
+++ b/eng/build.sh
@@ -63,6 +63,7 @@ pack=false
publish=false
sign=false
test_core_clr=false
+test_core_clr_batch=""
test_compilercomponent_tests=false
test_benchmarks=false
test_scripting=false
@@ -142,6 +143,11 @@ while [[ $# > 0 ]]; do
--testcoreclr|--test|-t)
test_core_clr=true
;;
+ --testcoreclrbatch)
+ test_core_clr=true
+ test_core_clr_batch=$2
+ shift
+ ;;
--testcompilercomponenttests)
test_compilercomponent_tests=true
;;
@@ -204,6 +210,7 @@ function Test() {
BuildMessage="Error running tests"
testproject=""
targetframework=""
+ extraargs=""
while [[ $# > 0 ]]; do
opt="$(echo "$1" | awk '{print tolower($0)}')"
case "$opt" in
@@ -215,6 +222,10 @@ function Test() {
targetframework=$2
shift
;;
+ --extraargs)
+ extraargs=$2
+ shift
+ ;;
*)
echo "Invalid argument: $1"
exit 1
@@ -233,18 +244,18 @@ function Test() {
testresultsdir="$artifacts_dir/TestResults/$configuration"
# MTP requires --solution flag for .sln files
- # For solutions, omit --report-xunit-trx-filename so each test assembly generates a unique .trx file.
- # With a static filename, all assemblies overwrite the same file and only the last one's results survive.
if [[ "$testproject" == *.sln ]]; then
testtarget="--solution"
- reportargs="--report-xunit-trx"
else
testtarget="--project"
- testlogfilename="${projectname}_${targetframework}.trx"
- reportargs="--report-xunit-trx --report-xunit-trx-filename $testlogfilename"
fi
- args=(test $testtarget "$testproject" --no-build -c "$configuration" -f "$targetframework" $reportargs --results-directory "$testresultsdir" --hangdump --hangdump-timeout 5m --hangdump-type Full)
+ # Xunit XML report via XunitXml.TestLogger with CI-friendly filenames
+ jobname="${SYSTEM_JOBNAME:-local}"
+ xunitlogfilename="{assembly}.{framework}.${jobname}.xml"
+ reportargs="--report-spekt-xunit --report-spekt-xunit-filename $xunitlogfilename"
+
+ args=(test $testtarget "$testproject" --no-build -c "$configuration" -f "$targetframework" $reportargs --results-directory "$testresultsdir" --hangdump --hangdump-timeout 5m --hangdump-type Full $extraargs)
"$DOTNET_INSTALL_DIR/dotnet" "${args[@]}" || exit $?
}
@@ -362,12 +373,28 @@ BuildSolution
if [[ "$test_core_clr" == true ]]; then
coreclrtestframework=$tfm
- # Note: FSharp.Test.Utilities is a utility library, not a test project. Its tests are disabled due to xUnit3 API incompatibilities.
- Test --testproject "$repo_root/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj" --targetframework $coreclrtestframework
- Test --testproject "$repo_root/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj" --targetframework $coreclrtestframework
- Test --testproject "$repo_root/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj" --targetframework $coreclrtestframework
- Test --testproject "$repo_root/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj" --targetframework $coreclrtestframework
- Test --testproject "$repo_root/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj" --targetframework $coreclrtestframework
+
+ if [[ "$test_core_clr_batch" != "" ]]; then
+ # Run batched: use TestSplit.fsx to get the commands for this batch
+ splitOutput=$("$DOTNET_INSTALL_DIR/dotnet" fsi "$scriptroot/tests/TestSplit.fsx" "$test_core_clr_batch" coreclr)
+ if [[ $? -ne 0 ]]; then
+ echo "TestSplit.fsx failed with exit code $?"
+ ExitWithExitCode 1
+ fi
+ while IFS= read -r line; do
+ # Extract project path and extra filter args from each line
+ project=$(echo "$line" | sed 's/^dotnet test //' | sed 's/ --no-build.*//')
+ filterargs=$(echo "$line" | sed 's/^dotnet test [^ ]* --no-build -c Release *//')
+ Test --testproject "$repo_root/$project" --targetframework $coreclrtestframework --extraargs "$filterargs"
+ done <<< "$splitOutput"
+ else
+ # Run all tests without batching
+ Test --testproject "$repo_root/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj" --targetframework $coreclrtestframework
+ Test --testproject "$repo_root/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj" --targetframework $coreclrtestframework
+ Test --testproject "$repo_root/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj" --targetframework $coreclrtestframework
+ Test --testproject "$repo_root/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj" --targetframework $coreclrtestframework
+ Test --testproject "$repo_root/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj" --targetframework $coreclrtestframework
+ fi
fi
if [[ "$test_compilercomponent_tests" == true ]]; then
diff --git a/eng/templates/batched-test-steps.yml b/eng/templates/batched-test-steps.yml
new file mode 100644
index 00000000000..712d5e3219f
--- /dev/null
+++ b/eng/templates/batched-test-steps.yml
@@ -0,0 +1,88 @@
+# Steps template for batched test jobs.
+# Use inside a job that has strategy:matrix with a batchNumber variable.
+#
+# Example usage:
+# - job: MyTestJob
+# strategy:
+# matrix:
+# Batch1: { batchNumber: 1 }
+# Batch2: { batchNumber: 2 }
+# Batch3: { batchNumber: 3 }
+# steps:
+# - template: /eng/templates/batched-test-steps.yml
+# parameters:
+# buildCommand: eng\CIBuild.cmd -testDesktopBatch $(batchNumber)
+# testRunTitlePrefix: 'MyJob testDesktop'
+# artifactNamePrefix: 'Windows testDesktop'
+
+parameters:
+ buildCommand: ''
+ buildEnv: {}
+ configuration: 'Release'
+ testRunTitlePrefix: ''
+ artifactNamePrefix: ''
+ publishBinLog: false
+ binLogPath: ''
+ publishDumps: false
+
+steps:
+- checkout: self
+ clean: true
+
+- script: ${{ parameters.buildCommand }}
+ env: ${{ parameters.buildEnv }}
+ displayName: Build / Test
+
+- task: PublishTestResults@2
+ displayName: Publish Test Results
+ inputs:
+ testResultsFormat: 'XUnit'
+ testRunTitle: ${{ parameters.testRunTitlePrefix }} Batch$(batchNumber)
+ mergeTestResults: true
+ testResultsFiles: '*.xml'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/${{ parameters.configuration }}'
+ continueOnError: true
+ condition: succeededOrFailed()
+
+- ${{ if parameters.publishBinLog }}:
+ - task: PublishBuildArtifacts@1
+ displayName: Publish BinLog
+ continueOnError: true
+ inputs:
+ PathToPublish: ${{ parameters.binLogPath }}
+ ArtifactName: '${{ parameters.artifactNamePrefix }} Batch$(batchNumber) binlogs'
+ ArtifactType: Container
+ parallel: true
+
+- ${{ if parameters.publishDumps }}:
+ - task: PublishBuildArtifacts@1
+ displayName: Publish Dumps
+ condition: failed()
+ continueOnError: true
+ inputs:
+ PathToPublish: '$(Build.SourcesDirectory)/artifacts/log/${{ parameters.configuration }}'
+ ArtifactName: '${{ parameters.artifactNamePrefix }} Batch$(batchNumber) process dumps'
+ ArtifactType: Container
+ parallel: true
+
+- task: PublishBuildArtifacts@1
+ displayName: Publish Test Logs
+ inputs:
+ PathtoPublish: '$(Build.SourcesDirectory)/artifacts/TestResults/${{ parameters.configuration }}'
+ ArtifactName: '${{ parameters.artifactNamePrefix }} Batch$(batchNumber) test logs'
+ publishLocation: Container
+ continueOnError: true
+ condition: always()
+
+- script: dotnet build $(Build.SourcesDirectory)/eng/DumpPackageRoot/DumpPackageRoot.csproj
+ displayName: Dump NuGet cache contents
+ condition: failed()
+
+- task: PublishBuildArtifacts@1
+ displayName: Publish NuGet cache contents
+ inputs:
+ PathtoPublish: '$(Build.SourcesDirectory)/artifacts/NugetPackageRootContents'
+ ArtifactName: 'NuGetPackageContents ${{ parameters.artifactNamePrefix }} Batch$(batchNumber)'
+ publishLocation: Container
+ continueOnError: true
+ condition: failed()
diff --git a/eng/tests/TestSplit.fsx b/eng/tests/TestSplit.fsx
new file mode 100644
index 00000000000..c2e2bc884f1
--- /dev/null
+++ b/eng/tests/TestSplit.fsx
@@ -0,0 +1,99 @@
+/// Test split table for parallel CI.
+/// Edit the batch assignments below, then run:
+/// dotnet fsi eng/tests/TestSplit.fsx [desktop|coreclr]
+/// to get the dotnet test commands for that batch.
+/// The platform argument controls which projects are included (default: all).
+
+let totalBatches = 3
+let residualBatch = 3 // uses negation filter; catches unlisted atoms + future namespaces
+
+// MTP --filter-namespace uses starts-with matching on the test namespace.
+// Unlisted atoms go to the residual batch automatically via --filter-not-namespace.
+
+let componentTestsAtoms =
+ [// atom batch
+ "CompilerDirectives", 1
+ "CompilerService", 1
+ "ErrorMessages", 1
+ "FSharpChecker", 1
+ "Import", 1
+ "Language", 1
+ "Miscellaneous", 1
+ "XmlComments", 1
+
+ "EmittedIL", 2
+ "Interop", 2
+ "Libraries", 2
+ "Globalization", 2
+ "InteractiveSession", 2
+
+ "CompilerOptions", 3
+ "Conformance", 3
+ "Diagnostics", 3
+ "Signatures", 3
+ "ConstraintSolver", 3
+ "Debugger", 3
+ "Scripting", 3
+ "TypeChecks", 3
+ ]
+
+// Platform tags: "all" = both desktop and coreclr, "desktop" = net472 only
+let otherProjects =
+ [// project path batch platform
+ "tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj", 1, "all"
+ "tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj", 2, "all"
+ "tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj", 2, "all"
+ "tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj", 3, "all"
+ "tests/fsharp/FSharpSuite.Tests.fsproj", 3, "desktop"
+ ]
+
+// ── filter generation ──
+
+let batch, platform =
+ match fsi.CommandLineArgs with
+ | [| _; n |] ->
+ let v = int n
+ if v < 1 || v > totalBatches then failwith $"Batch number must be between 1 and {totalBatches}, got {v}"
+ v, "all"
+ | [| _; n; p |] ->
+ let v = int n
+ if v < 1 || v > totalBatches then failwith $"Batch number must be between 1 and {totalBatches}, got {v}"
+ v, p
+ | _ -> failwith "Usage: dotnet fsi eng/tests/TestSplit.fsx [desktop|coreclr]"
+
+let matchesPlatform tag =
+ tag = "all" || tag = platform || platform = "all"
+
+let expandAtom atom =
+ [ atom
+ $"FSharp.Compiler.ComponentTests.{atom}"
+ $"ComponentTests.{atom}" ]
+
+let atomsForBatch b =
+ componentTestsAtoms
+ |> List.filter (fun (_, ba) -> ba = b)
+ |> List.collect (fst >> expandAtom)
+ |> List.distinct
+ |> List.sort
+
+let otherBatchesAtoms =
+ componentTestsAtoms
+ |> List.filter (fun (_, b) -> b <> batch)
+ |> List.collect (fst >> expandAtom)
+ |> List.distinct
+ |> List.sort
+
+let filterArgs =
+ if batch = residualBatch then
+ let atoms = otherBatchesAtoms |> String.concat " "
+ $"--filter-not-namespace {atoms}"
+ else
+ let atoms = atomsForBatch batch |> String.concat " "
+ $"--filter-namespace {atoms}"
+
+let componentTests = "tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj"
+
+printfn $"dotnet test {componentTests} --no-build -c Release {filterArgs}"
+
+for (proj, _, tag) in otherProjects |> List.filter (fun (_, b, tag) -> b = batch && matchesPlatform tag) do
+ printfn $"dotnet test {proj} --no-build -c Release"
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index bf1b81f2bef..ccc7e44ffa3 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/tests/EndToEndBuildTests/Directory.Build.props b/tests/EndToEndBuildTests/Directory.Build.props
index 496de8645f5..f97db4e1684 100644
--- a/tests/EndToEndBuildTests/Directory.Build.props
+++ b/tests/EndToEndBuildTests/Directory.Build.props
@@ -7,6 +7,8 @@
3.2.2
3.2.2
2.0.2
+ 8.0.0
+ 17.14.1
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
index 6b7ab73f2f9..ff40debc105 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
+++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
@@ -107,6 +107,8 @@
+
+
diff --git a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
index 009d4f0a8fa..cf8cc25e837 100644
--- a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
+++ b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
@@ -128,6 +128,8 @@
+
+