Skip to content
Draft
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
16 changes: 15 additions & 1 deletion src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ public GitHubActionsAttribute(
/// </summary>
public string[] RunsOnLabels { get; set; } = new string[0];

/// <summary>
/// Overrides the generated job's id and <c>name:</c>, which otherwise default to the runner image
/// (e.g. <c>windows-latest</c>). Useful with <see cref="RunsOnLabels"/>, where the image is only a
/// placeholder, since the job name is what shows in the PR checks list and what branch protection matches.
/// <para/>
/// Must be a valid job id (letters, digits, <c>-</c> and <c>_</c>, starting with a letter or <c>_</c>)
/// and requires exactly one image, as a matrix would produce duplicate ids. Unset keeps the default.
/// </summary>
public string JobName { get; set; }

public GitHubActionsSubmodules Submodules
{
set => submodules = value;
Expand Down Expand Up @@ -307,6 +317,10 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection<Executa

Assert.True(RunsOnLabels.All(x => !x.IsNullOrWhiteSpace()),
$"'{nameof(RunsOnLabels)}' entries must not be null, empty, or whitespace");
Assert.True(JobName == null || images.Length == 1,
$"Cannot use '{nameof(JobName)}' with multiple images; each job in the matrix would get the same id");
Assert.True(JobName == null || System.Text.RegularExpressions.Regex.IsMatch(JobName, "^[A-Za-z_][A-Za-z0-9_-]*$"),
$"'{nameof(JobName)}' must be a valid job id: letters, digits, '-' and '_', starting with a letter or '_'");

return configuration;
}
Expand All @@ -315,7 +329,7 @@ protected virtual GitHubActionsJob GetJobs(GitHubActionsImage image, IReadOnlyCo
{
return new GitHubActionsJob
{
Name = image.GetValue().Replace(".", "_"),
Name = JobName ?? image.GetValue().Replace(".", "_"),
RunsOnLabels = RunsOnLabels,
EnvironmentName = EnvironmentName,
EnvironmentUrl = EnvironmentUrl,
Expand Down
93 changes: 93 additions & 0 deletions tests/Fallout.Common.Specs/CI/GitHubActionsJobNameSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Linq;
using Fallout.Common.CI;
using Fallout.Common.CI.GitHubActions;
using Fallout.Common.CI.GitHubActions.Configuration;
using Fallout.Common.Execution;
using Fallout.Common.Utilities;
using FluentAssertions;
using Xunit;

namespace Fallout.Common.Specs.CI;

public class GitHubActionsJobNameSpecs
{
[Fact]
public void Unset_job_name_uses_image_value()
{
var job = GetJobs(new[] { GitHubActionsImage.WindowsLatest }, jobName: null).Single();

job.Name.Should().Be("windows-latest");
}

[Fact]
public void Unset_job_name_keeps_dots_replaced_in_matrix()
{
var jobs = GetJobs(new[] { GitHubActionsImage.UbuntuLatest, GitHubActionsImage.Ubuntu2204 }, jobName: null);

jobs.Select(x => x.Name).Should().Equal("ubuntu-latest", "ubuntu-22_04");
}

[Fact]
public void Job_name_is_used_as_job_id_and_name()
{
var job = GetJobs(new[] { GitHubActionsImage.WindowsLatest }, "build-windows").Single();

job.Name.Should().Be("build-windows");
using var stream = new System.IO.MemoryStream();
using (var streamWriter = new System.IO.StreamWriter(stream, leaveOpen: true))
{
job.Write(new CustomFileWriter(streamWriter, 2, "#"));
}

var output = System.Text.Encoding.UTF8.GetString(stream.ToArray());
output.Should().StartWith("build-windows:").And.Contain("name: build-windows");
}

[Fact]
public void Job_name_with_multiple_images_throws()
{
var act = () => GetJobs(new[] { GitHubActionsImage.UbuntuLatest, GitHubActionsImage.WindowsLatest }, "build");

act.Should().Throw<Exception>().WithMessage("*JobName*");
}

[Theory]
[InlineData("build")]
[InlineData("_build")]
[InlineData("Build-1_x")]
public void Valid_job_name_does_not_throw(string jobName)
{
var act = () => GetJobs(new[] { GitHubActionsImage.UbuntuLatest }, jobName);

act.Should().NotThrow();
}

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("1build")]
[InlineData("-build")]
[InlineData("build job")]
[InlineData("build.job")]
public void Invalid_job_name_throws(string jobName)
{
var act = () => GetJobs(new[] { GitHubActionsImage.UbuntuLatest }, jobName);

act.Should().Throw<Exception>().WithMessage("*JobName*");
}

private static GitHubActionsJob[] GetJobs(GitHubActionsImage[] images, string jobName)
{
var build = new ConfigurationGenerationSpecs.TestBuild();
var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile);
var attribute = new TestGitHubActionsAttribute(images[0], images[1..])
{
On = new[] { GitHubActionsTrigger.Push },
InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) },
JobName = jobName
};
((ConfigurationAttributeBase)attribute).Build = build;
return ((GitHubActionsConfiguration)attribute.GetConfiguration(relevantTargets)).Jobs;
}
}
Loading