Skip to content

Commit b97ae0b

Browse files
committed
fix(ci): prevent type-provider integration test hangs
1 parent 41288c1 commit b97ae0b

File tree

4 files changed

+91
-18
lines changed

4 files changed

+91
-18
lines changed

.github/workflows/ci-main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ permissions:
1313
jobs:
1414
build-and-test:
1515
runs-on: ubuntu-latest
16+
timeout-minutes: 25
1617

1718
steps:
1819
- name: Checkout

.github/workflows/ci-pr.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ permissions:
1717
jobs:
1818
build-and-test:
1919
runs-on: ubuntu-latest
20+
timeout-minutes: 25
2021

2122
steps:
2223
- name: Checkout

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to FScript are documented in this file.
55
## [Unreleased]
66

77
- Added a new `MagnusOpera.FScript.TypeProvider` package that type-checks scripts at compile time and exposes exported functions as strongly-typed F# members with runtime signature compatibility checks.
8+
- Stabilized type-provider integration tests in CI by hardening spawned `dotnet` process handling and adding job-level CI timeouts to prevent long hangs.
89

910
## [0.59.0]
1011

tests/FScript.TypeProvider.Tests/TypeProviderIntegrationTests.fs

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@ namespace FScript.TypeProvider.Tests
33
open System
44
open System.Diagnostics
55
open System.IO
6+
open System.Text
67
open NUnit.Framework
78

9+
type ProcessRunResult =
10+
{ ExitCode: int
11+
Stdout: string
12+
Stderr: string
13+
Duration: TimeSpan
14+
TimedOut: bool }
15+
816
[<TestFixture>]
917
type TypeProviderIntegrationTests () =
1018
let findRepoRoot () =
@@ -21,7 +29,14 @@ type TypeProviderIntegrationTests () =
2129

2230
loop TestContext.CurrentContext.TestDirectory
2331

24-
let runProcess (workingDirectory: string) (fileName: string) (arguments: string) =
32+
let runProcess
33+
(workingDirectory: string)
34+
(fileName: string)
35+
(arguments: string)
36+
(timeout: TimeSpan)
37+
: ProcessRunResult =
38+
let stdout = StringBuilder()
39+
let stderr = StringBuilder()
2540
let startInfo =
2641
ProcessStartInfo(
2742
FileName = fileName,
@@ -31,42 +46,97 @@ type TypeProviderIntegrationTests () =
3146
RedirectStandardError = true,
3247
UseShellExecute = false)
3348

49+
use outputClosed = new System.Threading.ManualResetEventSlim(false)
50+
use errorClosed = new System.Threading.ManualResetEventSlim(false)
51+
3452
use proc = new Process(StartInfo = startInfo)
53+
proc.OutputDataReceived.Add(fun args ->
54+
match args.Data with
55+
| null -> outputClosed.Set()
56+
| value -> stdout.AppendLine(value) |> ignore)
57+
proc.ErrorDataReceived.Add(fun args ->
58+
match args.Data with
59+
| null -> errorClosed.Set()
60+
| value -> stderr.AppendLine(value) |> ignore)
61+
62+
let startedAt = DateTimeOffset.UtcNow
3563
proc.Start() |> ignore
36-
let stdout = proc.StandardOutput.ReadToEnd()
37-
let stderr = proc.StandardError.ReadToEnd()
38-
proc.WaitForExit()
39-
proc.ExitCode, stdout, stderr
64+
proc.BeginOutputReadLine()
65+
proc.BeginErrorReadLine()
66+
67+
let completed = proc.WaitForExit(int timeout.TotalMilliseconds)
68+
if not completed then
69+
try
70+
proc.Kill(true)
71+
with _ -> ()
72+
proc.WaitForExit()
73+
74+
outputClosed.Wait(TimeSpan.FromSeconds(5.0)) |> ignore
75+
errorClosed.Wait(TimeSpan.FromSeconds(5.0)) |> ignore
76+
let endedAt = DateTimeOffset.UtcNow
77+
78+
{ ExitCode = proc.ExitCode
79+
Stdout = stdout.ToString()
80+
Stderr = stderr.ToString()
81+
Duration = endedAt - startedAt
82+
TimedOut = not completed }
83+
84+
let formatResult (commandLine: string) (result: ProcessRunResult) =
85+
let output = result.Stdout + "\n" + result.Stderr
86+
let trimmedOutput =
87+
if output.Length > 12000 then
88+
output.Substring(0, 12000) + "\n...[truncated]..."
89+
else
90+
output
91+
$"Command: {commandLine}\nExitCode: {result.ExitCode}\nTimedOut: {result.TimedOut}\nDuration: {result.Duration}\nOutput:\n{trimmedOutput}"
92+
93+
let buildCommand fixturePath =
94+
$"build \"{fixturePath}\" -c Release --disable-build-servers /p:UseSharedCompilation=false /nodeReuse:false"
4095

4196
[<Test>]
4297
member _.``type provider builds valid script fixture`` () =
4398
let repoRoot = findRepoRoot ()
4499
let fixturePath = Path.Combine(repoRoot, "tests", "FScript.TypeProvider.Tests.Fixtures.Valid")
45-
let exitCode, stdout, stderr = runProcess repoRoot "dotnet" $"build \"{fixturePath}\" -c Release"
46-
let output = stdout + "\n" + stderr
47-
Assert.That(exitCode, Is.EqualTo(0), $"Expected build success. Output:\n{output}")
100+
let command = buildCommand fixturePath
101+
let commandLine = "dotnet " + command
102+
let result = runProcess repoRoot "dotnet" command (TimeSpan.FromSeconds(120.0))
103+
let detail = formatResult commandLine result
104+
Assert.That(result.TimedOut, Is.False, $"Build command timed out.\n{detail}")
105+
Assert.That(result.ExitCode, Is.EqualTo(0), $"Expected build success.\n{detail}")
48106

49107
[<Test>]
50108
member _.``type provider fails compilation on script type error`` () =
51109
let repoRoot = findRepoRoot ()
52110
let fixturePath = Path.Combine(repoRoot, "tests", "FScript.TypeProvider.Tests.Fixtures.Invalid")
53-
let exitCode, stdout, stderr = runProcess repoRoot "dotnet" $"build \"{fixturePath}\" -c Release"
54-
let output = stdout + "\n" + stderr
55-
Assert.That(exitCode, Is.Not.EqualTo(0), "Expected build failure for invalid script.")
56-
Assert.That(output, Does.Contain("Failed to type-check FScript"))
111+
let command = buildCommand fixturePath
112+
let commandLine = "dotnet " + command
113+
let result = runProcess repoRoot "dotnet" command (TimeSpan.FromSeconds(120.0))
114+
let output = result.Stdout + "\n" + result.Stderr
115+
let detail = formatResult commandLine result
116+
Assert.That(result.TimedOut, Is.False, $"Build command timed out.\n{detail}")
117+
Assert.That(result.ExitCode, Is.Not.EqualTo(0), $"Expected build failure for invalid script.\n{detail}")
118+
Assert.That(output, Does.Contain("Failed to type-check FScript"), detail)
57119

58120
[<Test>]
59121
member _.``type provider fails unsupported exported signature`` () =
60122
let repoRoot = findRepoRoot ()
61123
let fixturePath = Path.Combine(repoRoot, "tests", "FScript.TypeProvider.Tests.Fixtures.Unsupported")
62-
let exitCode, stdout, stderr = runProcess repoRoot "dotnet" $"build \"{fixturePath}\" -c Release"
63-
let output = stdout + "\n" + stderr
64-
Assert.That(exitCode, Is.Not.EqualTo(0), "Expected build failure for unsupported signature.")
65-
Assert.That(output, Does.Contain("not supported in exported signatures"))
124+
let command = buildCommand fixturePath
125+
let commandLine = "dotnet " + command
126+
let result = runProcess repoRoot "dotnet" command (TimeSpan.FromSeconds(120.0))
127+
let output = result.Stdout + "\n" + result.Stderr
128+
let detail = formatResult commandLine result
129+
Assert.That(result.TimedOut, Is.False, $"Build command timed out.\n{detail}")
130+
Assert.That(result.ExitCode, Is.Not.EqualTo(0), $"Expected build failure for unsupported signature.\n{detail}")
131+
Assert.That(output, Does.Contain("not supported in exported signatures"), detail)
66132

67133
[<Test>]
68134
member _.``runtime resolver override can replace implementation and mismatch is rejected`` () =
69135
let repoRoot = findRepoRoot ()
70136
let fixturePath = Path.Combine(repoRoot, "tests", "FScript.TypeProvider.Tests.Fixtures.RuntimeOverride")
71-
let exitCode, stdout, stderr = runProcess repoRoot "dotnet" $"run --project \"{fixturePath}\" -c Release"
72-
Assert.That(exitCode, Is.EqualTo(0), $"Expected runtime fixture success.\nStdout:\n{stdout}\nStderr:\n{stderr}")
137+
let command = $"run --project \"{fixturePath}\" -c Release --disable-build-servers"
138+
let commandLine = "dotnet " + command
139+
let result = runProcess repoRoot "dotnet" command (TimeSpan.FromSeconds(180.0))
140+
let detail = formatResult commandLine result
141+
Assert.That(result.TimedOut, Is.False, $"Runtime command timed out.\n{detail}")
142+
Assert.That(result.ExitCode, Is.EqualTo(0), $"Expected runtime fixture success.\n{detail}")

0 commit comments

Comments
 (0)