Skip to content

Commit c04f5ad

Browse files
authored
Tests: add helper Stress attribute for stress testing (dotnet#18452)
1 parent 9c8efd2 commit c04f5ad

File tree

8 files changed

+81
-17
lines changed

8 files changed

+81
-17
lines changed

DEVGUIDE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,15 @@ For example:
252252
module TimeCritical =
253253
```
254254

255+
For stress testing async code you can use a custom `FSharp.Test.StressAttribute`.
256+
For example, applied to a single xUnit test case:
257+
```fsharp
258+
[<Theory; Stress(Count = 1000)>]
259+
```
260+
it will start it many times at the same time, and execute in parallel.
261+
262+
263+
255264

256265
### Updating FCS surface area baselines
257266

eng/Build.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,8 @@ try {
615615

616616
if ($testDesktop -and $ci) {
617617
$bgJob = TestUsingMSBuild -testProject "$RepoRoot\tests\fsharp\FSharpSuite.Tests.fsproj" -targetFramework $script:desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharpSuite.Tests\" -asBackgroundJob $true
618-
618+
619+
TestUsingMSBuild -testProject "$RepoRoot\tests\FSharp.Test.Utilities\FSharp.Test.Utilities.fsproj" -targetFramework $script:desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Test.Utilities\"
619620
TestUsingMSBuild -testProject "$RepoRoot\tests\FSharp.Compiler.ComponentTests\FSharp.Compiler.ComponentTests.fsproj" -targetFramework $script:desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Compiler.ComponentTests\"
620621
TestUsingMSBuild -testProject "$RepoRoot\tests\FSharp.Compiler.Service.Tests\FSharp.Compiler.Service.Tests.fsproj" -targetFramework $script:desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Compiler.Service.Tests\"
621622
TestUsingMSBuild -testProject "$RepoRoot\tests\FSharp.Compiler.Private.Scripting.UnitTests\FSharp.Compiler.Private.Scripting.UnitTests.fsproj" -targetFramework $script:desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Compiler.Private.Scripting.UnitTests\"

eng/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ BuildSolution
331331

332332
if [[ "$test_core_clr" == true ]]; then
333333
coreclrtestframework=$tfm
334+
Test --testproject "$repo_root/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj" --targetframework $coreclrtestframework
334335
Test --testproject "$repo_root/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj" --targetframework $coreclrtestframework
335336
Test --testproject "$repo_root/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj" --targetframework $coreclrtestframework
336337
Test --testproject "$repo_root/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj" --targetframework $coreclrtestframework

tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,18 +422,19 @@ let ``Cancel running jobs with the same key`` () =
422422
// detach requests from their running computations
423423
cts.Cancel()
424424

425+
// Cancel the Get requests, leaving the jobs running unawaited.
425426
for job in jobsToCancel do assertTaskCanceled job
426427

428+
// Start another request.
427429
let job = cache.Get(key 11, work) |> Async.StartAsTask
428430

429431
// up til now the jobs should have been running unobserved
430432
let current = eventsWhen events (received Requested)
431433
Assert.Equal(0, current |> countOf Canceled)
432434

433-
// waitUntil events (countOf Canceled >> (=) 10)
434-
435-
waitUntil events (received Started)
435+
waitUntil events (countOf Started >> (=) 11)
436436

437+
// Allow the single current request to finish.
437438
jobCanContinue.Set() |> ignore
438439

439440
job.Wait()

tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
1111
<Optimize>false</Optimize>
1212
<Tailcalls>false</Tailcalls>
13-
<IsTestProject>false</IsTestProject>
13+
<UnitTestType>xunit</UnitTestType>
14+
<IsTestProject>true</IsTestProject>
1415
<OtherFlags>$(OtherFlags) --warnon:1182 --realsig-</OtherFlags>
1516
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
1617
<!--Extra xUnit customizations, not required for the test suite to work, but can speed up local test runs and help with debugging.-->
@@ -23,10 +24,9 @@
2324
</PropertyGroup>
2425

2526
<ItemGroup>
26-
<ProjectCapability Remove="TestContainer" />
27-
</ItemGroup>
28-
29-
<ItemGroup>
27+
<Content Include="xunit.runner.json">
28+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
29+
</Content>
3030
<Compile Include="..\scripts\scriptlib.fsx">
3131
<Link>scriptlib.fsx</Link>
3232
</Compile>
@@ -46,11 +46,10 @@
4646
<Compile Include="ReflectionHelper.fs" />
4747
<Compile Include="SurfaceArea.fs" />
4848
<Compile Include="XunitHelpers.fs" />
49+
<Compile Include="XunitSetup.fs" />
50+
<Compile Include="Tests.fs" />
4951
</ItemGroup>
5052

51-
<ItemGroup>
52-
<None Include="XunitSetup.fs" />
53-
</ItemGroup>
5453

5554
<ItemGroup>
5655
<ProjectReference Include="$(FSharpSourcesRoot)\Compiler\FSharp.Compiler.Service.fsproj" />
@@ -70,11 +69,6 @@
7069
<ProjectReference Include="$(FSharpSourcesRoot)\FSharp.Build\FSharp.Build.fsproj" />
7170
</ItemGroup>
7271

73-
74-
<ItemGroup>
75-
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
76-
</ItemGroup>
77-
7872
<!-- Runtime dependencies. Beware. -->
7973
<ItemGroup>
8074
<PackageReference Include="Microsoft.NETCore.ILDAsm" Version="$(MicrosoftNETCoreILDAsmVersion)" />

tests/FSharp.Test.Utilities/Tests.fs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module FSharp.Test.UtilitiesTests
2+
3+
open System
4+
open System.Threading
5+
open Xunit
6+
open FSharp.Test
7+
8+
type RunOrFail(name) =
9+
let mutable count = 0
10+
member _.Run(shouldFail: bool) =
11+
let count = Interlocked.Increment &count
12+
if shouldFail && count = 42 then
13+
failwith $"{name}, failed as expected on {count}"
14+
else
15+
printfn $"{name}, iteration {count} passed"
16+
count
17+
18+
let passing = RunOrFail "Passing"
19+
let failing = RunOrFail "Failing"
20+
21+
[<Theory(Skip = "Explicit, unskip to run"); Stress(true, Count = 50)>]
22+
let ``Stress attribute should catch intermittent failure`` shouldFail _ =
23+
failing.Run shouldFail
24+
25+
[<Theory; Stress(Count = 5)>]
26+
let ``Stress attribute works`` _ =
27+
passing.Run false
28+
29+
[<Fact>]
30+
let ``TestConsole captures output`` () =
31+
let rnd = Random()
32+
let task n =
33+
async {
34+
use console = new TestConsole.ExecutionCapture()
35+
do! Async.Sleep (rnd.Next 50)
36+
printf $"Hello, world! {n}"
37+
do! Async.Sleep (rnd.Next 50)
38+
eprintf $"Some error {n}"
39+
return console.OutText, console.ErrorText
40+
}
41+
42+
let expected = [ for n in 0 .. 9 -> $"Hello, world! {n}", $"Some error {n}" ]
43+
44+
let results = Seq.init 10 task |> Async.Parallel |> Async.RunSynchronously
45+
Assert.Equal(expected, results)

tests/FSharp.Test.Utilities/XunitHelpers.fs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ open OpenTelemetry.Trace
2121
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Method, AllowMultiple = false)>]
2222
type RunTestCasesInSequenceAttribute() = inherit Attribute()
2323

24+
// Helper for stress testing.
25+
// Runs a test case many times in parallel.
26+
// Example usage: [<Theory; Stress(Count = 1000)>]
27+
type StressAttribute([<ParamArray>] data: obj array) =
28+
inherit DataAttribute()
29+
member val Count = 1 with get, set
30+
override this.GetData _ = Seq.init this.Count (fun i -> [| yield! data; yield box i |])
31+
2432
#if XUNIT_EXTRAS
2533

2634
// To use xUnit means to customize it. The following abomination adds 2 features:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3+
"appDomain": "denied",
4+
"parallelizeAssembly": true
5+
}

0 commit comments

Comments
 (0)