diff --git a/.editorconfig b/.editorconfig index e5dd2f2..80be132 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 +charset = utf-8 [*.{yml,yaml}] indent_size = 2 @@ -19,6 +20,9 @@ end_of_line = LF [*.DotSettings] end_of_line = LF +[*.ps1] +charset = utf-8-bom + [*.cs] #### .NET Coding Conventions #### diff --git a/Source/Codecov.Tests/Services/ContiniousIntegrationServers/GitHubActionTests.cs b/Source/Codecov.Tests/Services/ContiniousIntegrationServers/GitHubActionTests.cs index ae38b2c..a82e5c9 100644 --- a/Source/Codecov.Tests/Services/ContiniousIntegrationServers/GitHubActionTests.cs +++ b/Source/Codecov.Tests/Services/ContiniousIntegrationServers/GitHubActionTests.cs @@ -1,4 +1,3 @@ -using System; using Codecov.Services.ContinuousIntegrationServers; using FluentAssertions; using Moq; @@ -14,6 +13,7 @@ public void Branch_Should_Be_Empty_When_Environment_Variable_Does_Not_Exist() // Given var ga = new Mock() { CallBase = true }; ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REF")).Returns(string.Empty); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_HEAD_REF")).Returns(string.Empty); var githubAction = ga.Object; // When @@ -23,12 +23,29 @@ public void Branch_Should_Be_Empty_When_Environment_Variable_Does_Not_Exist() branch.Should().BeEmpty(); } + [Fact] + public void Branch_Should_Be_Set_From_Head_Ref_When_Environment_Variable_Exist() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REF")).Returns("refs/pull/234/merge"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_HEAD_REF")).Returns("develop"); + var githubAction = ga.Object; + + // When + var branch = githubAction.Branch; + + // Then + branch.Should().Be("develop"); + } + [Fact] public void Branch_Should_Be_Set_When_Enviornment_Variable_Exits() { // Given var ga = new Mock() { CallBase = true }; - ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REF")).Returns("ref/heads/develop"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REF")).Returns("refs/heads/develop"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_HEAD_REF")).Returns(string.Empty); var githubAction = ga.Object; // When @@ -38,6 +55,84 @@ public void Branch_Should_Be_Set_When_Enviornment_Variable_Exits() branch.Should().Be("develop"); } + [Fact] + public void Build_Should_Be_Empty_When_Environment_Variable_Does_Not_Exist() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_RUN_ID")).Returns(string.Empty); + var githubAction = ga.Object; + + // When + var build = githubAction.Build; + + // Then + build.Should().BeEmpty(); + } + + [Fact] + public void Build_Should_Be_Set_When_Enviornment_Variable_Exits() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_RUN_ID")).Returns("32402849"); + var githubAction = ga.Object; + + // When + var build = githubAction.Build; + + // Then + build.Should().Be("32402849"); + } + + [Fact] + public void BuildUrl_Should_Be_Empty_When_Build_Is_Empty() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REPOSITORY")).Returns("codecov/codecov-exe"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_RUN_ID")).Returns(string.Empty); + var githubAction = ga.Object; + + // When + var buildUrl = githubAction.BuildUrl; + + // Then + buildUrl.Should().BeEmpty(); + } + + [Fact] + public void BuildUrl_Should_Be_Empty_When_Slug_Is_Empty() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REPOSITORY")).Returns(string.Empty); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_RUN_ID")).Returns("some-id"); + var githubAction = ga.Object; + + // When + var buildUrl = githubAction.BuildUrl; + + // Then + buildUrl.Should().BeEmpty(); + } + + [Fact] + public void BuildUrl_Should_Not_Be_Empty_When_Environment_Variables_Exist() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REPOSITORY")).Returns("codecov/codecov-exe"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_RUN_ID")).Returns("23432"); + var githubAction = ga.Object; + + // When + var buildUrl = githubAction.BuildUrl; + + // Then + buildUrl.Should().Be("https://github.com/codecov/codecov-exe/actions/runs/23432"); + } + [Fact] public void Commit_Should_Be_Empty_String_When_Enviornment_Variable_Does_Not_Exits() { @@ -68,12 +163,29 @@ public void Commit_Should_Be_Set_When_Enviornment_Variable_Exits() commit.Should().Be("123"); } + [Fact] + public void Detecter_Should_Be_False_When_Action_Environment_Variable_Is_Null_Or_Empty() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTION")).Returns(string.Empty); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTIONS")).Returns(string.Empty); + var githubAction = ga.Object; + + // When + var detecter = githubAction.Detecter; + + // Then + detecter.Should().BeFalse(); + } + [Theory, InlineData(null), InlineData(""), InlineData("False"), InlineData("false"), InlineData("foo")] - public void Detecter_Should_Be_False_When_Actions_Environment_Variable_Does_Not_Exist_Or_Is_Not_True(string environmentData) + public void Detecter_Should_Be_False_When_Actions_And_Action_Environment_Variable_Does_Not_Exist_Or_Is_Not_True(string environmentData) { // Given var ga = new Mock() { CallBase = true }; ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTIONS")).Returns(environmentData); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTION")).Returns(string.Empty); var githubAction = ga.Object; // When @@ -83,12 +195,29 @@ public void Detecter_Should_Be_False_When_Actions_Environment_Variable_Does_Not_ detecter.Should().BeFalse(); } + [Fact] + public void Detecter_Should_Be_True_When_Action_Environment_Variable_Exist_And_Is_Not_Empty() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTION")).Returns("my-awesome-github-action"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTIONS")).Returns(string.Empty); + var githubActions = ga.Object; + + // When + var detecter = githubActions.Detecter; + + // Then + detecter.Should().BeTrue(); + } + [Theory, InlineData("True"), InlineData("true")] public void Detecter_Should_Be_True_When_Actions_Environment_Variable_Exist_And_Is_True(string environmentData) { // Given var ga = new Mock() { CallBase = true }; ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTIONS")).Returns(environmentData); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_ACTION")).Returns(string.Empty); var githubAction = ga.Object; // When @@ -98,6 +227,39 @@ public void Detecter_Should_Be_True_When_Actions_Environment_Variable_Exist_And_ detecter.Should().BeTrue(); } + [Fact] + public void PR_Should_Not_Be_Empty_When_Environment_Variables_Exist() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_HEAD_REF")).Returns("patch-2"); + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_REF")).Returns("refs/pull/7/merge"); + var githubAction = ga.Object; + + // When + var pr = githubAction.Pr; + var branch = githubAction.Branch; + + // Then + pr.Should().Be("7"); + branch.Should().Be("patch-2"); + } + + [Fact] + public void PR_Should_Not_be_Set_If_Head_Ref_Is_Empyt() + { + // Given + var ga = new Mock() { CallBase = true }; + ga.Setup(s => s.GetEnvironmentVariable("GITHUB_HEAD_REF")).Returns(string.Empty); + var githubAction = ga.Object; + + // When + var pr = githubAction.Pr; + + // THen + pr.Should().BeEmpty(); + } + [Fact] public void Service_Should_Be_Set_To_GitHubActions() { diff --git a/Source/Codecov.Tool/Codecov.Tool.csproj b/Source/Codecov.Tool/Codecov.Tool.csproj index 28e8b07..5e3ea69 100644 --- a/Source/Codecov.Tool/Codecov.Tool.csproj +++ b/Source/Codecov.Tool/Codecov.Tool.csproj @@ -2,6 +2,7 @@ netcoreapp2.1;netcoreapp3.0 + 8.0 .\Codecov.ruleset Codecov.Tool codecov diff --git a/Source/Codecov/Codecov.csproj b/Source/Codecov/Codecov.csproj index 7a2112c..82f55cd 100644 --- a/Source/Codecov/Codecov.csproj +++ b/Source/Codecov/Codecov.csproj @@ -2,6 +2,7 @@ netcoreapp3.1 win7-x64;win7-x86;linux-x64;osx-x64 + 8.0 Exe diff --git a/Source/Codecov/Coverage/EnviornmentVariables/EnviornmentVariables.cs b/Source/Codecov/Coverage/EnviornmentVariables/EnviornmentVariables.cs index 5fee310..09a3b89 100644 --- a/Source/Codecov/Coverage/EnviornmentVariables/EnviornmentVariables.cs +++ b/Source/Codecov/Coverage/EnviornmentVariables/EnviornmentVariables.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using Codecov.Services.ContinuousIntegrationServers; +using Codecov.Utilities; namespace Codecov.Coverage.EnviornmentVariables { @@ -42,7 +43,7 @@ private IDictionary LoadEnviornmentVariables() var enviornmentVariables = new Dictionary(ContinuousIntegrationServer.UserEnvironmentVariables); const string codecovName = "CODECOV_ENV"; - var codecovValue = Environment.GetEnvironmentVariable(codecovName); + var codecovValue = EnviornmentVariable.GetEnviornmentVariable(codecovName); if (!string.IsNullOrWhiteSpace(codecovValue) && !enviornmentVariables.ContainsKey(codecovName)) { enviornmentVariables[codecovName] = codecovValue; @@ -56,13 +57,13 @@ private IDictionary LoadEnviornmentVariables() continue; } - var value = Environment.GetEnvironmentVariable(enviornmentVariableName); + var value = EnviornmentVariable.GetEnviornmentVariable(enviornmentVariableName); if (string.IsNullOrWhiteSpace(value)) { continue; } - enviornmentVariables[enviornmentVariableName] = Environment.GetEnvironmentVariable(enviornmentVariableName); + enviornmentVariables[enviornmentVariableName] = EnviornmentVariable.GetEnviornmentVariable(enviornmentVariableName); } return enviornmentVariables; diff --git a/Source/Codecov/Factories/ContinuousIntegrationServerFactory.cs b/Source/Codecov/Factories/ContinuousIntegrationServerFactory.cs index 83dcf58..ee39e99 100644 --- a/Source/Codecov/Factories/ContinuousIntegrationServerFactory.cs +++ b/Source/Codecov/Factories/ContinuousIntegrationServerFactory.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Codecov.Services.ContinuousIntegrationServers; namespace Codecov.Factories @@ -7,7 +7,7 @@ internal static class ContinuousIntegrationServerFactory { public static IContinuousIntegrationServer Create() { - var continuousIntegrationServers = new IContinuousIntegrationServer[] { new AppVeyor(), new Travis(), new TeamCity(), new AzurePipelines(), new Jenkins() }; + var continuousIntegrationServers = new IContinuousIntegrationServer[] { new AppVeyor(), new Travis(), new TeamCity(), new AzurePipelines(), new Jenkins(), new GitHubAction() }; var buildServer = continuousIntegrationServers.FirstOrDefault(x => x.Detecter); return buildServer ?? new ContinuousIntegrationServer(); } diff --git a/Source/Codecov/GlobalSuppressions.cs b/Source/Codecov/GlobalSuppressions.cs index d4efd0e..dee7b11 100644 --- a/Source/Codecov/GlobalSuppressions.cs +++ b/Source/Codecov/GlobalSuppressions.cs @@ -1,7 +1,11 @@ -// This file is used by Code Analysis to maintain SuppressMessage attributes that are applied to this -// project. Project-level suppressions either have no target or are given a specific target and +// This file is used by Code Analysis to maintain SuppressMessage attributes that are applied to +// this project. Project-level suppressions either have no target or are given a specific target and // scoped to a namespace, type, member, etc. +using System.Diagnostics.CodeAnalysis; + [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Wrong Usage", "DF0020:Marks undisposed objects assinged to a field, originated in an object creation.", Justification = "It gets disposed when application exits", Scope = "member", Target = "~M:Codecov.Upload.CodecovUploader.#cctor")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Wrong Usage", "DF0021:Marks undisposed objects assinged to a field, originated from method invocation.", Justification = "It gets disposed when application exits", Scope = "member", Target = "~M:Codecov.Logger.Log.Create(System.Boolean,System.Boolean)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Wrong Usage", "DF0022:Marks undisposed objects assinged to a property, originated in an object creation.", Justification = "It gets disposed when parent is disposed", Scope = "member", Target = "~M:Codecov.Upload.CodecovUploader.CreateResponse(System.Net.Http.HttpRequestMessage)~System.Net.Http.HttpResponseMessage")] +[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to catch the general exception for logging purposes.", Scope = "member", Target = "~M:Codecov.Program.Run.Runner(System.Collections.Generic.IEnumerable{System.String})~System.Int32")] +[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "In theory an exception should not happen here, but catch it just in case.", Scope = "member", Target = "~M:Codecov.Program.Program.Main(System.String[])~System.Int32")] diff --git a/Source/Codecov/Services/ContinuousIntegrationServers/Appveyor.cs b/Source/Codecov/Services/ContinuousIntegrationServers/Appveyor.cs index 8b97b50..7b63388 100644 --- a/Source/Codecov/Services/ContinuousIntegrationServers/Appveyor.cs +++ b/Source/Codecov/Services/ContinuousIntegrationServers/Appveyor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; namespace Codecov.Services.ContinuousIntegrationServers @@ -44,6 +44,9 @@ public AppVeyor() public override string Slug => _slug.Value; + private static bool IsNullOrEmpty(params string[] parameters) + => parameters.Any(x => string.IsNullOrEmpty(x)); + private string LoadBuild() { var build = GetEnvironmentVariable("APPVEYOR_JOB_ID"); @@ -81,8 +84,5 @@ private string LoadJob() return job; } - - private static bool IsNullOrEmpty(params string[] parameters) - => parameters.Any(x => string.IsNullOrEmpty(x)); } } diff --git a/Source/Codecov/Services/ContinuousIntegrationServers/ContinuousIntegrationServer.cs b/Source/Codecov/Services/ContinuousIntegrationServers/ContinuousIntegrationServer.cs index 0dbb7da..7195dcb 100644 --- a/Source/Codecov/Services/ContinuousIntegrationServers/ContinuousIntegrationServer.cs +++ b/Source/Codecov/Services/ContinuousIntegrationServers/ContinuousIntegrationServer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -43,6 +43,17 @@ public ContinuousIntegrationServer() public virtual string Tag => string.Empty; + public virtual string GetEnvironmentVariable(string name) + { + if (UserEnvironmentVariables != null && UserEnvironmentVariables.ContainsKey(name)) + { + return UserEnvironmentVariables[name]; + } + + var value = Environment.GetEnvironmentVariable(name); + return string.IsNullOrWhiteSpace(value) ? string.Empty : value; + } + protected bool CheckEnvironmentVariables(params string[] environmentVariables) { var foundVariables = new List(); @@ -63,17 +74,6 @@ protected bool CheckEnvironmentVariables(params string[] environmentVariables) return !string.IsNullOrEmpty(firstValue) && foundVariables.Skip(1).All(v => v.Equals(firstValue, StringComparison.Ordinal)); } - public virtual string GetEnvironmentVariable(string name) - { - if (UserEnvironmentVariables?.ContainsKey(name) == true) - { - return UserEnvironmentVariables[name]; - } - - var value = Environment.GetEnvironmentVariable(name); - return string.IsNullOrWhiteSpace(value) ? string.Empty : value; - } - protected void AddEnviornmentVariable(string name) { if (UserEnvironmentVariables.ContainsKey(name)) @@ -81,7 +81,7 @@ protected void AddEnviornmentVariable(string name) return; } - var value = Environment.GetEnvironmentVariable(name); + var value = GetEnvironmentVariable(name); if (string.IsNullOrWhiteSpace(value)) { return; diff --git a/Source/Codecov/Services/ContinuousIntegrationServers/GitHubAction.cs b/Source/Codecov/Services/ContinuousIntegrationServers/GitHubAction.cs index 7ecd997..cb02d51 100644 --- a/Source/Codecov/Services/ContinuousIntegrationServers/GitHubAction.cs +++ b/Source/Codecov/Services/ContinuousIntegrationServers/GitHubAction.cs @@ -1,37 +1,103 @@ -using System; +using System; namespace Codecov.Services.ContinuousIntegrationServers { internal class GitHubAction : ContinuousIntegrationServer { private readonly Lazy _branch; + private readonly Lazy _build; private readonly Lazy _commit; private readonly Lazy _detecter; + private readonly Lazy _pr; private readonly Lazy _slug; public GitHubAction() { _branch = new Lazy(LoadBranch); + _build = new Lazy(() => GetEnvironmentVariable("GITHUB_RUN_ID")); _commit = new Lazy(() => GetEnvironmentVariable("GITHUB_SHA")); - _detecter = new Lazy(() => CheckEnvironmentVariables("GITHUB_ACTIONS")); + _detecter = new Lazy(() => CheckEnvironmentVariables("GITHUB_ACTIONS") || !string.IsNullOrWhiteSpace(GetEnvironmentVariable("GITHUB_ACTION"))); + _pr = new Lazy(LoadPullRequest); _slug = new Lazy(() => GetEnvironmentVariable("GITHUB_REPOSITORY")); } public override string Branch => _branch.Value; + public override string Build => _build.Value; + + public override string BuildUrl => LoadBuildUrl(); + public override string Commit => _commit.Value; public override bool Detecter => _detecter.Value; + public override string Pr => _pr.Value; + public override string Service => "github-actions"; public override string Slug => _slug.Value; + private static string ExtractSubstring(string text, string prefix, string postfix) + { + if (string.IsNullOrWhiteSpace(text)) + { + return string.Empty; + } + + var textLength = text.Length; + + var startIndex = string.IsNullOrEmpty(prefix) ? 0 : text.IndexOf(prefix); + var endIndex = string.IsNullOrEmpty(postfix) ? textLength : text.IndexOf(postfix); + if (startIndex == -1) + { + startIndex = 0; + } + else if (!string.IsNullOrEmpty(prefix)) + { + startIndex += prefix.Length; + } + + if (endIndex == -1) + { + endIndex = textLength; + } + + return text.Substring(startIndex, endIndex - startIndex); + } + private string LoadBranch() { + var headRef = GetEnvironmentVariable("GITHUB_HEAD_REF"); + if (!string.IsNullOrWhiteSpace(headRef)) + { + return headRef; + } + var branch = GetEnvironmentVariable("GITHUB_REF"); - return string.IsNullOrWhiteSpace(branch) ? string.Empty : branch.StartsWith("ref/heads/") ? branch.Substring(10) : branch; + return ExtractSubstring(branch, "refs/heads/", null); + } + + private string LoadPullRequest() + { + var headRef = GetEnvironmentVariable("GITHUB_HEAD_REF"); + if (string.IsNullOrEmpty(headRef)) + { + return string.Empty; + } + + var branchRef = GetEnvironmentVariable("GITHUB_REF"); + + return string.IsNullOrEmpty(branchRef) + ? string.Empty + : ExtractSubstring(branchRef, "refs/pull/", "/merge"); + } + + private string LoadBuildUrl() + { + return (string.IsNullOrWhiteSpace(Slug) || string.IsNullOrWhiteSpace(Build)) + ? string.Empty + : $"https://github.com/{Slug}/actions/runs/{Build}"; } } } diff --git a/Source/Codecov/Url/Host.cs b/Source/Codecov/Url/Host.cs index fd48c25..33136ea 100644 --- a/Source/Codecov/Url/Host.cs +++ b/Source/Codecov/Url/Host.cs @@ -1,4 +1,4 @@ -using System; +using System; using Codecov.Services.ContinuousIntegrationServers; namespace Codecov.Url diff --git a/Source/Codecov/Url/Query.cs b/Source/Codecov/Url/Query.cs index ad3eda4..8f8c2fe 100644 --- a/Source/Codecov/Url/Query.cs +++ b/Source/Codecov/Url/Query.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; diff --git a/Source/Codecov/Utilities/EnviornmentVariable.cs b/Source/Codecov/Utilities/EnviornmentVariable.cs index db67e39..92161b9 100644 --- a/Source/Codecov/Utilities/EnviornmentVariable.cs +++ b/Source/Codecov/Utilities/EnviornmentVariable.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System; namespace Codecov.Utilities { @@ -15,7 +14,7 @@ internal static string GetFirstExistingEnvironmentVariable(params string[] names { foreach (var name in names) { - var env = Environment.GetEnvironmentVariable(name); + var env = GetEnviornmentVariable(name); if (!string.IsNullOrWhiteSpace(env)) {