mirror of
https://github.com/actions/runner.git
synced 2026-04-04 01:13:36 +08:00
Compare commits
2 Commits
feature/de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9275b59cf | ||
|
|
f0c228635e |
@@ -6,8 +6,8 @@ NODE_URL=https://nodejs.org/dist
|
|||||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||||
NODE20_VERSION="20.20.1"
|
NODE20_VERSION="20.20.2"
|
||||||
NODE24_VERSION="24.14.0"
|
NODE24_VERSION="24.14.1"
|
||||||
|
|
||||||
get_abs_path() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# exploits the fact that pwd will print abs path when no args
|
||||||
|
|||||||
@@ -316,7 +316,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Schema = _actionManifestSchema,
|
Schema = _actionManifestSchema,
|
||||||
// TODO: Switch to real tracewriter for cutover
|
// TODO: Switch to real tracewriter for cutover
|
||||||
TraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter(),
|
TraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter(),
|
||||||
AllowCaseFunction = false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expression values from execution context
|
// Expression values from execution context
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -315,7 +315,6 @@ namespace GitHub.Runner.Worker
|
|||||||
maxBytes: 10 * 1024 * 1024),
|
maxBytes: 10 * 1024 * 1024),
|
||||||
Schema = _actionManifestSchema,
|
Schema = _actionManifestSchema,
|
||||||
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
||||||
AllowCaseFunction = false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expression values from execution context
|
// Expression values from execution context
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -11,9 +9,6 @@ using System.Threading.Tasks;
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using Microsoft.DevTunnels.Connections;
|
|
||||||
using Microsoft.DevTunnels.Contracts;
|
|
||||||
using Microsoft.DevTunnels.Management;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker.Dap
|
namespace GitHub.Runner.Worker.Dap
|
||||||
@@ -35,7 +30,9 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DapDebugger : RunnerService, IDapDebugger
|
public sealed class DapDebugger : RunnerService, IDapDebugger
|
||||||
{
|
{
|
||||||
|
private const int _defaultPort = 4711;
|
||||||
private const int _defaultTimeoutMinutes = 15;
|
private const int _defaultTimeoutMinutes = 15;
|
||||||
|
private const string _portEnvironmentVariable = "ACTIONS_RUNNER_DAP_PORT";
|
||||||
private const string _timeoutEnvironmentVariable = "ACTIONS_RUNNER_DAP_CONNECTION_TIMEOUT";
|
private const string _timeoutEnvironmentVariable = "ACTIONS_RUNNER_DAP_CONNECTION_TIMEOUT";
|
||||||
private const string _contentLengthHeader = "Content-Length: ";
|
private const string _contentLengthHeader = "Content-Length: ";
|
||||||
private const int _maxMessageSize = 10 * 1024 * 1024; // 10 MB
|
private const int _maxMessageSize = 10 * 1024 * 1024; // 10 MB
|
||||||
@@ -61,12 +58,6 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
private CancellationTokenRegistration? _cancellationRegistration;
|
private CancellationTokenRegistration? _cancellationRegistration;
|
||||||
private bool _isFirstStep = true;
|
private bool _isFirstStep = true;
|
||||||
|
|
||||||
// Dev Tunnel relay host for remote debugging
|
|
||||||
private TunnelRelayTunnelHost _tunnelRelayHost;
|
|
||||||
|
|
||||||
// When true, skip tunnel relay startup (unit tests only)
|
|
||||||
internal bool SkipTunnelRelay { get; set; }
|
|
||||||
|
|
||||||
// Synchronization for step execution
|
// Synchronization for step execution
|
||||||
private TaskCompletionSource<DapCommand> _commandTcs;
|
private TaskCompletionSource<DapCommand> _commandTcs;
|
||||||
private readonly object _stateLock = new object();
|
private readonly object _stateLock = new object();
|
||||||
@@ -110,18 +101,11 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
Trace.Info("DapDebugger initialized");
|
Trace.Info("DapDebugger initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(IExecutionContext jobContext)
|
public Task StartAsync(IExecutionContext jobContext)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(jobContext, nameof(jobContext));
|
ArgUtil.NotNull(jobContext, nameof(jobContext));
|
||||||
var debuggerConfig = jobContext.Global.Debugger;
|
var port = ResolvePort();
|
||||||
|
|
||||||
if (debuggerConfig == null || !debuggerConfig.HasValidTunnel)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Debugger requires valid tunnel configuration (tunnelId, clusterId, hostToken, port).");
|
|
||||||
}
|
|
||||||
|
|
||||||
var port = debuggerConfig.Tunnel.Port;
|
|
||||||
Trace.Info($"Starting DAP debugger on port {port}");
|
Trace.Info($"Starting DAP debugger on port {port}");
|
||||||
|
|
||||||
_jobContext = jobContext;
|
_jobContext = jobContext;
|
||||||
@@ -131,15 +115,6 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
_listener.Start();
|
_listener.Start();
|
||||||
Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
|
Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
|
||||||
|
|
||||||
// Start Dev Tunnel relay so remote clients reach the local DAP port.
|
|
||||||
// The relay is torn down explicitly in StopAsync (after the DAP session
|
|
||||||
// is closed) so we do NOT pass the job cancellation token here — that
|
|
||||||
// would race with the DAP shutdown and drop the transport mid-protocol.
|
|
||||||
if (!SkipTunnelRelay)
|
|
||||||
{
|
|
||||||
await StartTunnelRelayAsync(debuggerConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
_state = DapSessionState.WaitingForConnection;
|
_state = DapSessionState.WaitingForConnection;
|
||||||
_connectionLoopTask = ConnectionLoopAsync(jobContext.CancellationToken);
|
_connectionLoopTask = ConnectionLoopAsync(jobContext.CancellationToken);
|
||||||
|
|
||||||
@@ -151,36 +126,7 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
});
|
});
|
||||||
|
|
||||||
Trace.Info($"DAP debugger started on port {port}");
|
Trace.Info($"DAP debugger started on port {port}");
|
||||||
}
|
return Task.CompletedTask;
|
||||||
|
|
||||||
private async Task StartTunnelRelayAsync(DebuggerConfig config)
|
|
||||||
{
|
|
||||||
Trace.Info($"Starting Dev Tunnel relay (tunnel={config.Tunnel.TunnelId}, cluster={config.Tunnel.ClusterId})");
|
|
||||||
|
|
||||||
var managementClient = new TunnelManagementClient(
|
|
||||||
new ProductInfoHeaderValue("actions-runner", "1.0"),
|
|
||||||
() => Task.FromResult(
|
|
||||||
(AuthenticationHeaderValue)
|
|
||||||
new AuthenticationHeaderValue("tunnel", config.Tunnel.HostToken)));
|
|
||||||
|
|
||||||
var tunnel = new Tunnel
|
|
||||||
{
|
|
||||||
TunnelId = config.Tunnel.TunnelId,
|
|
||||||
ClusterId = config.Tunnel.ClusterId,
|
|
||||||
AccessTokens = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
[TunnelAccessScopes.Host] = config.Tunnel.HostToken
|
|
||||||
},
|
|
||||||
Ports = new[]
|
|
||||||
{
|
|
||||||
new TunnelPort { PortNumber = (ushort)config.Tunnel.Port }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
_tunnelRelayHost = new TunnelRelayTunnelHost(managementClient, new TraceSource("DevTunnelRelay"));
|
|
||||||
await _tunnelRelayHost.StartAsync(tunnel, CancellationToken.None);
|
|
||||||
|
|
||||||
Trace.Info("Dev Tunnel relay started");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WaitUntilReadyAsync()
|
public async Task WaitUntilReadyAsync()
|
||||||
@@ -253,25 +199,6 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
}
|
}
|
||||||
catch { /* best effort */ }
|
catch { /* best effort */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tear down Dev Tunnel relay
|
|
||||||
if (_tunnelRelayHost != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Trace.Info("Stopping Dev Tunnel relay");
|
|
||||||
await _tunnelRelayHost.DisposeAsync();
|
|
||||||
Trace.Info("Dev Tunnel relay stopped");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Warning($"Error stopping tunnel relay: {ex.Message}");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_tunnelRelayHost = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1345,6 +1272,18 @@ namespace GitHub.Runner.Worker.Dap
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal int ResolvePort()
|
||||||
|
{
|
||||||
|
var portEnv = Environment.GetEnvironmentVariable(_portEnvironmentVariable);
|
||||||
|
if (!string.IsNullOrEmpty(portEnv) && int.TryParse(portEnv, out var customPort) && customPort > 1024 && customPort <= 65535)
|
||||||
|
{
|
||||||
|
Trace.Info($"Using custom DAP port {customPort} from {_portEnvironmentVariable}");
|
||||||
|
return customPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _defaultPort;
|
||||||
|
}
|
||||||
|
|
||||||
internal int ResolveTimeout()
|
internal int ResolveTimeout()
|
||||||
{
|
{
|
||||||
var timeoutEnv = Environment.GetEnvironmentVariable(_timeoutEnvironmentVariable);
|
var timeoutEnv = Environment.GetEnvironmentVariable(_timeoutEnvironmentVariable);
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker.Dap
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Consolidated runtime configuration for the job debugger.
|
|
||||||
/// Populated once from the acquire response and owned by <see cref="GlobalContext"/>.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class DebuggerConfig
|
|
||||||
{
|
|
||||||
public DebuggerConfig(bool enabled, DebuggerTunnelInfo tunnel)
|
|
||||||
{
|
|
||||||
Enabled = enabled;
|
|
||||||
Tunnel = tunnel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Whether the debugger is enabled for this job.</summary>
|
|
||||||
public bool Enabled { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dev Tunnel details for remote debugging.
|
|
||||||
/// Required when <see cref="Enabled"/> is true.
|
|
||||||
/// </summary>
|
|
||||||
public DebuggerTunnelInfo Tunnel { get; }
|
|
||||||
|
|
||||||
/// <summary>Whether the tunnel configuration is complete and valid.</summary>
|
|
||||||
public bool HasValidTunnel => Tunnel != null
|
|
||||||
&& !string.IsNullOrEmpty(Tunnel.TunnelId)
|
|
||||||
&& !string.IsNullOrEmpty(Tunnel.ClusterId)
|
|
||||||
&& !string.IsNullOrEmpty(Tunnel.HostToken)
|
|
||||||
&& Tunnel.Port > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -970,7 +970,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Global.WriteDebug = Global.Variables.Step_Debug ?? false;
|
Global.WriteDebug = Global.Variables.Step_Debug ?? false;
|
||||||
|
|
||||||
// Debugger enabled flag (from acquire response).
|
// Debugger enabled flag (from acquire response).
|
||||||
Global.Debugger = new Dap.DebuggerConfig(message.EnableDebugger, message.DebuggerTunnel);
|
Global.EnableDebugger = message.EnableDebugger;
|
||||||
|
|
||||||
// Hook up JobServerQueueThrottling event, we will log warning on server tarpit.
|
// Hook up JobServerQueueThrottling event, we will log warning on server tarpit.
|
||||||
_jobServerQueue.JobServerQueueThrottling += JobServerQueueThrottling_EventReceived;
|
_jobServerQueue.JobServerQueueThrottling += JobServerQueueThrottling_EventReceived;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using GitHub.Actions.RunService.WebApi;
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using GitHub.Runner.Worker.Dap;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Sdk.RSWebApi.Contracts;
|
using Sdk.RSWebApi.Contracts;
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public StepsContext StepsContext { get; set; }
|
public StepsContext StepsContext { get; set; }
|
||||||
public Variables Variables { get; set; }
|
public Variables Variables { get; set; }
|
||||||
public bool WriteDebug { get; set; }
|
public bool WriteDebug { get; set; }
|
||||||
public DebuggerConfig Debugger { get; set; }
|
public bool EnableDebugger { get; set; }
|
||||||
public string InfrastructureFailureCategory { get; set; }
|
public string InfrastructureFailureCategory { get; set; }
|
||||||
public JObject ContainerHookState { get; set; }
|
public JObject ContainerHookState { get; set; }
|
||||||
public bool HasTemplateEvaluatorMismatch { get; set; }
|
public bool HasTemplateEvaluatorMismatch { get; set; }
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ namespace GitHub.Runner.Worker
|
|||||||
_tempDirectoryManager.InitializeTempDirectory(jobContext);
|
_tempDirectoryManager.InitializeTempDirectory(jobContext);
|
||||||
|
|
||||||
// Setup the debugger
|
// Setup the debugger
|
||||||
if (jobContext.Global.Debugger?.Enabled == true)
|
if (jobContext.Global.EnableDebugger)
|
||||||
{
|
{
|
||||||
Trace.Info("Debugger enabled for this job run");
|
Trace.Info("Debugger enabled for this job run");
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||||
<PackageReference Include="Microsoft.DevTunnels.Connections" Version="1.0.7317" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
String expression,
|
String expression,
|
||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions,
|
IEnumerable<IFunctionInfo> functions)
|
||||||
Boolean allowCaseFunction = true)
|
|
||||||
{
|
{
|
||||||
var context = new ParseContext(expression, trace, namedValues, functions, allowCaseFunction: allowCaseFunction);
|
var context = new ParseContext(expression, trace, namedValues, functions);
|
||||||
context.Trace.Info($"Parsing expression: <{expression}>");
|
context.Trace.Info($"Parsing expression: <{expression}>");
|
||||||
return CreateTree(context);
|
return CreateTree(context);
|
||||||
}
|
}
|
||||||
@@ -416,12 +415,6 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
String name,
|
String name,
|
||||||
out IFunctionInfo functionInfo)
|
out IFunctionInfo functionInfo)
|
||||||
{
|
{
|
||||||
if (String.Equals(name, "case", StringComparison.OrdinalIgnoreCase) && !context.AllowCaseFunction)
|
|
||||||
{
|
|
||||||
functionInfo = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
||||||
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
||||||
}
|
}
|
||||||
@@ -429,7 +422,6 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
private sealed class ParseContext
|
private sealed class ParseContext
|
||||||
{
|
{
|
||||||
public Boolean AllowUnknownKeywords;
|
public Boolean AllowUnknownKeywords;
|
||||||
public Boolean AllowCaseFunction;
|
|
||||||
public readonly String Expression;
|
public readonly String Expression;
|
||||||
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -445,8 +437,7 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions,
|
IEnumerable<IFunctionInfo> functions,
|
||||||
Boolean allowUnknownKeywords = false,
|
Boolean allowUnknownKeywords = false)
|
||||||
Boolean allowCaseFunction = true)
|
|
||||||
{
|
{
|
||||||
Expression = expression ?? String.Empty;
|
Expression = expression ?? String.Empty;
|
||||||
if (Expression.Length > ExpressionConstants.MaxLength)
|
if (Expression.Length > ExpressionConstants.MaxLength)
|
||||||
@@ -467,7 +458,6 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
|
|
||||||
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
||||||
AllowUnknownKeywords = allowUnknownKeywords;
|
AllowUnknownKeywords = allowUnknownKeywords;
|
||||||
AllowCaseFunction = allowCaseFunction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoOperationTraceWriter : ITraceWriter
|
private class NoOperationTraceWriter : ITraceWriter
|
||||||
|
|||||||
@@ -86,12 +86,6 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
|
|
||||||
internal ITraceWriter TraceWriter { get; set; }
|
internal ITraceWriter TraceWriter { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the case expression function is allowed.
|
|
||||||
/// Defaults to true. Set to false to disable the case function.
|
|
||||||
/// </summary>
|
|
||||||
internal Boolean AllowCaseFunction { get; set; } = true;
|
|
||||||
|
|
||||||
private IDictionary<String, Int32> FileIds
|
private IDictionary<String, Int32> FileIds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -94,7 +94,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -123,7 +123,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -152,7 +152,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
|
|||||||
@@ -260,13 +260,6 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DebuggerTunnelInfo DebuggerTunnel
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collection of variables associated with the current context.
|
/// Gets the collection of variables associated with the current context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Dev Tunnel information the runner needs to host the debugger tunnel.
|
|
||||||
/// Matches the run-service <c>DebuggerTunnel</c> contract.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
public sealed class DebuggerTunnelInfo
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string TunnelId { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string ClusterId { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string HostToken { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public int Port { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -681,7 +681,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
var node = default(ExpressionNode);
|
var node = default(ExpressionNode);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
node = expressionParser.CreateTree(condition, null, namedValues, functions, allowCaseFunction: context.AllowCaseFunction) as ExpressionNode;
|
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -17,10 +17,9 @@ namespace GitHub.Actions.Expressions
|
|||||||
String expression,
|
String expression,
|
||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions,
|
IEnumerable<IFunctionInfo> functions)
|
||||||
Boolean allowCaseFunction = true)
|
|
||||||
{
|
{
|
||||||
var context = new ParseContext(expression, trace, namedValues, functions, allowCaseFunction: allowCaseFunction);
|
var context = new ParseContext(expression, trace, namedValues, functions);
|
||||||
context.Trace.Info($"Parsing expression: <{expression}>");
|
context.Trace.Info($"Parsing expression: <{expression}>");
|
||||||
return CreateTree(context);
|
return CreateTree(context);
|
||||||
}
|
}
|
||||||
@@ -416,12 +415,6 @@ namespace GitHub.Actions.Expressions
|
|||||||
String name,
|
String name,
|
||||||
out IFunctionInfo functionInfo)
|
out IFunctionInfo functionInfo)
|
||||||
{
|
{
|
||||||
if (String.Equals(name, "case", StringComparison.OrdinalIgnoreCase) && !context.AllowCaseFunction)
|
|
||||||
{
|
|
||||||
functionInfo = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
||||||
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
||||||
}
|
}
|
||||||
@@ -429,7 +422,6 @@ namespace GitHub.Actions.Expressions
|
|||||||
private sealed class ParseContext
|
private sealed class ParseContext
|
||||||
{
|
{
|
||||||
public Boolean AllowUnknownKeywords;
|
public Boolean AllowUnknownKeywords;
|
||||||
public Boolean AllowCaseFunction;
|
|
||||||
public readonly String Expression;
|
public readonly String Expression;
|
||||||
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -445,8 +437,7 @@ namespace GitHub.Actions.Expressions
|
|||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions,
|
IEnumerable<IFunctionInfo> functions,
|
||||||
Boolean allowUnknownKeywords = false,
|
Boolean allowUnknownKeywords = false)
|
||||||
Boolean allowCaseFunction = true)
|
|
||||||
{
|
{
|
||||||
Expression = expression ?? String.Empty;
|
Expression = expression ?? String.Empty;
|
||||||
if (Expression.Length > ExpressionConstants.MaxLength)
|
if (Expression.Length > ExpressionConstants.MaxLength)
|
||||||
@@ -467,7 +458,6 @@ namespace GitHub.Actions.Expressions
|
|||||||
|
|
||||||
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
||||||
AllowUnknownKeywords = allowUnknownKeywords;
|
AllowUnknownKeywords = allowUnknownKeywords;
|
||||||
AllowCaseFunction = allowCaseFunction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoOperationTraceWriter : ITraceWriter
|
private class NoOperationTraceWriter : ITraceWriter
|
||||||
|
|||||||
@@ -1828,7 +1828,7 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
|||||||
var node = default(ExpressionNode);
|
var node = default(ExpressionNode);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
node = expressionParser.CreateTree(condition, null, namedValues, functions, allowCaseFunction: context.AllowCaseFunction) as ExpressionNode;
|
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -113,12 +113,6 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal Boolean StrictJsonParsing { get; set; }
|
internal Boolean StrictJsonParsing { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the case expression function is allowed.
|
|
||||||
/// Defaults to true. Set to false to disable the case function.
|
|
||||||
/// </summary>
|
|
||||||
internal Boolean AllowCaseFunction { get; set; } = true;
|
|
||||||
|
|
||||||
internal ITraceWriter TraceWriter { get; set; }
|
internal ITraceWriter TraceWriter { get; set; }
|
||||||
|
|
||||||
private IDictionary<String, Int32> FileIds
|
private IDictionary<String, Int32> FileIds
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -55,7 +55,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -93,7 +93,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -123,7 +123,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -153,7 +153,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
using GitHub.DistributedTask.ObjectTemplating;
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
using System;
|
using System;
|
||||||
@@ -9,7 +9,7 @@ namespace GitHub.Runner.Common.Tests.Sdk
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Regression tests for ExpressionParser.CreateTree to verify that
|
/// Regression tests for ExpressionParser.CreateTree to verify that
|
||||||
/// allowCaseFunction does not accidentally set allowUnknownKeywords.
|
/// the case function does not accidentally set allowUnknownKeywords.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ExpressionParserL0
|
public sealed class ExpressionParserL0
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common.Tests.Sdk
|
|||||||
[Trait("Category", "Sdk")]
|
[Trait("Category", "Sdk")]
|
||||||
public void CreateTree_RejectsUnrecognizedNamedValue()
|
public void CreateTree_RejectsUnrecognizedNamedValue()
|
||||||
{
|
{
|
||||||
// Regression: allowCaseFunction was passed positionally into
|
// Regression: the case function parameter was passed positionally into
|
||||||
// the allowUnknownKeywords parameter, causing all named values
|
// the allowUnknownKeywords parameter, causing all named values
|
||||||
// to be silently accepted.
|
// to be silently accepted.
|
||||||
var parser = new ExpressionParser();
|
var parser = new ExpressionParser();
|
||||||
@@ -52,7 +52,7 @@ namespace GitHub.Runner.Common.Tests.Sdk
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Sdk")]
|
[Trait("Category", "Sdk")]
|
||||||
public void CreateTree_CaseFunctionWorks_WhenAllowed()
|
public void CreateTree_CaseFunctionWorks()
|
||||||
{
|
{
|
||||||
var parser = new ExpressionParser();
|
var parser = new ExpressionParser();
|
||||||
var namedValues = new List<INamedValueInfo>
|
var namedValues = new List<INamedValueInfo>
|
||||||
@@ -60,35 +60,17 @@ namespace GitHub.Runner.Common.Tests.Sdk
|
|||||||
new NamedValueInfo<ContextValueNode>("github"),
|
new NamedValueInfo<ContextValueNode>("github"),
|
||||||
};
|
};
|
||||||
|
|
||||||
var node = parser.CreateTree("case(github.event_name, 'push', 'Push Event')", null, namedValues, null, allowCaseFunction: true);
|
var node = parser.CreateTree("case(github.event_name, 'push', 'Push Event')", null, namedValues, null);
|
||||||
|
|
||||||
Assert.NotNull(node);
|
Assert.NotNull(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Sdk")]
|
|
||||||
public void CreateTree_CaseFunctionRejected_WhenDisallowed()
|
|
||||||
{
|
|
||||||
var parser = new ExpressionParser();
|
|
||||||
var namedValues = new List<INamedValueInfo>
|
|
||||||
{
|
|
||||||
new NamedValueInfo<ContextValueNode>("github"),
|
|
||||||
};
|
|
||||||
|
|
||||||
var ex = Assert.Throws<ParseException>(() =>
|
|
||||||
parser.CreateTree("case(github.event_name, 'push', 'Push Event')", null, namedValues, null, allowCaseFunction: false));
|
|
||||||
|
|
||||||
Assert.Contains("Unrecognized function", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Sdk")]
|
[Trait("Category", "Sdk")]
|
||||||
public void CreateTree_CaseFunctionDoesNotAffectUnknownKeywords()
|
public void CreateTree_CaseFunctionDoesNotAffectUnknownKeywords()
|
||||||
{
|
{
|
||||||
// The key regression test: with allowCaseFunction=true (default),
|
// The key regression test: unrecognized named values must still be rejected.
|
||||||
// unrecognized named values must still be rejected.
|
|
||||||
var parser = new ExpressionParser();
|
var parser = new ExpressionParser();
|
||||||
var namedValues = new List<INamedValueInfo>
|
var namedValues = new List<INamedValueInfo>
|
||||||
{
|
{
|
||||||
@@ -96,7 +78,7 @@ namespace GitHub.Runner.Common.Tests.Sdk
|
|||||||
};
|
};
|
||||||
|
|
||||||
var ex = Assert.Throws<ParseException>(() =>
|
var ex = Assert.Throws<ParseException>(() =>
|
||||||
parser.CreateTree("github.ref", null, namedValues, null, allowCaseFunction: true));
|
parser.CreateTree("github.ref", null, namedValues, null));
|
||||||
|
|
||||||
Assert.Contains("Unrecognized named-value", ex.Message);
|
Assert.Contains("Unrecognized named-value", ex.Message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,56 +69,6 @@ public sealed class AgentJobRequestMessageL0
|
|||||||
Assert.False(recoveredMessage.EnableDebugger, "EnableDebugger should be false when JSON contains 'EnableDebugger': false");
|
Assert.False(recoveredMessage.EnableDebugger, "EnableDebugger should be false when JSON contains 'EnableDebugger': false");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void VerifyDebuggerTunnelDeserialization_WithTunnel()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage), new DataContractJsonSerializerSettings
|
|
||||||
{
|
|
||||||
KnownTypes = new[] { typeof(DebuggerTunnelInfo) }
|
|
||||||
});
|
|
||||||
string json = DoubleQuotify(
|
|
||||||
"{'EnableDebugger': true, 'DebuggerTunnel': {'TunnelId': 'tun-123', 'ClusterId': 'use2', 'HostToken': 'tok-abc', 'Port': 4711}}");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
stream.Write(Encoding.UTF8.GetBytes(json));
|
|
||||||
stream.Position = 0;
|
|
||||||
var recoveredMessage = serializer.ReadObject(stream) as AgentJobRequestMessage;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(recoveredMessage);
|
|
||||||
Assert.True(recoveredMessage.EnableDebugger);
|
|
||||||
Assert.NotNull(recoveredMessage.DebuggerTunnel);
|
|
||||||
Assert.Equal("tun-123", recoveredMessage.DebuggerTunnel.TunnelId);
|
|
||||||
Assert.Equal("use2", recoveredMessage.DebuggerTunnel.ClusterId);
|
|
||||||
Assert.Equal("tok-abc", recoveredMessage.DebuggerTunnel.HostToken);
|
|
||||||
Assert.Equal(4711, recoveredMessage.DebuggerTunnel.Port);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void VerifyDebuggerTunnelDeserialization_WithoutTunnel()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage));
|
|
||||||
string json = DoubleQuotify("{'EnableDebugger': true}");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
stream.Write(Encoding.UTF8.GetBytes(json));
|
|
||||||
stream.Position = 0;
|
|
||||||
var recoveredMessage = serializer.ReadObject(stream) as AgentJobRequestMessage;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(recoveredMessage);
|
|
||||||
Assert.True(recoveredMessage.EnableDebugger);
|
|
||||||
Assert.Null(recoveredMessage.DebuggerTunnel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string DoubleQuotify(string text)
|
private static string DoubleQuotify(string text)
|
||||||
{
|
{
|
||||||
return text.Replace('\'', '"');
|
return text.Replace('\'', '"');
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
public void Load_Node24Action()
|
public void Load_Node24Action()
|
||||||
@@ -1006,6 +1006,45 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Evaluate_Default_Input_Case_Function()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
_ec.Object.ExpressionValues["github"] = new LegacyContextData.DictionaryContextData
|
||||||
|
{
|
||||||
|
{ "ref", new LegacyContextData.StringContextData("refs/heads/main") },
|
||||||
|
};
|
||||||
|
_ec.Object.ExpressionValues["strategy"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["matrix"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["steps"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["job"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["runner"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["env"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionFunctions.Add(new LegacyExpressions.FunctionInfo<GitHub.Runner.Worker.Expressions.HashFilesFunction>("hashFiles", 1, 255));
|
||||||
|
|
||||||
|
// Act — evaluate a case() expression as a default input value.
|
||||||
|
// The feature flag is set, so this should succeed.
|
||||||
|
var token = new BasicExpressionToken(null, null, null, "case(true, 'matched', 'default')");
|
||||||
|
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", token);
|
||||||
|
|
||||||
|
// Assert — case() should evaluate successfully
|
||||||
|
Assert.Equal("matched", result);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Teardown()
|
private void Teardown()
|
||||||
{
|
{
|
||||||
_hc?.Dispose();
|
_hc?.Dispose();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
public sealed class DapDebuggerL0
|
public sealed class DapDebuggerL0
|
||||||
{
|
{
|
||||||
|
private const string PortEnvironmentVariable = "ACTIONS_RUNNER_DAP_PORT";
|
||||||
private const string TimeoutEnvironmentVariable = "ACTIONS_RUNNER_DAP_CONNECTION_TIMEOUT";
|
private const string TimeoutEnvironmentVariable = "ACTIONS_RUNNER_DAP_CONNECTION_TIMEOUT";
|
||||||
private DapDebugger _debugger;
|
private DapDebugger _debugger;
|
||||||
|
|
||||||
@@ -24,7 +25,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var hc = new TestHostContext(this, testName);
|
var hc = new TestHostContext(this, testName);
|
||||||
_debugger = new DapDebugger();
|
_debugger = new DapDebugger();
|
||||||
_debugger.Initialize(hc);
|
_debugger.Initialize(hc);
|
||||||
_debugger.SkipTunnelRelay = true;
|
|
||||||
return hc;
|
return hc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,26 +144,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
var jobContext = new Mock<IExecutionContext>();
|
var jobContext = new Mock<IExecutionContext>();
|
||||||
jobContext.Setup(x => x.CancellationToken).Returns(cancellationToken);
|
jobContext.Setup(x => x.CancellationToken).Returns(cancellationToken);
|
||||||
jobContext.Setup(x => x.Global).Returns(new GlobalContext());
|
|
||||||
jobContext
|
|
||||||
.Setup(x => x.GetGitHubContext(It.IsAny<string>()))
|
|
||||||
.Returns((string contextName) => string.Equals(contextName, "job", StringComparison.Ordinal) ? jobName : null);
|
|
||||||
return jobContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Mock<IExecutionContext> CreateJobContextWithTunnel(CancellationToken cancellationToken, int port, string jobName = null)
|
|
||||||
{
|
|
||||||
var tunnel = new GitHub.DistributedTask.Pipelines.DebuggerTunnelInfo
|
|
||||||
{
|
|
||||||
TunnelId = "test-tunnel",
|
|
||||||
ClusterId = "test-cluster",
|
|
||||||
HostToken = "test-token",
|
|
||||||
Port = port
|
|
||||||
};
|
|
||||||
var debuggerConfig = new DebuggerConfig(true, tunnel);
|
|
||||||
var jobContext = new Mock<IExecutionContext>();
|
|
||||||
jobContext.Setup(x => x.CancellationToken).Returns(cancellationToken);
|
|
||||||
jobContext.Setup(x => x.Global).Returns(new GlobalContext { Debugger = debuggerConfig });
|
|
||||||
jobContext
|
jobContext
|
||||||
.Setup(x => x.GetGitHubContext(It.IsAny<string>()))
|
.Setup(x => x.GetGitHubContext(It.IsAny<string>()))
|
||||||
.Returns((string contextName) => string.Equals(contextName, "job", StringComparison.Ordinal) ? jobName : null);
|
.Returns((string contextName) => string.Equals(contextName, "job", StringComparison.Ordinal) ? jobName : null);
|
||||||
@@ -185,36 +165,42 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
public async Task StartAsyncFailsWithoutValidTunnelConfig()
|
public void ResolvePortUsesCustomPortFromEnvironment()
|
||||||
{
|
{
|
||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
WithEnvironmentVariable(PortEnvironmentVariable, "9999", () =>
|
||||||
var jobContext = new Mock<IExecutionContext>();
|
|
||||||
jobContext.Setup(x => x.CancellationToken).Returns(cts.Token);
|
|
||||||
jobContext.Setup(x => x.Global).Returns(new GlobalContext
|
|
||||||
{
|
{
|
||||||
Debugger = new DebuggerConfig(true, null)
|
Assert.Equal(9999, _debugger.ResolvePort());
|
||||||
});
|
});
|
||||||
|
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _debugger.StartAsync(jobContext.Object));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
public async Task StartAsyncUsesPortFromTunnelConfig()
|
public void ResolvePortIgnoresInvalidPortFromEnvironment()
|
||||||
{
|
{
|
||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
WithEnvironmentVariable(PortEnvironmentVariable, "not-a-number", () =>
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
{
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
Assert.Equal(4711, _debugger.ResolvePort());
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
});
|
||||||
using var client = await ConnectClientAsync(port);
|
}
|
||||||
Assert.True(client.Connected);
|
}
|
||||||
await _debugger.StopAsync();
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void ResolvePortIgnoresOutOfRangePortFromEnvironment()
|
||||||
|
{
|
||||||
|
using (CreateTestContext())
|
||||||
|
{
|
||||||
|
WithEnvironmentVariable(PortEnvironmentVariable, "99999", () =>
|
||||||
|
{
|
||||||
|
Assert.Equal(4711, _debugger.ResolvePort());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,12 +254,15 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
{
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
using var client = await ConnectClientAsync(port);
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Assert.True(client.Connected);
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
await _debugger.StopAsync();
|
using var client = await ConnectClientAsync(port);
|
||||||
|
Assert.True(client.Connected);
|
||||||
|
await _debugger.StopAsync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,10 +275,13 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
foreach (var port in new[] { GetFreePort(), GetFreePort() })
|
foreach (var port in new[] { GetFreePort(), GetFreePort() })
|
||||||
{
|
{
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
{
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
await _debugger.StopAsync();
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
await _debugger.StopAsync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,22 +294,25 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
|
|
||||||
var waitTask = _debugger.WaitUntilReadyAsync();
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
await SendRequestAsync(client.GetStream(), new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Command = "configurationDone"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
});
|
|
||||||
|
|
||||||
await waitTask;
|
var waitTask = _debugger.WaitUntilReadyAsync();
|
||||||
Assert.Equal(DapSessionState.Ready, _debugger.State);
|
using var client = await ConnectClientAsync(port);
|
||||||
await _debugger.StopAsync();
|
await SendRequestAsync(client.GetStream(), new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "configurationDone"
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitTask;
|
||||||
|
Assert.Equal(DapSessionState.Ready, _debugger.State);
|
||||||
|
await _debugger.StopAsync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,22 +324,25 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port, "ci-job");
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
var stream = client.GetStream();
|
|
||||||
await SendRequestAsync(client.GetStream(), new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token, "ci-job");
|
||||||
Command = "threads"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
});
|
using var client = await ConnectClientAsync(port);
|
||||||
|
var stream = client.GetStream();
|
||||||
|
await SendRequestAsync(client.GetStream(), new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "threads"
|
||||||
|
});
|
||||||
|
|
||||||
var response = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
var response = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||||
Assert.Contains("\"command\":\"threads\"", response);
|
Assert.Contains("\"command\":\"threads\"", response);
|
||||||
Assert.Contains("\"name\":\"Job: ci-job\"", response);
|
Assert.Contains("\"name\":\"Job: ci-job\"", response);
|
||||||
await _debugger.StopAsync();
|
await _debugger.StopAsync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,27 +354,30 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
|
|
||||||
var waitTask = _debugger.WaitUntilReadyAsync();
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
await SendRequestAsync(client.GetStream(), new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Command = "configurationDone"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
|
||||||
|
var waitTask = _debugger.WaitUntilReadyAsync();
|
||||||
|
using var client = await ConnectClientAsync(port);
|
||||||
|
await SendRequestAsync(client.GetStream(), new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "configurationDone"
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitTask;
|
||||||
|
cts.Cancel();
|
||||||
|
|
||||||
|
// In the real runner, JobRunner always calls OnJobCompletedAsync
|
||||||
|
// from a finally block. The cancellation callback only unblocks
|
||||||
|
// pending waits; OnJobCompletedAsync handles state + cleanup.
|
||||||
|
await _debugger.OnJobCompletedAsync();
|
||||||
|
Assert.Equal(DapSessionState.Terminated, _debugger.State);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitTask;
|
|
||||||
cts.Cancel();
|
|
||||||
|
|
||||||
// In the real runner, JobRunner always calls OnJobCompletedAsync
|
|
||||||
// from a finally block. The cancellation callback only unblocks
|
|
||||||
// pending waits; OnJobCompletedAsync handles state + cleanup.
|
|
||||||
await _debugger.OnJobCompletedAsync();
|
|
||||||
Assert.Equal(DapSessionState.Terminated, _debugger.State);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,22 +400,25 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
|
|
||||||
var waitTask = _debugger.WaitUntilReadyAsync();
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
await SendRequestAsync(client.GetStream(), new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Command = "configurationDone"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
});
|
|
||||||
|
|
||||||
await waitTask;
|
var waitTask = _debugger.WaitUntilReadyAsync();
|
||||||
await _debugger.OnJobCompletedAsync();
|
using var client = await ConnectClientAsync(port);
|
||||||
Assert.Equal(DapSessionState.Terminated, _debugger.State);
|
await SendRequestAsync(client.GetStream(), new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "configurationDone"
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitTask;
|
||||||
|
await _debugger.OnJobCompletedAsync();
|
||||||
|
Assert.Equal(DapSessionState.Terminated, _debugger.State);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,17 +441,20 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
{
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
|
||||||
var waitTask = _debugger.WaitUntilReadyAsync();
|
var waitTask = _debugger.WaitUntilReadyAsync();
|
||||||
await Task.Delay(50);
|
await Task.Delay(50);
|
||||||
cts.Cancel();
|
cts.Cancel();
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => waitTask);
|
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => waitTask);
|
||||||
Assert.IsNotType<TimeoutException>(ex);
|
Assert.IsNotType<TimeoutException>(ex);
|
||||||
await _debugger.StopAsync();
|
await _debugger.StopAsync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,29 +471,32 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.SecretMasker.AddValue("initialized");
|
hc.SecretMasker.AddValue("initialized");
|
||||||
|
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
var stream = client.GetStream();
|
|
||||||
|
|
||||||
await SendRequestAsync(stream, new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Command = "initialize"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
using var client = await ConnectClientAsync(port);
|
||||||
|
var stream = client.GetStream();
|
||||||
|
|
||||||
|
await SendRequestAsync(stream, new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "initialize"
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||||
|
Assert.Contains("\"type\":\"response\"", response);
|
||||||
|
Assert.Contains("\"command\":\"initialize\"", response);
|
||||||
|
Assert.Contains("\"success\":true", response);
|
||||||
|
|
||||||
|
var initializedEvent = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||||
|
Assert.Contains("\"type\":\"event\"", initializedEvent);
|
||||||
|
Assert.Contains("\"event\":\"initialized\"", initializedEvent);
|
||||||
|
|
||||||
|
await _debugger.StopAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
|
||||||
Assert.Contains("\"type\":\"response\"", response);
|
|
||||||
Assert.Contains("\"command\":\"initialize\"", response);
|
|
||||||
Assert.Contains("\"success\":true", response);
|
|
||||||
|
|
||||||
var initializedEvent = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
|
||||||
Assert.Contains("\"type\":\"event\"", initializedEvent);
|
|
||||||
Assert.Contains("\"event\":\"initialized\"", initializedEvent);
|
|
||||||
|
|
||||||
await _debugger.StopAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,38 +508,41 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
|
|
||||||
// Complete handshake so session is ready
|
|
||||||
var waitTask = _debugger.WaitUntilReadyAsync();
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
var stream = client.GetStream();
|
|
||||||
await SendRequestAsync(stream, new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Command = "configurationDone"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
|
||||||
|
// Complete handshake so session is ready
|
||||||
|
var waitTask = _debugger.WaitUntilReadyAsync();
|
||||||
|
using var client = await ConnectClientAsync(port);
|
||||||
|
var stream = client.GetStream();
|
||||||
|
await SendRequestAsync(stream, new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "configurationDone"
|
||||||
|
});
|
||||||
|
await waitTask;
|
||||||
|
|
||||||
|
// Simulate a step starting (which pauses)
|
||||||
|
var step = new Mock<IStep>();
|
||||||
|
step.Setup(s => s.DisplayName).Returns("Test Step");
|
||||||
|
step.Setup(s => s.ExecutionContext).Returns((IExecutionContext)null);
|
||||||
|
var stepTask = _debugger.OnStepStartingAsync(step.Object);
|
||||||
|
|
||||||
|
// Give the step time to pause
|
||||||
|
await Task.Delay(50);
|
||||||
|
|
||||||
|
// Cancel the job — should release the step pause
|
||||||
|
cts.Cancel();
|
||||||
|
await stepTask;
|
||||||
|
|
||||||
|
// In the real runner, OnJobCompletedAsync always follows.
|
||||||
|
await _debugger.OnJobCompletedAsync();
|
||||||
|
Assert.Equal(DapSessionState.Terminated, _debugger.State);
|
||||||
});
|
});
|
||||||
await waitTask;
|
|
||||||
|
|
||||||
// Simulate a step starting (which pauses)
|
|
||||||
var step = new Mock<IStep>();
|
|
||||||
step.Setup(s => s.DisplayName).Returns("Test Step");
|
|
||||||
step.Setup(s => s.ExecutionContext).Returns((IExecutionContext)null);
|
|
||||||
var stepTask = _debugger.OnStepStartingAsync(step.Object);
|
|
||||||
|
|
||||||
// Give the step time to pause
|
|
||||||
await Task.Delay(50);
|
|
||||||
|
|
||||||
// Cancel the job — should release the step pause
|
|
||||||
cts.Cancel();
|
|
||||||
await stepTask;
|
|
||||||
|
|
||||||
// In the real runner, OnJobCompletedAsync always follows.
|
|
||||||
await _debugger.OnJobCompletedAsync();
|
|
||||||
Assert.Equal(DapSessionState.Terminated, _debugger.State);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,10 +558,13 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
// Start then immediate stop (no connection, no WaitUntilReady)
|
// Start then immediate stop (no connection, no WaitUntilReady)
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
{
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
await _debugger.StopAsync();
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
await _debugger.StopAsync();
|
||||||
|
});
|
||||||
|
|
||||||
// StopAsync after already stopped
|
// StopAsync after already stopped
|
||||||
await _debugger.StopAsync();
|
await _debugger.StopAsync();
|
||||||
@@ -563,34 +579,37 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using (CreateTestContext())
|
using (CreateTestContext())
|
||||||
{
|
{
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
await WithEnvironmentVariableAsync(PortEnvironmentVariable, port.ToString(), async () =>
|
||||||
var jobContext = CreateJobContextWithTunnel(cts.Token, port);
|
|
||||||
await _debugger.StartAsync(jobContext.Object);
|
|
||||||
|
|
||||||
var waitTask = _debugger.WaitUntilReadyAsync();
|
|
||||||
using var client = await ConnectClientAsync(port);
|
|
||||||
var stream = client.GetStream();
|
|
||||||
await SendRequestAsync(stream, new Request
|
|
||||||
{
|
{
|
||||||
Seq = 1,
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||||
Type = "request",
|
var jobContext = CreateJobContext(cts.Token);
|
||||||
Command = "configurationDone"
|
await _debugger.StartAsync(jobContext.Object);
|
||||||
|
|
||||||
|
var waitTask = _debugger.WaitUntilReadyAsync();
|
||||||
|
using var client = await ConnectClientAsync(port);
|
||||||
|
var stream = client.GetStream();
|
||||||
|
await SendRequestAsync(stream, new Request
|
||||||
|
{
|
||||||
|
Seq = 1,
|
||||||
|
Type = "request",
|
||||||
|
Command = "configurationDone"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read the configurationDone response
|
||||||
|
await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||||
|
await waitTask;
|
||||||
|
|
||||||
|
// Complete the job — events are sent via OnJobCompletedAsync
|
||||||
|
await _debugger.OnJobCompletedAsync();
|
||||||
|
|
||||||
|
var msg1 = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||||
|
var msg2 = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
// Both events should arrive (order may vary)
|
||||||
|
var combined = msg1 + msg2;
|
||||||
|
Assert.Contains("\"event\":\"terminated\"", combined);
|
||||||
|
Assert.Contains("\"event\":\"exited\"", combined);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Read the configurationDone response
|
|
||||||
await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
|
||||||
await waitTask;
|
|
||||||
|
|
||||||
// Complete the job — events are sent via OnJobCompletedAsync
|
|
||||||
await _debugger.OnJobCompletedAsync();
|
|
||||||
|
|
||||||
var msg1 = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
|
||||||
var msg2 = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5));
|
|
||||||
|
|
||||||
// Both events should arrive (order may vary)
|
|
||||||
var combined = msg1 + msg2;
|
|
||||||
Assert.Contains("\"event\":\"terminated\"", combined);
|
|
||||||
Assert.Contains("\"event\":\"exited\"", combined);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user