Skip to content

Commit 5de9d24

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix-issue1641-1812
2 parents 2c89787 + 4df3cc1 commit 5de9d24

8 files changed

Lines changed: 200 additions & 3 deletions

File tree

.github/workflows/dotnet.yml

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,41 @@ jobs:
182182
run: dotnet build tests/CodeIndex.Tests/CodeIndex.Tests.csproj --configuration Release --framework ${{ matrix.test-framework }} --no-restore
183183

184184
- name: Test
185-
run: dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj --configuration Release --framework ${{ matrix.test-framework }} --no-build --nologo --logger "trx;LogFileName=test_results.trx" --results-directory ./TestResults
185+
shell: pwsh
186+
run: |
187+
$testArgs = @(
188+
"test",
189+
"tests/CodeIndex.Tests/CodeIndex.Tests.csproj",
190+
"--configuration", "Release",
191+
"--framework", "${{ matrix.test-framework }}",
192+
"--no-build",
193+
"--nologo",
194+
"--settings", "tests/CodeIndex.Tests/CodeIndex.Tests.runsettings",
195+
"--blame-crash",
196+
"--blame-hang",
197+
"--blame-hang-timeout", "5m",
198+
"--logger", "trx;LogFileName=test_results.trx",
199+
"--results-directory", "./TestResults"
200+
)
201+
202+
dotnet @testArgs
203+
$firstExitCode = $LASTEXITCODE
204+
if ($firstExitCode -eq 0) {
205+
exit 0
206+
}
207+
208+
Write-Warning "Initial test run failed with exit code $firstExitCode. Rerunning once to classify possible flakiness."
209+
dotnet @testArgs
210+
$retryExitCode = $LASTEXITCODE
211+
if ($retryExitCode -eq 0) {
212+
New-Item -ItemType Directory -Force -Path ./TestResults | Out-Null
213+
"Initial test run failed, but the single retry passed. Treat this run as flaky and inspect TRX/blame artifacts." |
214+
Set-Content -Encoding UTF8 ./TestResults/flaky-retry.txt
215+
Write-Warning "Tests passed on retry; uploaded TestResults include flaky-retry.txt."
216+
exit 0
217+
}
218+
219+
exit $retryExitCode
186220
187221
- name: Summarize TRX telemetry
188222
if: always()
@@ -194,7 +228,14 @@ jobs:
194228
with:
195229
name: TestResults-${{ matrix.os }}-${{ matrix.test-framework }}
196230
if-no-files-found: warn
197-
path: TestResults/**/*.trx
231+
path: |
232+
TestResults/**/*.trx
233+
TestResults/**/*.txt
234+
TestResults/**/*.xml
235+
TestResults/**/*.dmp
236+
TestResults/**/*.dump
237+
TestResults/**/*Sequence*.xml
238+
TestResults/**/*.hangdump
198239
199240
- name: Publish
200241
if: matrix.os == 'ubuntu-latest' && matrix.test-framework == 'net8.0'

DEVELOPER_GUIDE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
```bash
88
dotnet build
99
dotnet test
10+
dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj --settings tests/CodeIndex.Tests/CodeIndex.Tests.runsettings --blame-crash --blame-hang --blame-hang-timeout 5m
1011
dotnet run --project src/CodeIndex -- <command> [options]
1112
```
1213

@@ -32,7 +33,11 @@ multi-targets `net8.0;net9.0`, and CI runs the test suite on both frameworks
3233
across Linux, Windows, and macOS. Use a .NET SDK that can restore and run both
3334
target frameworks when validating the full CI-equivalent test matrix.
3435

35-
For test suite structure, shared helpers, and test-writing conventions, see [TESTING_GUIDE.md](TESTING_GUIDE.md).
36+
CI uses `tests/CodeIndex.Tests/CodeIndex.Tests.runsettings` plus VSTest blame
37+
crash/hang collection and a bounded one-time retry to distinguish repeatable
38+
failures from pass-on-retry flakes. For test suite structure, shared helpers,
39+
state-isolation rules, timeout diagnostics, and test-writing conventions, see
40+
[TESTING_GUIDE.md](TESTING_GUIDE.md).
3641

3742
## CI / Artifact Distribution
3843

TESTING_GUIDE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ If you change test code, test helpers, test execution flow, or testing conventio
1111
```bash
1212
dotnet test
1313
dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj
14+
dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj --settings tests/CodeIndex.Tests/CodeIndex.Tests.runsettings --blame-crash --blame-hang --blame-hang-timeout 5m
1415
dotnet test --filter "FullyQualifiedName~GitHelperTests"
1516
```
1617

@@ -25,6 +26,7 @@ Use the full suite by default. Use targeted filters only while iterating locally
2526
- These test-only packages are separate from the production dependency rule in `src/CodeIndex`, which still allows only `Microsoft.Data.Sqlite` at runtime.
2627
- `FsCheck.Xunit` is reserved for property-based tests that assert universal invariants (never-throws contracts, idempotence, "output is parseable by downstream consumer") across randomly generated inputs. Use it to complement, not replace, the example-based `[Fact]` / `[Theory]` tests — pick FsCheck when the property is a universally quantified claim, and an example test when a specific concrete case is the contract.
2728
- Test parallelism: enabled by default across independent test classes. Tests that touch process-global state such as SQLite pool resets, environment variables, or current-directory overrides must use an explicit non-parallel collection, and tests that swap `Console.Out` / `Console.Error` must lock on `TestConsoleLock.Gate`.
29+
- CI runs the test project through `tests/CodeIndex.Tests/CodeIndex.Tests.runsettings`, enables VSTest blame crash and hang collection, applies a 30-minute session timeout plus 60-second xUnit long-running diagnostics, and reruns the suite once after an initial failure. If the retry passes, CI uploads `TestResults/flaky-retry.txt` with the TRX and blame artifacts so the run is treated as suspect instead of silently trusted.
2830

2931
## Test Layout
3032

@@ -85,6 +87,16 @@ The test project mirrors the production areas closely.
8587
- When a production comment or error string is bilingual, preserve that expectation in tests where it matters.
8688
- If a behavior change is user-visible, update tests, `CHANGELOG.md`, and any affected docs together.
8789

90+
### Shared state and parallelism audit
91+
92+
Use the inventory below before adding or moving a test class:
93+
94+
- SQLite pool resets, direct `SqliteConnection.ClearAllPools()` calls, process current-directory changes, or process-global environment variable mutation: put the class in the `SQLite pool sensitive` non-parallel collection.
95+
- Environment variables: use `EnvironmentVariableScope.Capture(...)` so setup failures and assertion failures restore the original values through one cleanup path.
96+
- `Console.Out` or `Console.Error` replacement: lock `TestConsoleLock.Gate` around the whole capture/swap window.
97+
- Temporary repositories and files: create them through `TestProjectHelper` when practical, and do not depend on user-level git config.
98+
- Long-running or performance-oriented tests: keep them skipped by default or give them broad deterministic budgets; if CI reports them in xUnit long-running diagnostics, first check runner load before tightening thresholds.
99+
88100
## Shared Helpers
89101

90102
### `TestProjectHelper`
@@ -198,6 +210,7 @@ Check the following:
198210
```bash
199211
dotnet test
200212
dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj
213+
dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj --settings tests/CodeIndex.Tests/CodeIndex.Tests.runsettings --blame-crash --blame-hang --blame-hang-timeout 5m
201214
dotnet test --filter "FullyQualifiedName~GitHelperTests"
202215
```
203216

@@ -212,6 +225,7 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"
212225
- これらの test-only package は `src/CodeIndex` の本番依存ルールとは別であり、runtime 側は引き続き `Microsoft.Data.Sqlite` のみを許容する。
213226
- `FsCheck.Xunit` はランダム生成入力に対する普遍的不変条件(never-throws、idempotence、"出力が downstream consumer で parse 可能" 等)を表明する property-based テスト専用です。例ベースの `[Fact]` / `[Theory]` を置き換えるのではなく補完するもので、普遍量化された主張なら FsCheck、特定の具体ケースが契約なら例ベースという形で使い分けてください。
214227
- テスト並列実行: 独立したテストクラス間ではデフォルトで有効です。SQLite pool の解放、環境変数の変更、カレントディレクトリの上書きのような process-global 状態を触るテストは、明示的な non-parallel collection に入れてください。`Console.Out` / `Console.Error` を差し替えるテストは `TestConsoleLock.Gate` で lock してください。
228+
- CI は `tests/CodeIndex.Tests/CodeIndex.Tests.runsettings` 経由でテストプロジェクトを実行し、VSTest の blame crash / hang 収集、30分のセッションタイムアウト、60秒の xUnit long-running 診断を有効にします。初回失敗時は suite を1回だけ再実行し、再実行で成功した場合は TRX / blame artifact と一緒に `TestResults/flaky-retry.txt` を upload して、その実行を疑わしい flaky run として扱います。
215229

216230
## テスト構成
217231

@@ -271,6 +285,16 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"
271285
- 本番コードのコメントやエラー文字列が英日併記前提なら、重要な箇所ではその期待もテストに反映する。
272286
- ユーザーに見える挙動を変えたら、テストに加えて `CHANGELOG.md` と関連ドキュメントも同じ変更に含める。
273287

288+
### 共有状態と並列実行の監査
289+
290+
テストクラスを追加または移動する前に、次の一覧を確認してください。
291+
292+
- SQLite pool reset、`SqliteConnection.ClearAllPools()` の直接呼び出し、プロセスの current directory 変更、process-global な環境変数変更: クラスを non-parallel な `SQLite pool sensitive` collection に入れる。
293+
- 環境変数: `EnvironmentVariableScope.Capture(...)` を使い、setup failure や assertion failure でも単一の cleanup 経路で元の値に戻す。
294+
- `Console.Out` / `Console.Error` の差し替え: capture / swap 期間全体を `TestConsoleLock.Gate` で lock する。
295+
- 一時 repo / file: 可能な限り `TestProjectHelper` 経由で作り、user-level の git config に依存しない。
296+
- 長時間または performance 系テスト: デフォルト skip にするか、決定的で十分広い budget を与える。CI の xUnit long-running 診断に出た場合は、閾値を締める前に runner 負荷を確認する。
297+
274298
## 共通ヘルパー
275299

276300
### `TestProjectHelper`
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1610
5+
affected:
6+
- .github/workflows/dotnet.yml
7+
- TESTING_GUIDE.md
8+
- DEVELOPER_GUIDE.md
9+
---
10+
11+
## English
12+
13+
- **CI flaky-test classification (#1610)** — Added a bounded one-time test retry in CI and a `flaky-retry.txt` artifact marker when the retry passes after an initial failure.
14+
15+
## 日本語
16+
17+
- **CI の flaky test 分類 (#1610)** — CI テストに1回限りの bounded retry を追加し、初回失敗後の再実行で成功した場合に `flaky-retry.txt` artifact marker を残すようにしました。
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1612
5+
affected:
6+
- TESTING_GUIDE.md
7+
- DEVELOPER_GUIDE.md
8+
---
9+
10+
## English
11+
12+
- **Test parallelism audit guidance (#1612)** — Documented the shared-state inventory for test classes that need serialized execution or explicit cleanup guards.
13+
14+
## 日本語
15+
16+
- **テスト並列実行の監査ガイド (#1612)** — 直列実行または明示的な cleanup guard が必要なテストクラス向けに、共有状態の inventory を文書化しました。
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1821
5+
affected:
6+
- .github/workflows/dotnet.yml
7+
- tests/CodeIndex.Tests/CodeIndex.Tests.runsettings
8+
- TESTING_GUIDE.md
9+
- DEVELOPER_GUIDE.md
10+
---
11+
12+
## English
13+
14+
- **CI test timeout and blame diagnostics (#1821)** — Added test runsettings with a session timeout and xUnit long-running diagnostics, enabled VSTest crash/hang blame capture in CI, and uploaded blame artifacts with TRX results.
15+
16+
## 日本語
17+
18+
- **CI テストの timeout / blame 診断 (#1821)** — セッションタイムアウトと xUnit long-running 診断を含む test runsettings を追加し、CI で VSTest の crash/hang blame 収集を有効化して、TRX と一緒に blame artifact をアップロードするようにしました。
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Xml.Linq;
2+
3+
namespace CodeIndex.Tests;
4+
5+
public class CiWorkflowTests
6+
{
7+
[Fact]
8+
public void DotnetWorkflow_RunsTestsWithRunsettingsBlameRetryAndArtifacts()
9+
{
10+
var workflow = File.ReadAllText(Path.Combine(GetRepositoryRoot(), ".github", "workflows", "dotnet.yml"));
11+
12+
Assert.Contains("--settings\", \"tests/CodeIndex.Tests/CodeIndex.Tests.runsettings", workflow);
13+
Assert.Contains("--blame-crash", workflow);
14+
Assert.Contains("--blame-hang", workflow);
15+
Assert.Contains("--blame-hang-timeout\", \"5m", workflow);
16+
Assert.Contains("Rerunning once to classify possible flakiness.", workflow);
17+
Assert.Contains("flaky-retry.txt", workflow);
18+
Assert.Contains("TestResults/**/*.trx", workflow);
19+
Assert.Contains("TestResults/**/*Sequence*.xml", workflow);
20+
Assert.Contains("TestResults/**/*.dmp", workflow);
21+
Assert.Contains("TestResults/**/*.dump", workflow);
22+
}
23+
24+
[Fact]
25+
public void Runsettings_DefinesSessionTimeoutAndXunitLongRunningDiagnostics()
26+
{
27+
var path = Path.Combine(GetRepositoryRoot(), "tests", "CodeIndex.Tests", "CodeIndex.Tests.runsettings");
28+
var document = XDocument.Load(path);
29+
30+
Assert.Equal(
31+
"1800000",
32+
document.Root?.Element("RunConfiguration")?.Element("TestSessionTimeout")?.Value);
33+
Assert.Equal(
34+
"60",
35+
document.Root?.Element("xUnit")?.Element("LongRunningTestSeconds")?.Value);
36+
Assert.Equal(
37+
"./TestResults",
38+
document.Root?.Element("RunConfiguration")?.Element("ResultsDirectory")?.Value);
39+
}
40+
41+
[Fact]
42+
public void TestingGuide_DocumentsSharedStateParallelismInventory()
43+
{
44+
var guide = File.ReadAllText(Path.Combine(GetRepositoryRoot(), "TESTING_GUIDE.md"));
45+
46+
Assert.Contains("Shared state and parallelism audit", guide);
47+
Assert.Contains("SQLite pool sensitive", guide);
48+
Assert.Contains("EnvironmentVariableScope.Capture", guide);
49+
Assert.Contains("TestConsoleLock.Gate", guide);
50+
Assert.Contains("TestProjectHelper", guide);
51+
Assert.Contains("共有状態と並列実行の監査", guide);
52+
}
53+
54+
private static string GetRepositoryRoot()
55+
{
56+
var dir = new DirectoryInfo(AppContext.BaseDirectory);
57+
while (dir != null)
58+
{
59+
if (File.Exists(Path.Combine(dir.FullName, "CodeIndex.sln")))
60+
return dir.FullName;
61+
dir = dir.Parent;
62+
}
63+
64+
throw new InvalidOperationException("Could not locate repository root / リポジトリルートを特定できませんでした");
65+
}
66+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RunSettings>
3+
<RunConfiguration>
4+
<ResultsDirectory>./TestResults</ResultsDirectory>
5+
<TestSessionTimeout>1800000</TestSessionTimeout>
6+
</RunConfiguration>
7+
<xUnit>
8+
<LongRunningTestSeconds>60</LongRunningTestSeconds>
9+
</xUnit>
10+
</RunSettings>

0 commit comments

Comments
 (0)