mirror of
https://github.com/actions/runner.git
synced 2026-07-03 11:06:08 +08:00
Highlight Complete job step in execution view on job-completed pause
When the debugger pauses after job completion, surface the synthetic "Complete job" entry as the active stack frame so clients highlight the cleanup line. Previously the position stayed on the last real step. Threads CompleteJobLine through RenderResult / JobExecutionView and gates HandleStackTrace on a new _jobCompleted flag set when OnJobCompletedAsync enters its inspection pause. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -89,6 +89,11 @@ namespace GitHub.Runner.Worker.Dap
|
||||
private IStep _currentStep;
|
||||
private IExecutionContext _jobContext;
|
||||
|
||||
// Set true once OnJobCompletedAsync begins its inspection pause.
|
||||
// While set, HandleStackTrace surfaces the synthetic "Complete job"
|
||||
// frame so the client highlights the cleanup line.
|
||||
private bool _jobCompleted;
|
||||
|
||||
// Client connection tracking for reconnection support
|
||||
private volatile bool _isClientConnected;
|
||||
|
||||
@@ -244,6 +249,10 @@ namespace GitHub.Runner.Worker.Dap
|
||||
{
|
||||
if (_jobContext != null)
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
_jobCompleted = true;
|
||||
}
|
||||
Trace.Info("Job completed — pausing for inspection");
|
||||
SendStoppedEvent("completed", "Job completed — inspect variables before the session ends.");
|
||||
|
||||
@@ -1380,10 +1389,12 @@ namespace GitHub.Runner.Worker.Dap
|
||||
{
|
||||
IStep currentStep;
|
||||
JobExecutionView view;
|
||||
bool jobCompleted;
|
||||
lock (_stateLock)
|
||||
{
|
||||
currentStep = _currentStep;
|
||||
view = _executionView;
|
||||
jobCompleted = _jobCompleted;
|
||||
}
|
||||
|
||||
var frames = new List<StackFrame>();
|
||||
@@ -1392,9 +1403,23 @@ namespace GitHub.Runner.Worker.Dap
|
||||
{
|
||||
var source = BuildExecutionViewSource(view.JobId);
|
||||
|
||||
// Frame 0: the currently-executing step (only when one is set).
|
||||
if (currentStep != null)
|
||||
if (jobCompleted)
|
||||
{
|
||||
// Surface the synthetic Complete job step so the client
|
||||
// highlights the cleanup line at end-of-job pause.
|
||||
frames.Add(new StackFrame
|
||||
{
|
||||
Id = _currentFrameId,
|
||||
Name = MaskUserVisibleText("Complete job"),
|
||||
Line = view.CompleteJobLine,
|
||||
Column = 1,
|
||||
Source = source,
|
||||
PresentationHint = "normal",
|
||||
});
|
||||
}
|
||||
else if (currentStep != null)
|
||||
{
|
||||
// Frame 0: the currently-executing step (only when one is set).
|
||||
var stepLine = view.TryGetLineForStep(currentStep) ?? 1;
|
||||
frames.Add(new StackFrame
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace GitHub.Runner.Worker.Dap
|
||||
new(StringComparer.Ordinal);
|
||||
private string _yaml;
|
||||
private IReadOnlyList<int> _entryStartLines = Array.Empty<int>();
|
||||
private int _completeJobLine;
|
||||
|
||||
public JobExecutionView(string jobId)
|
||||
{
|
||||
@@ -72,6 +73,21 @@ namespace GitHub.Runner.Worker.Dap
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 1-based line where the synthetic <c>- step: Complete job</c> entry
|
||||
/// appears in <see cref="Yaml"/>. Always non-zero — Cleanup is always emitted.
|
||||
/// </summary>
|
||||
public int CompleteJobLine
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _completeJobLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number of entries (excludes synthetic Setup/Cleanup boundaries).</summary>
|
||||
public int EntryCount
|
||||
{
|
||||
@@ -261,6 +277,7 @@ namespace GitHub.Runner.Worker.Dap
|
||||
var result = JobExecutionViewRenderer.Render(_jobId, _entries.AsReadOnly());
|
||||
_yaml = result.Yaml;
|
||||
_entryStartLines = result.EntryStartLines;
|
||||
_completeJobLine = result.CompleteJobLine;
|
||||
|
||||
_lineByStep.Clear();
|
||||
for (int i = 0; i < _stepIdentities.Count; i++)
|
||||
|
||||
@@ -95,14 +95,21 @@ namespace GitHub.Runner.Worker.Dap
|
||||
/// </summary>
|
||||
internal readonly struct RenderResult
|
||||
{
|
||||
public RenderResult(string yaml, IReadOnlyList<int> entryStartLines)
|
||||
public RenderResult(string yaml, IReadOnlyList<int> entryStartLines, int completeJobLine)
|
||||
{
|
||||
Yaml = yaml;
|
||||
EntryStartLines = entryStartLines;
|
||||
CompleteJobLine = completeJobLine;
|
||||
}
|
||||
|
||||
public string Yaml { get; }
|
||||
public IReadOnlyList<int> EntryStartLines { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 1-based line where the synthetic <c>- step: Complete job</c> entry
|
||||
/// appears in <see cref="Yaml"/>. Always non-zero — Cleanup is always emitted.
|
||||
/// </summary>
|
||||
public int CompleteJobLine { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,9 +167,11 @@ namespace GitHub.Runner.Worker.Dap
|
||||
// cleanup: section — always present, preceded by a blank line.
|
||||
sb.Append('\n');
|
||||
sb.Append("cleanup:\n");
|
||||
newlinesEmitted += 2;
|
||||
int completeJobLine = newlinesEmitted + 1;
|
||||
sb.Append(" - step: Complete job\n");
|
||||
|
||||
return new RenderResult(sb.ToString(), Array.AsReadOnly(startLines));
|
||||
return new RenderResult(sb.ToString(), Array.AsReadOnly(startLines), completeJobLine);
|
||||
}
|
||||
|
||||
private static void EmitPhaseSection(
|
||||
|
||||
@@ -517,6 +517,36 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Contains(" - step: Complete job\n", result.Yaml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Render_ReportsCompleteJobLineMatchingYaml()
|
||||
{
|
||||
// Empty entries — Cleanup still emitted.
|
||||
var emptyResult = JobExecutionViewRenderer.Render("j", new List<JobExecutionViewEntry>());
|
||||
AssertCompleteJobLineMatchesYaml(emptyResult);
|
||||
|
||||
// Non-empty entries across phases.
|
||||
var populatedResult = JobExecutionViewRenderer.Render("build", WorkedExampleEntries());
|
||||
AssertCompleteJobLineMatchesYaml(populatedResult);
|
||||
}
|
||||
|
||||
private static void AssertCompleteJobLineMatchesYaml(RenderResult result)
|
||||
{
|
||||
var lines = result.Yaml.Split('\n');
|
||||
int? actual = null;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (lines[i] == " - step: Complete job")
|
||||
{
|
||||
actual = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(actual.Value, result.CompleteJobLine);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
|
||||
Reference in New Issue
Block a user