From d85da899ff267700c79957f6ae8f1de10c308556 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Sun, 24 May 2026 23:33:49 +0900 Subject: [PATCH 1/4] Fix oversize file diagnostics for #1603 --- USER_GUIDE.md | 4 +- changelog.d/unreleased/1603.fixed.md | 19 ++++++++ src/CodeIndex/Cli/IndexCommandRunner.cs | 14 ++++++ src/CodeIndex/Cli/QueryCommandRunner.cs | 2 +- src/CodeIndex/Indexer/Scanning/FileIndexer.cs | 44 ++++++++++++++++++- .../IndexCommandRunnerTests.cs | 38 ++++++++++++++++ 6 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 changelog.d/unreleased/1603.fixed.md diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 2db041fef7..fe0802ee94 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -1838,9 +1838,9 @@ When SQLite returns permission-style errors such as `SQLITE_AUTH`, `SQLITE_PERM` - Recovery: fix file/directory permissions, or exclude the path via `.cdidxignore`. The index keeps running across the rest of the tree; no rebuild is required after permissions are fixed — a normal `cdidx index .` will pick up the now-readable files. 12. **File rejected: too large** - - Symptom: `--verbose` shows `[ERR ] : File too large (N MiB > M MiB limit). Override with --max-file-bytes or CDIDX_MAX_FILE_BYTES= when this source file is intentionally indexable.` (the file becomes part of the run's `errors` count), and the file does not appear in search. + - Symptom: `validate --kind file_too_large` reports `File too large (N MiB > M MiB limit). Override with --max-file-bytes or CDIDX_MAX_FILE_BYTES= when this source file is intentionally indexable.` The file is listed in `files`, but no chunks, symbols, or references are indexed for it, so it does not appear in search. - Cause: the file exceeds the configured per-file size limit. Indexing huge generated files would waste tokens and bloat the DB. - - Recovery: shrink or split the file, add it to `.cdidxignore`, or raise the limit with `cdidx index . --max-file-bytes 50M` / `CDIDX_MAX_FILE_BYTES=50M` when the file is legitimate source. Generated artifacts should generally be gitignored too. Note that any `[ERR ]` files in a run leave readiness flags unstamped, so resolve oversize entries before depending on `graph_table_available` / `issues_table_available` results. + - Recovery: shrink or split the file, add it to `.cdidxignore`, or raise the limit with `cdidx index . --max-file-bytes 50M` / `CDIDX_MAX_FILE_BYTES=50M` when the file is legitimate source. Generated artifacts should generally be gitignored too. 13. **Feature unavailable on trimmed / AOT build** (`E009_FEATURE_UNAVAILABLE`) - Symptom: `Error [E009_FEATURE_UNAVAILABLE]: ...` when invoking flags such as `--json` on a build that lacks the required code paths. diff --git a/changelog.d/unreleased/1603.fixed.md b/changelog.d/unreleased/1603.fixed.md new file mode 100644 index 0000000000..bf7bbb37e0 --- /dev/null +++ b/changelog.d/unreleased/1603.fixed.md @@ -0,0 +1,19 @@ +--- +category: fixed +issues: + - 1603 +affected: + - src/CodeIndex/Cli/IndexCommandRunner.cs + - src/CodeIndex/Cli/QueryCommandRunner.cs + - src/CodeIndex/Indexer/Scanning/FileIndexer.cs + - tests/CodeIndex.Tests/IndexCommandRunnerTests.cs + - USER_GUIDE.md +--- + +## English + +- **Oversize files now surface through `validate` (#1603)** — files above the configured indexing size limit are persisted with a `file_too_large` issue instead of disappearing as per-file errors, so `validate --kind file_too_large` explains why their chunks, symbols, and references are absent. + +## 日本語 + +- **サイズ上限を超えたファイルが `validate` で見えるようになりました (#1603)** — 設定された indexing size limit を超えたファイルは per-file error として消えるのではなく `file_too_large` issue として保存されるため、`validate --kind file_too_large` で chunks / symbols / references が存在しない理由を確認できます。 diff --git a/src/CodeIndex/Cli/IndexCommandRunner.cs b/src/CodeIndex/Cli/IndexCommandRunner.cs index 7fff6cfc73..6ce7c7947d 100644 --- a/src/CodeIndex/Cli/IndexCommandRunner.cs +++ b/src/CodeIndex/Cli/IndexCommandRunner.cs @@ -3401,6 +3401,20 @@ void StopJsonHeartbeat() { extractionResults.Add(FullScanFileWorkItem.Skipped(filePath, ex.Message), cancellationToken); } + catch (FileIndexer.FileTooLargeSkippedException ex) + { + var record = indexer.BuildSkippedFileRecord(filePath); + var issue = new FileIssue + { + Path = ex.RelativePath, + Kind = "file_too_large", + Line = 0, + Message = ex.Message, + }; + extractionResults.Add( + FullScanFileWorkItem.Success(filePath, record, string.Empty, [], ex.Message, [], [], [], [issue]), + cancellationToken); + } catch (Exception ex) { extractionResults.Add(FullScanFileWorkItem.Failure(filePath, ex), cancellationToken); diff --git a/src/CodeIndex/Cli/QueryCommandRunner.cs b/src/CodeIndex/Cli/QueryCommandRunner.cs index 9710ca3b14..84fcad0315 100644 --- a/src/CodeIndex/Cli/QueryCommandRunner.cs +++ b/src/CodeIndex/Cli/QueryCommandRunner.cs @@ -3347,7 +3347,7 @@ private static JsonObject BuildUnusedResultsByBucketJson(IEnumerable _maxFileSizeBytes) - throw new InvalidOperationException(BuildFileTooLargeMessage(initialLength, grewDuringRead: false)); + throw new FileTooLargeSkippedException( + NormalizePathSeparators(relativePath), + initialLength, + _maxFileSizeBytes, + BuildFileTooLargeMessage(initialLength, grewDuringRead: false)); // Pre-size the accumulator to the observed length but cap initial capacity // at the configured limit so a tampered Length cannot force a huge up-front allocation. @@ -2387,7 +2391,11 @@ public static string NormalizePathSeparators(string path) { total += read; if (total > _maxFileSizeBytes) - throw new InvalidOperationException(BuildFileTooLargeMessage(total, grewDuringRead: true)); + throw new FileTooLargeSkippedException( + NormalizePathSeparators(relativePath), + total, + _maxFileSizeBytes, + BuildFileTooLargeMessage(total, grewDuringRead: true)); accumulator.Write(buffer, 0, read); } bytes = accumulator.ToArray(); @@ -2494,6 +2502,27 @@ public static string NormalizePathSeparators(string path) return (record, content, bytes, warning); } + public FileRecord BuildSkippedFileRecord(string absolutePath) + { + if (!IsFilePathSyntaxIndexable(absolutePath)) + throw new InvalidOperationException("Cannot index a file path that contains NUL or control characters."); + + var relativePath = Path.GetRelativePath(_projectRoot, absolutePath); + var normalizedRelativePath = NormalizePathSeparators(relativePath); + var ioPath = LongPath.EnsureWindowsPrefix(absolutePath); + var info = new FileInfo(ioPath); + return new FileRecord + { + Path = normalizedRelativePath, + Lang = TryDetectLanguage(absolutePath).Language, + Size = info.Exists ? info.Length : 0, + Lines = 0, + Checksum = null, + Modified = info.Exists ? info.LastWriteTimeUtc : DateTime.MinValue, + Generated = HasGeneratedCodeFileName(normalizedRelativePath), + }; + } + internal static bool IsFilePathSyntaxIndexable(string path) { foreach (var c in path) @@ -2995,6 +3024,17 @@ internal static bool ContainsIndexBlockingNullByte(byte[] rawBytes) internal sealed class BinaryFileSkippedException(string message) : InvalidOperationException(message); + internal sealed class FileTooLargeSkippedException( + string relativePath, + long actualBytes, + long limitBytes, + string message) : InvalidOperationException(message) + { + public string RelativePath { get; } = relativePath; + public long ActualBytes { get; } = actualBytes; + public long LimitBytes { get; } = limitBytes; + } + public static bool HasConflictMarkers(string content) => TryGetConflictMarkerLine(content, out _); diff --git a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs index 15acb55b54..d11b0edca5 100644 --- a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs @@ -80,6 +80,44 @@ public void Run_NullByteFile_SkipsWithoutPersistingPartialRows() } } + [Fact] + public void Run_FileAboveMaxFileBytes_PersistsFileTooLargeIssue() + { + var projectRoot = CreateTempProject(); + try + { + var filePath = Path.Combine(projectRoot, "large.py"); + File.WriteAllText(filePath, "print('start')\n" + new string('a', 256)); + + var (exitCode, json) = RunAndCaptureJson([projectRoot, "--max-file-bytes", "128", "--json"]); + + Assert.Equal(CommandExitCodes.Success, exitCode); + Assert.Equal("success", json.GetProperty("status").GetString()); + Assert.Equal(0, json.GetProperty("summary").GetProperty("errors").GetInt32()); + + var dbPath = Path.Combine(projectRoot, ".cdidx", "codeindex.db"); + Assert.Equal(1, CountRows(dbPath, "files")); + Assert.Equal(0, CountRows(dbPath, "chunks")); + Assert.Equal(0, CountRows(dbPath, "symbols")); + Assert.Equal(0, CountRows(dbPath, "symbol_references")); + + using var db = new DbContext(dbPath); + db.TryMigrateForRead(); + var reader = new DbReader(db.Connection, db.IsReadOnly); + var issue = Assert.Single(reader.GetIssues("file_too_large")); + Assert.Equal("large.py", issue.Path); + Assert.Equal(0, issue.Line); + Assert.Contains("File too large", issue.Message); + Assert.Contains("--max-file-bytes", issue.Message); + Assert.Contains(FileIndexer.MaxFileSizeEnvironmentVariable, issue.Message); + } + finally + { + SqliteConnection.ClearAllPools(); + DeleteDirectory(projectRoot); + } + } + [Fact] public void Run_NewIndexDatabase_RunsAnalyzeAfterSuccessfulIndex() { From bebc8fc36c597554133ea126b06fb26f4b7f2e4d Mon Sep 17 00:00:00 2001 From: Widthdom Date: Sun, 24 May 2026 23:44:34 +0900 Subject: [PATCH 2/4] Handle partial oversize indexing for #1603 --- src/CodeIndex/Cli/IndexCommandRunner.cs | 35 +++++++++++ .../IndexCommandRunnerTests.cs | 61 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/src/CodeIndex/Cli/IndexCommandRunner.cs b/src/CodeIndex/Cli/IndexCommandRunner.cs index 2cfee649da..50ca078fcf 100644 --- a/src/CodeIndex/Cli/IndexCommandRunner.cs +++ b/src/CodeIndex/Cli/IndexCommandRunner.cs @@ -2143,6 +2143,41 @@ void ThrowIfUpdateCancelled() continue; } + if (ex is FileIndexer.FileTooLargeSkippedException fileTooLarge) + { + if (fileBatchMarked) + writer.ClearBatchInProgress(); + + DemoteReadinessOnce(); + writer.MarkBatchInProgress(); + using var txn = writer.BeginTransaction(); + var skippedRecord = indexer.BuildSkippedFileRecord(absPath); + writer.PurgeStaleFilesSharingChecksum(projectRoot, skippedRecord.Path, skippedRecord.Checksum); + if (projectRootWritten) + writer.PurgeStaleFilesSharingDirectoryAndStem(projectRoot, skippedRecord.Path); + WriteProjectRootOnce(); + var fileId = writer.UpsertFile(skippedRecord); + writer.InsertChunks([]); + writer.InsertSymbols([]); + writer.InsertReferences([]); + writer.InsertIssues(fileId, + [ + new FileIssue + { + Path = fileTooLarge.RelativePath, + Kind = "file_too_large", + Line = 0, + Message = fileTooLarge.Message, + }, + ]); + writer.ClearBatchInProgress(); + txn.Commit(); + + updated++; + ftsMutated = true; + continue; + } + if (ex is FileNotFoundException or DirectoryNotFoundException) { if (fileBatchMarked) diff --git a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs index e53e3ed9bc..1fa0547c54 100644 --- a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs @@ -118,6 +118,38 @@ public void Run_FileAboveMaxFileBytes_PersistsFileTooLargeIssue() } } + [Fact] + public void RunFiles_FileAboveMaxFileBytes_PersistsFileTooLargeIssue() + { + var projectRoot = CreateTempProject(); + try + { + var filePath = Path.Combine(projectRoot, "large.py"); + File.WriteAllText(filePath, "print('start')\n" + new string('a', 256)); + + var (exitCode, json) = RunAndCaptureJson([projectRoot, "--files", "large.py", "--max-file-bytes", "128", "--json"]); + + Assert.Equal(CommandExitCodes.Success, exitCode); + Assert.Equal("success", json.GetProperty("status").GetString()); + Assert.Equal(0, json.GetProperty("summary").GetProperty("errors").GetInt32()); + + var dbPath = Path.Combine(projectRoot, ".cdidx", "codeindex.db"); + Assert.Equal(1, CountRows(dbPath, "files")); + Assert.Equal(0, CountRows(dbPath, "chunks")); + Assert.Equal(0, CountRows(dbPath, "symbols")); + Assert.Equal(0, CountRows(dbPath, "symbol_references")); + + var issue = Assert.Single(ReadFileIssues(dbPath, "file_too_large")); + Assert.Equal("large.py", issue.Path); + Assert.Contains("File too large", issue.Message); + } + finally + { + SqliteConnection.ClearAllPools(); + DeleteDirectory(projectRoot); + } + } + [Fact] public void Run_NewIndexDatabase_RunsAnalyzeAfterSuccessfulIndex() { @@ -7880,6 +7912,35 @@ private static int CountRows(string dbPath, string tableName) return Convert.ToInt32(command.ExecuteScalar()); } + private static List ReadFileIssues(string dbPath, string kind) + { + using var connection = new SqliteConnection($"Data Source={dbPath}"); + connection.Open(); + using var command = connection.CreateCommand(); + command.CommandText = """ + SELECT f.path, i.kind, i.line, i.message + FROM file_issues i + JOIN files f ON f.id = i.file_id + WHERE i.kind = @kind + ORDER BY f.path + """; + command.Parameters.AddWithValue("@kind", kind); + using var reader = command.ExecuteReader(); + var issues = new List(); + while (reader.Read()) + { + issues.Add(new FileIssue + { + Path = reader.GetString(0), + Kind = reader.GetString(1), + Line = reader.GetInt32(2), + Message = reader.GetString(3), + }); + } + + return issues; + } + private (int ExitCode, JsonElement Json, string Stderr) RunAndCaptureJsonWithStderr(string[] args) { lock (TestConsoleLock.Gate) From b476f3bf0a8d6d7f6be2cc038dc59b5f15fb18cc Mon Sep 17 00:00:00 2001 From: Widthdom Date: Sun, 24 May 2026 23:46:15 +0900 Subject: [PATCH 3/4] Drop unrelated merge artifacts for #1603 --- DEVELOPER_GUIDE.md | 15 +++++++++------ README.md | 4 ++-- TESTING_GUIDE.md | 4 ++-- USER_GUIDE.md | 16 ++++++++-------- changelog.d/unreleased/+net9-test-docs.docs.md | 16 ++++++++++++++++ changelog.d/unreleased/1904.docs.md | 4 ++-- 6 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 changelog.d/unreleased/+net9-test-docs.docs.md diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 947cfe5773..65874f7046 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -10,9 +10,10 @@ dotnet test dotnet run --project src/CodeIndex -- [options] ``` -Development, CI, and NuGet tool packaging target `net8.0`. Use a .NET 8.x SDK -for supported and tested builds; newer major .NET releases are outside the -supported/tested matrix until CI covers them. +The production CLI and NuGet tool packaging target `net8.0`. The test project +multi-targets `net8.0;net9.0`, and CI runs the test suite on both frameworks +across Linux, Windows, and macOS. Use a .NET SDK that can restore and run both +target frameworks when validating the full CI-equivalent test matrix. For test suite structure, shared helpers, and test-writing conventions, see [TESTING_GUIDE.md](TESTING_GUIDE.md). @@ -1661,9 +1662,11 @@ dotnet test dotnet run --project src/CodeIndex -- [options] ``` -開発、CI、NuGet ツールのパッケージングは `net8.0` を対象にしています。 -サポート済みかつテスト済みのビルドには .NET 8.x SDK を使ってください。 -CI で対象になるまでは、より新しいメジャー .NET リリースはサポート・テスト対象外です。 +製品版 CLI と NuGet ツールのパッケージングは `net8.0` を対象にしています。 +テストプロジェクトは `net8.0;net9.0` の multi-target で、CI は Linux、 +Windows、macOS の各 lane で両方の framework に対してテストスイートを実行します。 +CI 相当のフル検証を行う場合は、両方の target framework を restore / 実行できる +.NET SDK を使ってください。 テストスイートの構成、共有ヘルパー、テスト作法については [TESTING_GUIDE.md#テストガイド](TESTING_GUIDE.md#テストガイド) を参照してください。 diff --git a/README.md b/README.md index 8d0e49fdfb..cc0ec91cd9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![CodeQL](https://github.com/Widthdom/CodeIndex/actions/workflows/codeql.yml/badge.svg)](https://github.com/Widthdom/CodeIndex/actions/workflows/codeql.yml) [![Release](https://github.com/Widthdom/CodeIndex/actions/workflows/release.yml/badge.svg)](https://github.com/Widthdom/CodeIndex/actions/workflows/release.yml) -![.NET 8.x](https://img.shields.io/badge/.NET-8.x-512BD4?logo=dotnet&logoColor=white) +![.NET 8.x / 9.x tests](https://img.shields.io/badge/.NET-8.x%20%2F%209.x%20tests-512BD4?logo=dotnet&logoColor=white) ![C#](https://img.shields.io/badge/C%23-12-239120?logo=csharp&logoColor=white) ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) ![License](https://img.shields.io/badge/License-FSL--1.1--ALv2-orange) @@ -232,7 +232,7 @@ details. [![CodeQL](https://github.com/Widthdom/CodeIndex/actions/workflows/codeql.yml/badge.svg)](https://github.com/Widthdom/CodeIndex/actions/workflows/codeql.yml) [![Release](https://github.com/Widthdom/CodeIndex/actions/workflows/release.yml/badge.svg)](https://github.com/Widthdom/CodeIndex/actions/workflows/release.yml) -![.NET 8.x](https://img.shields.io/badge/.NET-8.x-512BD4?logo=dotnet&logoColor=white) +![.NET 8.x / 9.x tests](https://img.shields.io/badge/.NET-8.x%20%2F%209.x%20tests-512BD4?logo=dotnet&logoColor=white) ![C#](https://img.shields.io/badge/C%23-12-239120?logo=csharp&logoColor=white) ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) ![License](https://img.shields.io/badge/License-FSL--1.1--ALv2-orange) diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md index 0c52ff5d6d..fa2676ab72 100644 --- a/TESTING_GUIDE.md +++ b/TESTING_GUIDE.md @@ -19,7 +19,7 @@ Use the full suite by default. Use targeted filters only while iterating locally ## Test Stack - Framework: xUnit -- Target framework: `net8.0` +- Target frameworks: `net8.0` and `net9.0` - Main test project: `tests/CodeIndex.Tests/CodeIndex.Tests.csproj` - Common direct test-only packages: `Microsoft.NET.Test.Sdk`, `xunit`, `xunit.runner.visualstudio`, `coverlet.collector`, `Microsoft.Data.Sqlite`, `FsCheck.Xunit` - These test-only packages are separate from the production dependency rule in `src/CodeIndex`, which still allows only `Microsoft.Data.Sqlite` at runtime. @@ -204,8 +204,8 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests" ## テストスタック - フレームワーク: xUnit -- 対象フレームワーク: `net8.0` - メインのテストプロジェクト: `tests/CodeIndex.Tests/CodeIndex.Tests.csproj` +- 対象フレームワーク: `net8.0` と `net9.0` - 主な直接参照の test-only package: `Microsoft.NET.Test.Sdk`、`xunit`、`xunit.runner.visualstudio`、`coverlet.collector`、`Microsoft.Data.Sqlite`、`FsCheck.Xunit` - これらの test-only package は `src/CodeIndex` の本番依存ルールとは別であり、runtime 側は引き続き `Microsoft.Data.Sqlite` のみを許容する。 - `FsCheck.Xunit` はランダム生成入力に対する普遍的不変条件(never-throws、idempotence、"出力が downstream consumer で parse 可能" 等)を表明する property-based テスト専用です。例ベースの `[Fact]` / `[Theory]` を置き換えるのではなく補完するもので、普遍量化された主張なら FsCheck、特定の具体ケースが契約なら例ベースという形で使い分けてください。 diff --git a/USER_GUIDE.md b/USER_GUIDE.md index fe0802ee94..11fca3c294 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -10,7 +10,7 @@ AI/MCP setup, language list, and troubleshooting details. [![CodeQL](https://github.com/Widthdom/CodeIndex/actions/workflows/codeql.yml/badge.svg)](https://github.com/Widthdom/CodeIndex/actions/workflows/codeql.yml) [![Release](https://github.com/Widthdom/CodeIndex/actions/workflows/release.yml/badge.svg)](https://github.com/Widthdom/CodeIndex/actions/workflows/release.yml) -![.NET 8.x](https://img.shields.io/badge/.NET-8.x-512BD4?logo=dotnet&logoColor=white) +![.NET 8.x / 9.x tests](https://img.shields.io/badge/.NET-8.x%20%2F%209.x%20tests-512BD4?logo=dotnet&logoColor=white) ![C#](https://img.shields.io/badge/C%23-12-239120?logo=csharp&logoColor=white) ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) ![License](https://img.shields.io/badge/License-FSL--1.1--ALv2-orange) @@ -451,9 +451,9 @@ RUN export CDIDX_INSTALL_DIR=/usr/local/bin \ ### Option B: NuGet Global Tool Requires the [.NET 8.x SDK](https://dotnet.microsoft.com/download/dotnet/8.0). -CodeIndex targets `net8.0`; .NET 8.x is the supported and tested SDK/runtime -line. Newer major .NET releases are outside the supported/tested matrix until -CI covers them. +CodeIndex targets `net8.0`; .NET 8.x is the supported SDK/runtime line for the +published tool, while the CI test suite also covers the test project on +`net9.0`. ```bash dotnet tool install -g cdidx @@ -1876,7 +1876,7 @@ The short version: `version.json` is the single source of truth, and the maintai # cdidx(日本語) -![.NET 8.x](https://img.shields.io/badge/.NET-8.x-512BD4?logo=dotnet&logoColor=white) +![.NET 8.x / 9.x tests](https://img.shields.io/badge/.NET-8.x%20%2F%209.x%20tests-512BD4?logo=dotnet&logoColor=white) ![C#](https://img.shields.io/badge/C%23-12-239120?logo=csharp&logoColor=white) ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) ![License](https://img.shields.io/badge/License-FSL--1.1--ALv2-orange) @@ -2305,9 +2305,9 @@ RUN export CDIDX_INSTALL_DIR=/usr/local/bin \ ### 方法B: NuGet グローバルツール [.NET 8.x SDK](https://dotnet.microsoft.com/download/dotnet/8.0) が必要です。 -CodeIndex は `net8.0` を対象にしており、.NET 8.x がサポート済みかつ -テスト済みの SDK/runtime 系列です。CI で対象になるまでは、より新しい -メジャー .NET リリースはサポート・テスト対象外です。 +CodeIndex は `net8.0` を対象にしており、公開ツールのサポート対象 +SDK/runtime 系列は .NET 8.x です。一方で、CI のテストスイートは +テストプロジェクトを `net9.0` でも検証します。 ```bash dotnet tool install -g cdidx diff --git a/changelog.d/unreleased/+net9-test-docs.docs.md b/changelog.d/unreleased/+net9-test-docs.docs.md new file mode 100644 index 0000000000..36f3ff69a8 --- /dev/null +++ b/changelog.d/unreleased/+net9-test-docs.docs.md @@ -0,0 +1,16 @@ +--- +category: docs +affected: + - DEVELOPER_GUIDE.md + - README.md + - TESTING_GUIDE.md + - USER_GUIDE.md +--- + +## English + +- **Documented .NET 9 test coverage** — README, USER_GUIDE, DEVELOPER_GUIDE, and TESTING_GUIDE now distinguish the `net8.0` production tool target from the `net8.0` / `net9.0` test matrix. + +## 日本語 + +- **.NET 9 のテストカバレッジを文書化しました** — README、USER_GUIDE、DEVELOPER_GUIDE、TESTING_GUIDE で、製品版ツールの `net8.0` target と `net8.0` / `net9.0` の test matrix を区別して説明するようにしました。 diff --git a/changelog.d/unreleased/1904.docs.md b/changelog.d/unreleased/1904.docs.md index dee4c22819..76b4ea9006 100644 --- a/changelog.d/unreleased/1904.docs.md +++ b/changelog.d/unreleased/1904.docs.md @@ -10,8 +10,8 @@ affected: ## English -- **Clarified the supported .NET line for NuGet tool installs (#1904)** — README and USER_GUIDE now consistently describe .NET 8.x as the supported and tested SDK/runtime line instead of mixing exact `.NET 8.0` and permissive `.NET 8+` wording. +- **Clarified the supported .NET line for NuGet tool installs (#1904)** — README and USER_GUIDE now consistently describe .NET 8.x as the supported SDK/runtime line for the published tool instead of mixing exact `.NET 8.0` and permissive `.NET 8+` wording. ## 日本語 -- **NuGet ツールインストールでサポートする .NET 系列を明確化しました (#1904)** — README と USER_GUIDE は、`.NET 8.0` と `.NET 8+` の混在表記ではなく、.NET 8.x をサポート済みかつテスト済みの SDK/runtime 系列として一貫して説明するようになりました。 +- **NuGet ツールインストールでサポートする .NET 系列を明確化しました (#1904)** — README と USER_GUIDE は、`.NET 8.0` と `.NET 8+` の混在表記ではなく、公開ツールでサポートする SDK/runtime 系列を .NET 8.x として一貫して説明するようになりました。 From 361082b43edecb759b8fd6c93da25809040f5342 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Mon, 25 May 2026 00:31:27 +0900 Subject: [PATCH 4/4] Fix oversized file CI expectations --- tests/CodeIndex.Tests/FileIndexerTests.cs | 15 ++++++++----- .../IndexCommandRunnerTests.cs | 22 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/CodeIndex.Tests/FileIndexerTests.cs b/tests/CodeIndex.Tests/FileIndexerTests.cs index b9bb2b0c34..8d345584ef 100644 --- a/tests/CodeIndex.Tests/FileIndexerTests.cs +++ b/tests/CodeIndex.Tests/FileIndexerTests.cs @@ -343,7 +343,7 @@ public void BuildRecordWithRawBytes_OverExplicitMaxFileBytes_ThrowsActionableOve var indexer = new FileIndexer(tempDir, ignoreCase: false, ignoreRuleRoot: null, maxFileSizeBytes: 4); - var ex = Assert.Throws(() => indexer.BuildRecordWithRawBytes(path)); + var ex = Assert.Throws(() => indexer.BuildRecordWithRawBytes(path)); Assert.Contains("File too large", ex.Message); Assert.Contains("--max-file-bytes", ex.Message); Assert.Contains(FileIndexer.MaxFileSizeEnvironmentVariable, ex.Message); @@ -3470,10 +3470,10 @@ public void StripLineLeadingInvisibles_ConsecutiveLineLeadingInvisibles_AllStrip } [Fact] - public void BuildRecord_ThrowsForOversizedFile() + public void BuildRecord_ThrowsFileTooLargeSkippedExceptionForOversizedFile() { - // Files exceeding the default cap should throw InvalidOperationException - // 既定上限を超えるファイルはInvalidOperationExceptionを投げる + // Files exceeding the default cap should carry structured skip metadata. + // 既定上限を超えるファイルは structured skip metadata を持つ例外を投げる。 var tempDir = Path.Combine(Path.GetTempPath(), $"codeindex_test_{Guid.NewGuid():N}"); try { @@ -3485,7 +3485,10 @@ public void BuildRecord_ThrowsForOversizedFile() stream.SetLength(FileIndexer.DefaultMaxFileSizeBytes + 1); var indexer = new FileIndexer(tempDir); - Assert.Throws(() => indexer.BuildRecord(filePath)); + var ex = Assert.Throws(() => indexer.BuildRecord(filePath)); + Assert.Equal("large.py", ex.RelativePath); + Assert.Equal(FileIndexer.DefaultMaxFileSizeBytes + 1, ex.ActualBytes); + Assert.Equal(FileIndexer.DefaultMaxFileSizeBytes, ex.LimitBytes); } finally { @@ -3512,7 +3515,7 @@ public void BuildRecord_DefaultRejectsTenMiBFileBeforeReadingPayload() var indexer = new FileIndexer(tempDir); var before = GC.GetAllocatedBytesForCurrentThread(); - var ex = Assert.Throws(() => indexer.BuildRecord(filePath)); + var ex = Assert.Throws(() => indexer.BuildRecord(filePath)); var allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.Contains("File too large", ex.Message); diff --git a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs index 1fa0547c54..5afb2cb748 100644 --- a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs @@ -3512,7 +3512,7 @@ public interface IParseable } [Fact] - public void Run_FullScan_WithIndexingErrors_PrintsRecoveryWarning() + public void Run_FullScan_WithOversizedFile_PrintsSkipWarningWithoutRecoveryWarning() { var projectRoot = CreateTempProject(); try @@ -3523,8 +3523,9 @@ public void Run_FullScan_WithIndexingErrors_PrintsRecoveryWarning() var (exitCode, _, stderr) = RunCliInSubprocess([projectRoot], projectRoot); Assert.Equal(CommandExitCodes.Success, exitCode); - Assert.Contains("Some files failed to index", stderr); - Assert.Contains("rerun `cdidx index", stderr); + Assert.Contains("[WARN] File too large", stderr); + Assert.DoesNotContain("Some files failed to index", stderr); + Assert.DoesNotContain("rerun `cdidx index", stderr); } finally { @@ -3617,7 +3618,7 @@ public void Run_FullScan_CancelledAfterReadinessDemotion_RollsBackExistingIndex( } [Fact] - public void Run_UpdateMode_WithIndexingErrors_PrintsRecoveryWarning() + public void Run_UpdateMode_WithOversizedFile_PrintsSkipWarningWithoutRecoveryWarning() { var projectRoot = CreateTempProject(); try @@ -3632,8 +3633,9 @@ public void Run_UpdateMode_WithIndexingErrors_PrintsRecoveryWarning() var (exitCode, _, stderr) = RunCliInSubprocess([projectRoot, "--files", "huge.py"], projectRoot); Assert.Equal(CommandExitCodes.Success, exitCode); - Assert.Contains("Some files failed to update", stderr); - Assert.Contains("rerun `cdidx index", stderr); + Assert.Equal(string.Empty, stderr); + Assert.DoesNotContain("Some files failed to update", stderr); + Assert.DoesNotContain("rerun `cdidx index", stderr); } finally { @@ -6722,7 +6724,7 @@ public void Run_UpdateMode_DoesNotRestampFoldReadyWhenSymbolExtractorVersionMism } [Fact] - public void Run_UpdateMode_ClearsHotspotFamilyTrustOnPartialFailure() + public void Run_UpdateMode_RestampsHotspotFamilyTrustOnOversizedFileSkip() { var projectRoot = CreateTempProject(); try @@ -6741,11 +6743,11 @@ public void Run_UpdateMode_ClearsHotspotFamilyTrustOnPartialFailure() var (exitCode2, json2) = RunAndCaptureJson([projectRoot, "--files", "app.cs", "--json"]); Assert.Equal(CommandExitCodes.Success, exitCode2); - Assert.Equal("partial", json2.GetProperty("status").GetString()); - Assert.Equal(1, json2.GetProperty("summary").GetProperty("errors").GetInt32()); + Assert.Equal("success", json2.GetProperty("status").GetString()); + Assert.Equal(0, json2.GetProperty("summary").GetProperty("errors").GetInt32()); using var verifyDb = new DbContext(dbPath); - Assert.Null(verifyDb.GetMetaString(DbContext.GetHotspotFamilyVersionMetaKey("csharp"))); + Assert.Equal(DbContext.HotspotFamilyVersion.ToString(System.Globalization.CultureInfo.InvariantCulture), verifyDb.GetMetaString(DbContext.GetHotspotFamilyVersionMetaKey("csharp"))); } finally {