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);
}