From 0f8418486288404add5dcc06ddde7acb184fbac7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:05:01 +0000 Subject: [PATCH] [ci-fix-net11] De-flake Essentials.AI file-based tests: read shared TestData with FileShare.ReadWrite Parallel xUnit test classes read the same TestData files via File.ReadAllLines, which defaults to FileShare.Read and races on Windows, intermittently throwing IOException 'The process cannot access the file because it is being used by another process'. Route all shared-file reads through a DataStreamsHelper helper that opens with FileShare.ReadWrite. No assertions changed. Refs: dotnet/maui#36452 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TestHelpers/DataStreamsHelper.cs | 22 ++++++++++++++++++- .../JsonStreamChunkerTests/Integration.cs | 2 +- .../FileBasedTests.cs | 4 ++-- .../FileBasedTests.cs | 12 +++++----- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/AI/tests/Essentials.AI.UnitTests/TestHelpers/DataStreamsHelper.cs b/src/AI/tests/Essentials.AI.UnitTests/TestHelpers/DataStreamsHelper.cs index 36da3a6332cc..148af442b1c2 100644 --- a/src/AI/tests/Essentials.AI.UnitTests/TestHelpers/DataStreamsHelper.cs +++ b/src/AI/tests/Essentials.AI.UnitTests/TestHelpers/DataStreamsHelper.cs @@ -19,7 +19,27 @@ public static string GetTxtItinerary(string fileName) public static string[] GetFileLines(string fileName) { var path = GetFile(fileName); - return File.ReadAllLines(path); + return ReadAllLinesShared(path); + } + + // Reads all lines from a file while allowing other readers/writers to keep the + // file open concurrently. Test classes run in parallel and multiple theories read + // the same shared TestData files, which can otherwise race and throw an IOException + // ("The process cannot access the file because it is being used by another process") + // on Windows. Opening with FileShare.ReadWrite makes the shared reads deterministic. + public static string[] ReadAllLinesShared(string path) + { + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var reader = new StreamReader(stream); + + var lines = new List(); + string? line; + while ((line = reader.ReadLine()) is not null) + { + lines.Add(line); + } + + return lines.ToArray(); } public static IEnumerable JsonlItineraries diff --git a/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamChunkerTests/Integration.cs b/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamChunkerTests/Integration.cs index f366913eefd8..0e4d02e1e505 100644 --- a/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamChunkerTests/Integration.cs +++ b/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamChunkerTests/Integration.cs @@ -20,7 +20,7 @@ public void Process_FromJsonlFile_ProducesValidJsonMatchingFinalLine(string file // Arrange var chunker = new JsonStreamChunker(); - var lines = File.ReadAllLines(filePath); + var lines = DataStreamsHelper.ReadAllLinesShared(filePath); // Act var chunks = new List(); diff --git a/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamingRoundtripTests/FileBasedTests.cs b/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamingRoundtripTests/FileBasedTests.cs index c5b43a9a60ae..226a68e7beec 100644 --- a/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamingRoundtripTests/FileBasedTests.cs +++ b/src/AI/tests/Essentials.AI.UnitTests/Tests/JsonStreamingRoundtripTests/FileBasedTests.cs @@ -19,7 +19,7 @@ public void Roundtrip_FromJsonlFile_DeserializerProducesEquivalentResult(string _ = fileName; // We are not using the parameter here // Arrange - var lines = File.ReadAllLines(filePath); + var lines = DataStreamsHelper.ReadAllLinesShared(filePath); var finalLine = lines[^1]; // Act - pass each line through chunker and deserializer @@ -48,7 +48,7 @@ public void Roundtrip_FromJsonlFile_EachLineDeserializesProgressively(string fil _ = fileName; // We are not using the parameter here // This test validates that each intermediate line produces a valid partial object - var lines = File.ReadAllLines(filePath); + var lines = DataStreamsHelper.ReadAllLinesShared(filePath); var models = ProcessLines(lines); diff --git a/src/AI/tests/Essentials.AI.UnitTests/Tests/StreamingJsonDeserializerTests/FileBasedTests.cs b/src/AI/tests/Essentials.AI.UnitTests/Tests/StreamingJsonDeserializerTests/FileBasedTests.cs index b60de7522523..7d3a69a2ca21 100644 --- a/src/AI/tests/Essentials.AI.UnitTests/Tests/StreamingJsonDeserializerTests/FileBasedTests.cs +++ b/src/AI/tests/Essentials.AI.UnitTests/Tests/StreamingJsonDeserializerTests/FileBasedTests.cs @@ -18,7 +18,7 @@ public void ProcessChunk_TxtFile_DeserializesProgressively(string fileName, stri { _ = fileName; // Used for test display name var deserializer = new StreamingJsonDeserializer(DeserializationOptions); - var chunks = File.ReadAllLines(filePath); + var chunks = DataStreamsHelper.ReadAllLinesShared(filePath); Itinerary? lastItinerary = null; @@ -41,7 +41,7 @@ public void ProcessChunk_TxtFile_FinalResultMatchesDirectDeserialization(string { _ = fileName; // Used for test display name var deserializer = new StreamingJsonDeserializer(DeserializationOptions); - var chunks = File.ReadAllLines(filePath); + var chunks = DataStreamsHelper.ReadAllLinesShared(filePath); Itinerary? finalItinerary = null; @@ -65,7 +65,7 @@ public void ProcessChunk_TxtFile_FinalResultMatchesDirectDeserialization(string public void ProcessChunk_TxtFile_ShouldProgressDaysArray(string fileName, string filePath) { var deserializer = new StreamingJsonDeserializer(DeserializationOptions); - var chunks = File.ReadAllLines(filePath); + var chunks = DataStreamsHelper.ReadAllLinesShared(filePath); var daysProgression = new List<(int ChunkNumber, int? DaysCount, int? ActivitiesInFirstDay)>(); @@ -98,7 +98,7 @@ public void ProcessChunk_TxtFile_ShouldProgressDaysArray(string fileName, string public void ProcessChunk_TxtFile_ShouldProgressTitleField(string fileName, string filePath) { var deserializer = new StreamingJsonDeserializer(DeserializationOptions); - var chunks = File.ReadAllLines(filePath); + var chunks = DataStreamsHelper.ReadAllLinesShared(filePath); var titleProgression = new List(); @@ -129,7 +129,7 @@ public void ProcessChunk_TxtFile_EachChunkProducesNonEmptyObject(string fileName { _ = fileName; // Used for test display name var deserializer = new StreamingJsonDeserializer(DeserializationOptions); - var chunks = File.ReadAllLines(filePath); + var chunks = DataStreamsHelper.ReadAllLinesShared(filePath); foreach (var chunk in chunks) { @@ -152,7 +152,7 @@ public void ProcessChunk_TxtFile_UpdateRate(string fileName, string filePath) // partial property values, or unchanged content - 70% is a reasonable threshold. var deserializer = new StreamingJsonDeserializer(DeserializationOptions); - var chunks = File.ReadAllLines(filePath); + var chunks = DataStreamsHelper.ReadAllLinesShared(filePath); string? previousJson = null; int totalChunks = 0;