diff --git a/TUnit.Engine/Reporters/Html/GitHubArtifactUploader.cs b/TUnit.Engine/Reporters/Html/GitHubArtifactUploader.cs index 6b60c2e37f..a06af4f6ef 100644 --- a/TUnit.Engine/Reporters/Html/GitHubArtifactUploader.cs +++ b/TUnit.Engine/Reporters/Html/GitHubArtifactUploader.cs @@ -34,12 +34,20 @@ internal static class GitHubArtifactUploader // Step 1: CreateArtifact (deduplicate name on 409 conflict) var createUrl = $"{origin}/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact"; string? signedUploadUrl = null; + string? acceptedArtifactName = null; + var nameWithoutExt = Path.GetFileNameWithoutExtension(fileName); + var ext = Path.GetExtension(fileName); for (var nameAttempt = 0; nameAttempt < 3 && signedUploadUrl is null; nameAttempt++) { - var artifactName = nameAttempt == 0 - ? fileName - : $"{Path.GetFileNameWithoutExtension(fileName)}-{nameAttempt + 1}{Path.GetExtension(fileName)}"; + var artifactName = nameAttempt switch + { + 0 => fileName, + // On first conflict, append the job backend ID to uniquely identify this matrix job + 1 => $"{nameWithoutExt}-{GetShortJobId(workflowJobRunBackendId)}{ext}", + // On further conflicts, add an extra numeric suffix + _ => $"{nameWithoutExt}-{GetShortJobId(workflowJobRunBackendId)}-{nameAttempt}{ext}", + }; var createBody = BuildCreateArtifactJson(workflowRunBackendId, workflowJobRunBackendId, artifactName); @@ -66,6 +74,11 @@ internal static class GitHubArtifactUploader using var doc = JsonDocument.Parse(json); return doc.RootElement.GetProperty("signed_upload_url").GetString(); }, cancellationToken); + + if (signedUploadUrl is not null) + { + acceptedArtifactName = artifactName; + } } if (signedUploadUrl is null) @@ -105,7 +118,7 @@ internal static class GitHubArtifactUploader // Step 3: FinalizeArtifact var finalizeUrl = $"{origin}/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact"; - var finalizeBody = BuildFinalizeArtifactJson(workflowRunBackendId, workflowJobRunBackendId, fileName, fileBytes.Length, sha256Hash); + var finalizeBody = BuildFinalizeArtifactJson(workflowRunBackendId, workflowJobRunBackendId, acceptedArtifactName!, fileBytes.Length, sha256Hash); var artifactId = await RetryAsync(async () => { @@ -127,6 +140,13 @@ internal static class GitHubArtifactUploader return artifactId; } + private static string GetShortJobId(string jobRunBackendId) + { + return jobRunBackendId.Length > 8 + ? jobRunBackendId[..8] + : jobRunBackendId; + } + private static string BuildCreateArtifactJson(string runId, string jobId, string fileName) { using var ms = new MemoryStream();