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
11 changes: 4 additions & 7 deletions src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -892,15 +892,12 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation

Trace.Info("Initializing Job context");
var jobContext = new JobContext();
if (Global.Variables.GetBoolean(Constants.Runner.Features.AddCheckRunIdToJobContext) ?? false)
ExpressionValues.TryGetValue("job", out var jobDictionary);
if (jobDictionary != null)
{
ExpressionValues.TryGetValue("job", out var jobDictionary);
if (jobDictionary != null)
foreach (var pair in jobDictionary.AssertDictionary("job"))
{
foreach (var pair in jobDictionary.AssertDictionary("job"))
{
jobContext[pair.Key] = pair.Value;
}
jobContext[pair.Key] = pair.Value;
}
}
ExpressionValues["job"] = jobContext;
Expand Down
64 changes: 64 additions & 0 deletions src/Runner.Worker/JobContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,69 @@ public double? CheckRunId
}
}
}

public string WorkflowRef
{
get
{
if (this.TryGetValue("workflow_ref", out var value) && value is StringContextData str)
{
return str.Value;
}
return null;
}
set
{
this["workflow_ref"] = value != null ? new StringContextData(value) : null;
}
}

public string WorkflowSha
{
get
{
if (this.TryGetValue("workflow_sha", out var value) && value is StringContextData str)
{
return str.Value;
}
return null;
}
set
{
this["workflow_sha"] = value != null ? new StringContextData(value) : null;
}
}

public string WorkflowRepository
{
get
{
if (this.TryGetValue("workflow_repository", out var value) && value is StringContextData str)
{
return str.Value;
}
return null;
}
set
{
this["workflow_repository"] = value != null ? new StringContextData(value) : null;
}
}

public string WorkflowFilePath
{
get
{
if (this.TryGetValue("workflow_file_path", out var value) && value is StringContextData str)
{
return str.Value;
}
return null;
}
set
{
this["workflow_file_path"] = value != null ? new StringContextData(value) : null;
}
}
}
}
89 changes: 80 additions & 9 deletions src/Test/L0/Worker/ExecutionContextL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,19 +1203,19 @@ public void InitializeJob_HydratesJobContextWithCheckRunId()
}
}

// TODO: this test can be deleted when `AddCheckRunIdToJobContext` is fully rolled out
// AddCheckRunIdToJobContext is now permanently enabled server-side (hardcoded to "true"
// in acquirejobhandler.go). The runner always copies ContextData["job"] entries, so the
// flag-disabled test is no longer applicable. Replaced with a test that verifies
// check_run_id is always hydrated regardless of the flag value.
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InitializeJob_HydratesJobContextWithCheckRunId_FeatureFlagDisabled()
public void InitializeJob_HydratesJobContextWithCheckRunId_AlwaysCopied()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange: Create a job request message and make sure the feature flag is disabled
var variables = new Dictionary<string, VariableValue>()
{
[Constants.Runner.Features.AddCheckRunIdToJobContext] = new VariableValue("false"),
};
// Arrange: No feature flag set at all
var variables = new Dictionary<string, VariableValue>();
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
var pagingLogger = new Moq.Mock<IPagingLogger>();
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
Expand All @@ -1233,9 +1233,80 @@ public void InitializeJob_HydratesJobContextWithCheckRunId_FeatureFlagDisabled()
// Act
ec.InitializeJob(jobRequest, CancellationToken.None);

// Assert
// Assert: check_run_id is always copied regardless of flag
Assert.NotNull(ec.JobContext);
Assert.Equal(123456, ec.JobContext.CheckRunId);
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InitializeJob_HydratesJobContextWithWorkflowIdentity()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange
var variables = new Dictionary<string, VariableValue>();
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
var pagingLogger = new Moq.Mock<IPagingLogger>();
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
hc.EnqueueInstance(pagingLogger.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);

// Arrange: Server sends all 4 workflow identity fields
var jobContext = new Pipelines.ContextData.DictionaryContextData();
jobContext["workflow_ref"] = new StringContextData("my-org/my-repo/.github/workflows/reusable.yml@refs/heads/main");
jobContext["workflow_sha"] = new StringContextData("abc123def456");
jobContext["workflow_repository"] = new StringContextData("my-org/my-repo");
jobContext["workflow_file_path"] = new StringContextData(".github/workflows/reusable.yml");
jobRequest.ContextData["job"] = jobContext;
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();

// Act
ec.InitializeJob(jobRequest, CancellationToken.None);

// Assert: all properties hydrated from server
Assert.NotNull(ec.JobContext);
Assert.Equal("my-org/my-repo/.github/workflows/reusable.yml@refs/heads/main", ec.JobContext.WorkflowRef);
Assert.Equal("abc123def456", ec.JobContext.WorkflowSha);
Assert.Equal("my-org/my-repo", ec.JobContext.WorkflowRepository);
Assert.Equal(".github/workflows/reusable.yml", ec.JobContext.WorkflowFilePath);
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InitializeJob_WorkflowIdentityNotSet_WhenServerSendsNoData()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange: Server sends no workflow identity in job context
var variables = new Dictionary<string, VariableValue>();
var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
var pagingLogger = new Moq.Mock<IPagingLogger>();
var jobServerQueue = new Moq.Mock<IJobServerQueue>();
hc.EnqueueInstance(pagingLogger.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);

// Arrange: empty job context
jobRequest.ContextData["job"] = new Pipelines.ContextData.DictionaryContextData();
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();

// Act
ec.InitializeJob(jobRequest, CancellationToken.None);

// Assert: no workflow identity
Assert.NotNull(ec.JobContext);
Assert.Null(ec.JobContext.CheckRunId); // with the feature flag disabled we should not have added a CheckRunId to the JobContext
Assert.Null(ec.JobContext.WorkflowRef);
Assert.Null(ec.JobContext.WorkflowSha);
Assert.Null(ec.JobContext.WorkflowRepository);
Assert.Null(ec.JobContext.WorkflowFilePath);
}
}

Expand Down
104 changes: 104 additions & 0 deletions src/Test/L0/Worker/JobContextL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,109 @@ public void CheckRunId_SetNull_RemovesKey()
ctx.CheckRunId = null;
Assert.Null(ctx.CheckRunId);
}

[Fact]
public void WorkflowRef_SetAndGet_WorksCorrectly()
{
var ctx = new JobContext();
ctx.WorkflowRef = "owner/repo/.github/workflows/ci.yml@refs/heads/main";
Assert.Equal("owner/repo/.github/workflows/ci.yml@refs/heads/main", ctx.WorkflowRef);
Assert.True(ctx.TryGetValue("workflow_ref", out var value));
Assert.IsType<StringContextData>(value);
}

[Fact]
public void WorkflowRef_NotSet_ReturnsNull()
{
var ctx = new JobContext();
Assert.Null(ctx.WorkflowRef);
}

[Fact]
public void WorkflowRef_SetNull_ClearsValue()
{
var ctx = new JobContext();
ctx.WorkflowRef = "owner/repo/.github/workflows/ci.yml@refs/heads/main";
ctx.WorkflowRef = null;
Assert.Null(ctx.WorkflowRef);
}

[Fact]
public void WorkflowSha_SetAndGet_WorksCorrectly()
{
var ctx = new JobContext();
ctx.WorkflowSha = "abc123def456";
Assert.Equal("abc123def456", ctx.WorkflowSha);
Assert.True(ctx.TryGetValue("workflow_sha", out var value));
Assert.IsType<StringContextData>(value);
}

[Fact]
public void WorkflowSha_NotSet_ReturnsNull()
{
var ctx = new JobContext();
Assert.Null(ctx.WorkflowSha);
}

[Fact]
public void WorkflowSha_SetNull_ClearsValue()
{
var ctx = new JobContext();
ctx.WorkflowSha = "abc123def456";
ctx.WorkflowSha = null;
Assert.Null(ctx.WorkflowSha);
}

[Fact]
public void WorkflowRepository_SetAndGet_WorksCorrectly()
{
var ctx = new JobContext();
ctx.WorkflowRepository = "owner/repo";
Assert.Equal("owner/repo", ctx.WorkflowRepository);
Assert.True(ctx.TryGetValue("workflow_repository", out var value));
Assert.IsType<StringContextData>(value);
}

[Fact]
public void WorkflowRepository_NotSet_ReturnsNull()
{
var ctx = new JobContext();
Assert.Null(ctx.WorkflowRepository);
}

[Fact]
public void WorkflowRepository_SetNull_ClearsValue()
{
var ctx = new JobContext();
ctx.WorkflowRepository = "owner/repo";
ctx.WorkflowRepository = null;
Assert.Null(ctx.WorkflowRepository);
}

[Fact]
public void WorkflowFilePath_SetAndGet_WorksCorrectly()
{
var ctx = new JobContext();
ctx.WorkflowFilePath = ".github/workflows/ci.yml";
Assert.Equal(".github/workflows/ci.yml", ctx.WorkflowFilePath);
Assert.True(ctx.TryGetValue("workflow_file_path", out var value));
Assert.IsType<StringContextData>(value);
}

[Fact]
public void WorkflowFilePath_NotSet_ReturnsNull()
{
var ctx = new JobContext();
Assert.Null(ctx.WorkflowFilePath);
}

[Fact]
public void WorkflowFilePath_SetNull_ClearsValue()
{
var ctx = new JobContext();
ctx.WorkflowFilePath = ".github/workflows/ci.yml";
ctx.WorkflowFilePath = null;
Assert.Null(ctx.WorkflowFilePath);
}
}
}
Loading