Fix Helix SDK test isolation: current-directory race in XHarness tests - #17571
Conversation
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ss tests Co-authored-by: mmitche <8725170+mmitche@users.noreply.github.com>
Co-authored-by: mmitche <8725170+mmitche@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate filesystem-semantics issues remain unresolved.
Review details
Suppressed comments (5)
src/Common/Microsoft.Arcade.Test.Common/MockFileSystem.cs:70
- When an existing file is opened with
FileMode.Create, this now constructsMockFileStreamfrom the old contents instead of truncating it. A caller that writes a shorter replacement from position 0 leaves stale trailing bytes inFiles(for example,ProvisioningProfileProviderusesFileMode.Create), so the mock no longer matches realFileStreamsemantics. Preserve create/truncate behavior when initializing the writable stream.
=> access == FileAccess.Read && FileExists(path)
? new MemoryStream(Encoding.UTF8.GetBytes(Files[path]))
: new MockFileStream(this, path);
src/Microsoft.DotNet.Helix/JobMonitor/TestResults/LocalTestResultsReader.cs:306
File.OpenReadused here before this change opens the file withFileShare.Read, butFileSystem.GetFileStreamdelegates tonew FileStream(path, mode, access), whose default isFileShare.None. That makes the production reader acquire an exclusive read handle, so a second reader of the same result file can now fail with anIOException; preserve the previous sharing semantics in the filesystem abstraction (for example, useFileShare.Readfor read-only opens).
=> XmlReader.Create(_fileSystem.GetFileStream(filePath, FileMode.Open, FileAccess.Read), new XmlReaderSettings
src/Microsoft.DotNet.Helix/JobMonitor/TestResults/LocalTestResultsReader.cs:26
- All reader tests now construct the new three-argument overload with
MockFileSystem, so the default constructor used byTestResultProcessor(which creates the realFileSystem) is no longer exercised. A regression in the defaultIFileSystemwiring or actualFileStreambehavior could therefore pass this suite; retain at least one test that uses the normal constructor with a real temporary result file.
: this(logger, attachmentMode, new FileSystem())
src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAndroidWorkItemsTests.cs:149
- This only checks that
PayloadArchivestarts with the project directory. It would pass if the task emitted a different archive filename or otherwise changed the metadata while staying under that directory, so it does not verify the stated unchanged payload metadata contract. Assert the exact archive path derived from the relative APK.
_task.WorkItems.Single().GetMetadata("PayloadArchive").Should().StartWith(projectDirectory);
src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAppleWorkItemsTests.cs:174
- This only checks that
PayloadArchivestarts with the project directory. It would pass if the task emitted a different archive filename or otherwise changed the metadata while staying under that directory, so it does not verify the stated unchanged payload metadata contract. Assert the exact archive path derived from the relative app bundle.
_task.WorkItems.Single().GetMetadata("PayloadArchive").Should().StartWith(projectDirectory);
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
CreateXHarnessAppleWorkItemsTests.ArchivePayloadIsOverwrittenfails intermittently:LocalTestResultsReaderTestsmutatesEnvironment.CurrentDirectorywhile XHarness tests resolve relative paths throughTaskEnvironment.Fallback, which reads the process current directory on every lookup. When the interleaving is unlucky,PrepareWorkItemresolvesapps/System.Foo.appto a path theMockFileSystemnever registered, the task logs an error, andInvokeExecutereturns false.Test-only change. Parallel execution is preserved — no nonparallel collection, sleeps, retries, or production behavior changes.
Changes
Dropped the global mutation (
LocalTestResultsReaderTests): the test already passes a fully qualified path to the reader, so the set/restore ofEnvironment.CurrentDirectorywas pure global-state churn.Explicit task environment (
CreateXHarnessApple/AndroidWorkItemsTests): each test instance gets its own project directory, so setup registration and production resolution anchor to the same place regardless of interleaving.Failure diagnostics: added
MockBuildEngine.ErrorSummaryand threaded it through the success assertions, so an unexpected task failure reports the logged error instead of onlyExpected boolean to be True, but found False.Regression coverage:
RelativeAppBundlePathsResolveAgainstTheProjectDirectory/RelativeApkPathsResolveAgainstTheProjectDirectoryassert relative payload paths land underTaskEnvironment.ProjectDirectoryand reach the archive manager andPayloadArchivemetadata unchanged.To double check: