diff --git a/.ado/publish.yml b/.ado/publish.yml index ced192aa..fece3ea3 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -33,13 +33,13 @@ parameters: - Name: osx_x64 Pool: name: Azure Pipelines - vmImage: macos-14 + vmImage: macos-15 os: macOs TargetRuntime: osx-x64 - Name: osx_arm64 Pool: name: Azure Pipelines - vmImage: macos-14-arm64 + vmImage: macos-15-arm64 os: macOS TargetRuntime: osx-arm64 - Name: linux_x64 @@ -91,28 +91,28 @@ parameters: - Name: osx_x64_net90 Pool: name: Azure Pipelines - vmImage: macos-14 + vmImage: macos-15 os: macOS TargetRuntime: osx-x64 DotNetVersion: net9.0 - Name: osx_x64_net100 Pool: name: Azure Pipelines - vmImage: macos-14 + vmImage: macos-15 os: macOS TargetRuntime: osx-x64 DotNetVersion: net10.0 - Name: osx_arm64_net90 Pool: name: Azure Pipelines - vmImage: macos-14-arm64 + vmImage: macos-15-arm64 os: macOS TargetRuntime: osx-arm64 DotNetVersion: net9.0 - Name: osx_arm64_net100 Pool: name: Azure Pipelines - vmImage: macos-14-arm64 + vmImage: macos-15-arm64 os: macOS TargetRuntime: osx-arm64 DotNetVersion: net10.0 @@ -258,6 +258,17 @@ extends: ${{ if and( variables.DisableOsxArm64CodeQL, eq( MatrixEntry.TargetRuntime, 'osx-arm64' )) }}: ONEES_ENFORCED_CODEQL_ENABLED: false + templateContext: + outputs: + # Publish .trx results + test-case build logs even on failure, for diagnosis. + # -$(System.JobAttempt) keeps the name unique when re-running failed jobs. + - output: pipelineArtifact + condition: succeededOrFailed() + artifactName: test-logs-${{ MatrixEntry.Name }}-$(System.JobAttempt) + targetPath: $(Build.StagingDirectory)/test/${{ MatrixEntry.DotNetVersion }}-Release + sbomEnabled: false + isProduction: false + pool: ${{ MatrixEntry.Pool }} steps: @@ -302,10 +313,20 @@ extends: displayName: Run tests env: TRACE_NODE_API_HOST: 1 + + # Collect per-test-case native build logs next to the .trx for the test-logs artifact. + - task: CopyFiles@2 + displayName: Stage test-case build logs + condition: succeededOrFailed() continueOnError: true + inputs: + sourceFolder: $(Build.SourcesDirectory)/out/obj/Release/TestCases + contents: '**/*.log' + targetFolder: "$(Build.StagingDirectory)/test/${{ MatrixEntry.DotNetVersion }}-Release/build-logs" - task: PublishTestResults@2 displayName: Publish test results + condition: succeededOrFailed() inputs: testRunTitle: Test ${{ MatrixEntry.TargetRuntime }} ${{ MatrixEntry.DotnetVersion }} testResultsFormat: 'VSTest' diff --git a/Directory.Packages.props b/Directory.Packages.props index 3fdb20c6..973b56fb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,7 +10,7 @@ - + diff --git a/test/GCTests.cs b/test/GCTests.cs index 441d21e9..dc1b4f04 100644 --- a/test/GCTests.cs +++ b/test/GCTests.cs @@ -53,13 +53,21 @@ public void GCHandles() Assert.Equal(3 + 5 + 2, JSRuntimeContext.Current.GCHandleCount); }); - nodejs.GC(); - - nodejs.Run(() => + // JS GC is asynchronous, so pump a bounded number of cycles until the two temporary + // handles are released rather than asserting after a single GC. + long handleCount = 0; + for (int attempt = 0; attempt < 20; attempt++) { - // After GC, the handle count should have reverted back to the original set. - Assert.Equal(3 + 5, JSRuntimeContext.Current.GCHandleCount); - }); + nodejs.GC(); + nodejs.Run(() => { handleCount = JSRuntimeContext.Current.GCHandleCount; }); + if (handleCount == 3 + 5) + { + break; + } + } + + // After GC, the handle count should have reverted back to the original set. + Assert.Equal(3 + 5, handleCount); } [Fact] @@ -102,12 +110,18 @@ public void GCObjects() // The JS object released its reference to the .NET object, but it hasn't been GC'd yet. Assert.Equal(1ul, DotnetClass.Instances); - // Request a .NET GC, and wait for finalizers (which run on another thread after the GC). - System.GC.Collect(); - System.GC.WaitForPendingFinalizers(); + // Releasing the .NET object takes more than one GC pass (the first finalizes the JS + // wrapper and frees its handle; a later one collects the object) and GC is async across + // both runtimes, so pump a bounded number of cycles instead of asserting after one pass. + for (int attempt = 0; DotnetClass.Instances != 0 && attempt < 20; attempt++) + { + nodejs.GC(); + nodejs.Run(() => { }); + System.GC.Collect(); + System.GC.WaitForPendingFinalizers(); + } - // Now the .NET object should have been finalized/GC'd, as indicated by the - // instance count decremented by the finalizer. + // The finalizer should have run, decrementing the instance count. Assert.Equal(0ul, DotnetClass.Instances); }