Skip to content

Commit a966c0a

Browse files
feat: outputting in GitHub Actions error format (#446)
1 parent 932945f commit a966c0a

File tree

8 files changed

+94
-4
lines changed

8 files changed

+94
-4
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Feature: GitHub Actions output format emits workflow commands for linting errors.
2+
3+
4+
Scenario Outline:
5+
Given the repository "<repository>" is cloned and checked out at the commit "<checkout_commit>".
6+
When linting from the "<commit_hash>".
7+
And the argument --output is provided as "github".
8+
Then the GitHub Actions output contains a merge commit error.
9+
10+
11+
Examples:
12+
| repository | checkout_commit | commit_hash |
13+
| https://github.com/asomers/mockall.git | 231bd5ff58ed4f9e99bba74f0239995942f8d29d | e02d8f08f8ab114c79a0e8cf5bd6de860f0f7c2e |

end-to-end-tests/features/steps/assertions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ def assert_error_is_one_of(result, errors):
4040
assert result.stderr in errors, "Expected standard error to equal one of these errors.\n" + \
4141
f"Standard error = {result.stderr.encode()}.\n" + \
4242
f"Errors = {errors}.\n"
43+
44+
45+
def assert_output_contains(result, output):
46+
assert output in result.stdout, "Expected standard output to contain the output.\n" + \
47+
f"Standard output = {result.stdout.encode()}.\n" + \
48+
f"Output = {output.encode()}.\n"

end-to-end-tests/features/steps/then.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
assert_error_matches_regex,
1010
assert_no_errors,
1111
assert_no_output,
12+
assert_output_contains,
1213
)
1314

1415

@@ -79,3 +80,12 @@ def assert_ambiguous_shortened_commit_hash_error(context, shortened_commit_hash)
7980

8081
# Then
8182
assert_error_matches_regex(result, ambiguous_shortened_commit_hash_error)
83+
84+
85+
@then('the GitHub Actions output contains a merge commit error.')
86+
def assert_github_output_contains_merge_commit_error(context):
87+
result = execute_clean_git_history(context)
88+
assert_command_unsuccessful(result)
89+
assert_output_contains(result, "::error title=Merge Commit::")
90+
assert_output_contains(result, "::group::")
91+
assert_output_contains(result, "::endgroup::")

end-to-end-tests/features/steps/when.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ def set_linting_from_the(context, git):
99
@when('the argument --max-commits is provided as "{max_commits}".')
1010
def set_max_commits(context, max_commits):
1111
context.arguments += f" --max-commits {max_commits} "
12+
13+
14+
@when('the argument --output is provided as "{output}".')
15+
def set_output(context, output):
16+
context.arguments += f" --output {output} "
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use std::fmt::Write;
2+
3+
use super::{CommitError, CommitsError, LintingResults};
4+
5+
pub(crate) fn print_all(results: &LintingResults) -> String {
6+
let mut output = String::new();
7+
8+
if let Some(commit_errors) = &results.commit_errors {
9+
for commit in &commit_errors.order {
10+
if let Some(errors) = commit_errors.errors.get(commit) {
11+
let short_hash = &commit.hash[..7.min(commit.hash.len())];
12+
let message = commit.message.lines().next().unwrap_or_default();
13+
14+
let _ = writeln!(output, "::group::{short_hash} - {message}");
15+
16+
for error in errors {
17+
match error {
18+
CommitError::MergeCommit => {
19+
let _ = writeln!(
20+
output,
21+
"::error title=Merge Commit::Commit {short_hash} is a merge commit."
22+
);
23+
}
24+
}
25+
}
26+
27+
let _ = writeln!(output, "::endgroup::");
28+
}
29+
}
30+
}
31+
32+
if let Some(commits_errors) = &results.commits_errors {
33+
for commits_error in &commits_errors.errors {
34+
match commits_error {
35+
CommitsError::MaxCommitsExceeded {
36+
max_commits,
37+
actual_commits,
38+
} => {
39+
let _ = writeln!(
40+
output,
41+
"::error title=Max Commits Exceeded::Maximum commits exceeded: found {actual_commits} commits, but maximum allowed is {max_commits}."
42+
);
43+
}
44+
}
45+
}
46+
}
47+
48+
output
49+
}

src/linting_results/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@ use std::collections::{HashMap, VecDeque};
22

33
use crate::commits::commit::Commit;
44

5+
mod github_actions;
56
mod pretty;
67

78
/// The representation of an error that an individual commit can have.
89
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
910
pub enum CommitError {
10-
/// Commit is a merge commit (has multiple parents).
1111
MergeCommit,
1212
}
1313

1414
/// The representation of an error for the collection of commits as a whole.
1515
#[derive(Debug, Clone, PartialEq, Eq)]
1616
pub enum CommitsError {
17-
/// The number of commits exceeds the maximum allowed.
1817
MaxCommitsExceeded {
1918
max_commits: usize,
2019
actual_commits: usize,
@@ -51,8 +50,11 @@ pub struct LintingResults {
5150
}
5251

5352
impl LintingResults {
54-
/// Get a pretty representation of the linting results as a string, suitable as output for a user.
5553
pub fn pretty(&self) -> String {
5654
pretty::print_all(self)
5755
}
56+
57+
pub fn github_actions(&self) -> String {
58+
github_actions::print_all(self)
59+
}
5860
}

src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub(crate) struct Arguments {
3333
#[arg(
3434
long,
3535
default_value = "default",
36-
help = "Specifies the format for outputting results, acceptable values are quiet, default, and pretty."
36+
help = "Specifies the format for outputting results, acceptable values are quiet, default, pretty, and github."
3737
)]
3838
pub(crate) output: Output,
3939

@@ -79,6 +79,9 @@ fn run(arguments: Arguments) -> Result<i32> {
7979
Output::Pretty => {
8080
println!("{}", linting_results.pretty());
8181
}
82+
Output::GitHub => {
83+
println!("{}", linting_results.github_actions());
84+
}
8285
}
8386

8487
// As we don't want an error printed but linting failed so want to exit unsuccessfully.

src/output.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ pub enum Output {
55
Quiet,
66
Default,
77
Pretty,
8+
#[value(name = "github")]
9+
GitHub,
810
}

0 commit comments

Comments
 (0)