mirror of
https://github.com/actions/runner.git
synced 2025-12-27 20:13:15 +08:00
Runner Support for executing Node24 Actions (#3940)
This commit is contained in:
@@ -1659,6 +1659,76 @@ runs:
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void LoadsNode24ActionDefinition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange.
|
||||
Setup();
|
||||
const string Content = @"
|
||||
# Container action
|
||||
name: 'Hello World'
|
||||
description: 'Greet the world and record the time'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
greeting: # id of input
|
||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||
required: true
|
||||
default: 'Hello'
|
||||
entryPoint: # id of input
|
||||
description: 'optional docker entrypoint overwrite.'
|
||||
required: false
|
||||
outputs:
|
||||
time: # id of output
|
||||
description: 'The time we did the greeting'
|
||||
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||
runs:
|
||||
using: 'node24'
|
||||
main: 'task.js'
|
||||
";
|
||||
Pipelines.ActionStep instance;
|
||||
string directory;
|
||||
CreateAction(yamlContent: Content, instance: out instance, directory: out directory);
|
||||
|
||||
// Act.
|
||||
Definition definition = _actionManager.LoadAction(_ec.Object, instance);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(definition);
|
||||
Assert.Equal(directory, definition.Directory);
|
||||
Assert.NotNull(definition.Data);
|
||||
Assert.NotNull(definition.Data.Inputs); // inputs
|
||||
Dictionary<string, string> inputDefaults = new(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var input in definition.Data.Inputs)
|
||||
{
|
||||
var name = input.Key.AssertString("key").Value;
|
||||
var value = input.Value.AssertScalar("value").ToString();
|
||||
|
||||
_hc.GetTrace().Info($"Default: {name} = {value}");
|
||||
inputDefaults[name] = value;
|
||||
}
|
||||
|
||||
Assert.Equal(2, inputDefaults.Count);
|
||||
Assert.True(inputDefaults.ContainsKey("greeting"));
|
||||
Assert.Equal("Hello", inputDefaults["greeting"]);
|
||||
Assert.True(string.IsNullOrEmpty(inputDefaults["entryPoint"]));
|
||||
Assert.NotNull(definition.Data.Execution); // execution
|
||||
|
||||
Assert.NotNull(definition.Data.Execution as NodeJSActionExecutionData);
|
||||
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
||||
Assert.Equal("node24", (definition.Data.Execution as NodeJSActionExecutionData).NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
|
||||
@@ -502,6 +502,49 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Load_Node24Action()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Arrange
|
||||
Setup();
|
||||
|
||||
var actionManifest = new ActionManifestManager();
|
||||
actionManifest.Initialize(_hc);
|
||||
|
||||
//Act
|
||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "node24action.yml"));
|
||||
|
||||
//Assert
|
||||
Assert.Equal("Hello World", result.Name);
|
||||
Assert.Equal("Greet the world and record the time", result.Description);
|
||||
Assert.Equal(2, result.Inputs.Count);
|
||||
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||
Assert.Equal(1, result.Deprecated.Count);
|
||||
|
||||
Assert.True(result.Deprecated.ContainsKey("greeting"));
|
||||
result.Deprecated.TryGetValue("greeting", out string value);
|
||||
Assert.Equal("This property has been deprecated", value);
|
||||
|
||||
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||
|
||||
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||
|
||||
Assert.Equal("main.js", nodeAction.Script);
|
||||
Assert.Equal("node24", nodeAction.NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -758,7 +801,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
//Assert
|
||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||
Assert.Contains($"Failed to load {action_path}", err.Message);
|
||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16', 'node20' or 'node24'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[InlineData("node12", "node20")]
|
||||
[InlineData("node16", "node20")]
|
||||
[InlineData("node20", "node20")]
|
||||
[InlineData("node24", "node24")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
@@ -41,7 +42,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
var hf = new HandlerFactory();
|
||||
hf.Initialize(hc);
|
||||
|
||||
// Server Feature Flag
|
||||
// Setup variables
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
Variables serverVariables = new(hc, variables);
|
||||
|
||||
@@ -72,5 +73,48 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Node24ExplicitlyRequested_HonoredByDefault()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var hf = new HandlerFactory();
|
||||
hf.Initialize(hc);
|
||||
|
||||
// Basic variables setup
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
Variables serverVariables = new(hc, variables);
|
||||
|
||||
_ec.Setup(x => x.Global).Returns(new GlobalContext()
|
||||
{
|
||||
Variables = serverVariables,
|
||||
EnvironmentVariables = new Dictionary<string, string>()
|
||||
});
|
||||
|
||||
// Act - Node 24 explicitly requested in action.yml
|
||||
var data = new NodeJSActionExecutionData();
|
||||
data.NodeVersion = "node24";
|
||||
var handler = hf.Create(
|
||||
_ec.Object,
|
||||
new ScriptReference(),
|
||||
new Mock<IStepHost>().Object,
|
||||
data,
|
||||
new Dictionary<string, string>(),
|
||||
new Dictionary<string, string>(),
|
||||
new Variables(hc, new Dictionary<string, VariableValue>()),
|
||||
"",
|
||||
new List<JobExtensionRunner>()
|
||||
) as INodeScriptActionHandler;
|
||||
|
||||
// Assert - should be node24 as requested
|
||||
Assert.Equal("node24", handler.Data.NodeVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
src/Test/L0/Worker/Handlers/NodeHandlerL0.cs
Normal file
35
src/Test/L0/Worker/Handlers/NodeHandlerL0.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker.Handlers
|
||||
{
|
||||
public sealed class NodeHandlerL0
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void NodeJSActionExecutionDataSupportsNode24()
|
||||
{
|
||||
// Create NodeJSActionExecutionData with node24
|
||||
var nodeJSData = new NodeJSActionExecutionData
|
||||
{
|
||||
NodeVersion = "node24",
|
||||
Script = "test.js"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("node24", nodeJSData.NodeVersion);
|
||||
Assert.Equal(ActionExecutionType.NodeJS, nodeJSData.ExecutionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,60 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task DetermineNode24RuntimeVersionInAlpineContainerAsync()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var sh = new ContainerStepHost();
|
||||
sh.Initialize(hc);
|
||||
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||
|
||||
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||
{
|
||||
output.Add("alpine");
|
||||
})
|
||||
.ReturnsAsync(0);
|
||||
|
||||
// Act.
|
||||
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal("node24_alpine", nodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task DetermineNode24RuntimeVersionInUnknownContainerAsync()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var sh = new ContainerStepHost();
|
||||
sh.Initialize(hc);
|
||||
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||
|
||||
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||
{
|
||||
output.Add("github");
|
||||
})
|
||||
.ReturnsAsync(0);
|
||||
|
||||
// Act.
|
||||
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal("node24", nodeVersion);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
63
src/Test/L0/Worker/StepHostNodeVersionL0.cs
Normal file
63
src/Test/L0/Worker/StepHostNodeVersionL0.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class StepHostNodeVersionL0
|
||||
{
|
||||
private Mock<IExecutionContext> _ec;
|
||||
private DefaultStepHost _defaultStepHost;
|
||||
|
||||
public StepHostNodeVersionL0()
|
||||
{
|
||||
_ec = new Mock<IExecutionContext>();
|
||||
_defaultStepHost = new DefaultStepHost();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CheckNodeVersionForArm32_Node24OnArm32Linux()
|
||||
{
|
||||
// Test via NodeUtil directly
|
||||
string preferredVersion = "node24";
|
||||
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
|
||||
// On ARM32 Linux, we should fall back to node20
|
||||
bool isArm32 = RuntimeInformation.ProcessArchitecture == Architecture.Arm ||
|
||||
Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")?.Contains("ARM") == true;
|
||||
bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
if (isArm32 && isLinux)
|
||||
{
|
||||
// Should downgrade to node20 on ARM32 Linux
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
Assert.NotNull(warningMessage);
|
||||
Assert.Contains("Node 24 is not supported on Linux ARM32 platforms", warningMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On non-ARM32 platforms, should pass through the version unmodified
|
||||
Assert.Equal("node24", nodeVersion);
|
||||
Assert.Null(warningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CheckNodeVersionForArm32_PassThroughNonNode24Versions()
|
||||
{
|
||||
string preferredVersion = "node20";
|
||||
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
|
||||
// Should never modify the version for non-node24 inputs
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
Assert.Null(warningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user