Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions .ado/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageVersion Include="Microsoft.JavaScript.LibNode" Version="20.1800.203" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.10.91" />
<PackageVersion Include="Nullability.Source" Version="2.1.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
Expand Down
36 changes: 25 additions & 11 deletions test/GCTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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);
}

Expand Down
Loading