git_checks_core/
run.rs

1// Copyright Kitware, Inc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::fmt::{self, Debug};
10use std::iter;
11use std::slice;
12
13use git_workarea::{CommitId, GitContext, GitError, Identity, WorkAreaError};
14use log::{debug, error};
15use rayon::prelude::*;
16use thiserror::Error;
17
18use crate::check::{BranchCheck, Check, CheckResult, TopicCheck};
19use crate::commit::{Commit, CommitError, Topic};
20use crate::context::CheckGitContext;
21
22/// Errors which can occur when running checks.
23#[derive(Debug, Error)]
24#[non_exhaustive]
25pub enum RunError {
26    /// Command preparation failure.
27    #[error("git error: {}", source)]
28    Git {
29        /// The cause of the error.
30        #[from]
31        source: GitError,
32    },
33    /// An error occurred when working with the workarea.
34    #[error("git workarea error: {}", source)]
35    WorkArea {
36        /// The cause of the error.
37        #[from]
38        source: WorkAreaError,
39    },
40    /// An error occurred when working with a commit.
41    #[error("commit error: {}", source)]
42    Commit {
43        /// The cause of the error.
44        #[from]
45        source: CommitError,
46    },
47    /// Failure to create a ref to refer to the checked commit.
48    #[error("run check error: failed to update the {} ref: {}", base_ref, output)]
49    UpdateRef {
50        /// The base name of the ref.
51        base_ref: CommitId,
52        /// Git's output for the error.
53        output: String,
54    },
55    /// Failure to list revisions to check.
56    #[error(
57        "run check error: failed to list refs from {} to {}",
58        base_ref,
59        new_ref
60    )]
61    RevList {
62        /// The base ref for the topic.
63        base_ref: CommitId,
64        /// The head of the topic.
65        new_ref: CommitId,
66        /// Git's output for the error.
67        output: String,
68    },
69}
70
71impl RunError {
72    fn update_ref(base_ref: CommitId, output: &[u8]) -> Self {
73        RunError::UpdateRef {
74            base_ref,
75            output: String::from_utf8_lossy(output).into(),
76        }
77    }
78
79    fn rev_list(base_ref: CommitId, new_ref: CommitId, output: &[u8]) -> Self {
80        RunError::RevList {
81            base_ref,
82            new_ref,
83            output: String::from_utf8_lossy(output).into(),
84        }
85    }
86}
87
88/// Configuration for checks to run against a repository.
89#[derive(Default, Clone)]
90pub struct GitCheckConfiguration<'a> {
91    /// Checks to run on each commit.
92    checks: Vec<&'a dyn Check>,
93    /// Checks to run on the branch as a whole.
94    checks_branch: Vec<&'a dyn BranchCheck>,
95    /// Checks to run on the topic.
96    checks_topic: Vec<&'a dyn TopicCheck>,
97}
98
99/// Results from checking a topic.
100#[derive(Debug)]
101pub struct TopicCheckResult {
102    /// The results for each commit in the topic.
103    commit_results: Vec<(CommitId, CheckResult)>,
104    /// The results for the branch as a whole.
105    topic_result: CheckResult,
106}
107
108impl TopicCheckResult {
109    /// The results for each commit checked.
110    pub fn commit_results(&self) -> slice::Iter<(CommitId, CheckResult)> {
111        self.commit_results.iter()
112    }
113
114    /// The results for the topic as a whole.
115    pub fn topic_result(&self) -> &CheckResult {
116        &self.topic_result
117    }
118}
119
120impl From<TopicCheckResult> for CheckResult {
121    fn from(res: TopicCheckResult) -> Self {
122        res.commit_results
123            .into_iter()
124            .map(|(_, result)| result)
125            .chain(iter::once(res.topic_result))
126            .fold(Self::new(), Self::combine)
127    }
128}
129
130impl<'a> GitCheckConfiguration<'a> {
131    /// Create a new check configuration.
132    pub fn new() -> Self {
133        GitCheckConfiguration {
134            checks: Vec::new(),
135            checks_branch: Vec::new(),
136            checks_topic: Vec::new(),
137        }
138    }
139
140    /// Add a check to be run on every commit.
141    pub fn add_check(&mut self, check: &'a dyn Check) -> &mut Self {
142        self.checks.push(check);
143
144        self
145    }
146
147    /// Add a check to be once for the entire branch.
148    pub fn add_branch_check(&mut self, check: &'a dyn BranchCheck) -> &mut Self {
149        self.checks_branch.push(check);
150
151        self
152    }
153
154    /// Add a check to be once for the entire topic.
155    pub fn add_topic_check(&mut self, check: &'a dyn TopicCheck) -> &mut Self {
156        self.checks_topic.push(check);
157
158        self
159    }
160
161    /// Find refs that should be checked given a target branch and the topic names.
162    fn list<'b, B>(
163        &self,
164        ctx: &GitContext,
165        reason: &str,
166        target_branch: &CommitId,
167        bases: B,
168        topic: &CommitId,
169    ) -> Result<Vec<CommitId>, RunError>
170    where
171        B: IntoIterator<Item = &'b CommitId>,
172    {
173        let (new_ref, base_ref) = ctx.reserve_refs(format!("check/{}", reason), topic)?;
174        let update_ref = ctx
175            .git()
176            .arg("update-ref")
177            .args(["-m", reason])
178            .arg(&base_ref)
179            .arg(target_branch.as_str())
180            .output()
181            .map_err(|err| GitError::subcommand("update-ref", err))?;
182        if !update_ref.status.success() {
183            return Err(RunError::update_ref(
184                CommitId::new(base_ref),
185                &update_ref.stderr,
186            ));
187        }
188
189        let rev_list = ctx
190            .git()
191            .arg("rev-list")
192            .arg("--reverse")
193            .arg("--topo-order")
194            .arg(&new_ref)
195            .arg(format!("^{}", base_ref))
196            .args(bases.into_iter().map(|base| format!("^{}", base)))
197            .output()
198            .map_err(|err| GitError::subcommand("rev-list", err))?;
199        if !rev_list.status.success() {
200            return Err(RunError::rev_list(
201                CommitId::new(base_ref),
202                CommitId::new(new_ref),
203                &rev_list.stderr,
204            ));
205        }
206        let refs = String::from_utf8_lossy(&rev_list.stdout);
207
208        Ok(refs.lines().map(CommitId::new).collect())
209    }
210
211    /// Run a commit check over a commit.
212    fn run_check(ctx: &CheckGitContext, check: &dyn Check, commit: &Commit) -> CheckResult {
213        debug!(
214            target: "git-checks",
215            "running check {} on commit {}",
216            check.name(),
217            commit.sha1,
218        );
219
220        check.check(ctx, commit).unwrap_or_else(|err| {
221            error!(
222                target: "git-checks",
223                "check {} failed on commit {}: {:?}",
224                check.name(),
225                commit.sha1,
226                err,
227            );
228
229            let mut res = CheckResult::new();
230            res.add_alert(
231                format!(
232                    "failed to run the {} check on commit {}",
233                    check.name(),
234                    commit.sha1,
235                ),
236                true,
237            );
238            res
239        })
240    }
241
242    /// Run a branch check over a commit.
243    fn run_branch_check(
244        ctx: &CheckGitContext,
245        check: &dyn BranchCheck,
246        commit: &CommitId,
247    ) -> CheckResult {
248        debug!(target: "git-checks", "running check {}", check.name());
249
250        check.check(ctx, commit).unwrap_or_else(|err| {
251            error!(
252                target: "git-checks",
253                "branch check {}: {:?}",
254                check.name(),
255                err,
256            );
257
258            let mut res = CheckResult::new();
259            res.add_alert(
260                format!("failed to run the {} branch check", check.name()),
261                true,
262            );
263            res
264        })
265    }
266
267    /// Run a topic check over a topic.
268    fn run_topic_check(
269        ctx: &CheckGitContext,
270        check: &dyn TopicCheck,
271        topic: &Topic,
272    ) -> CheckResult {
273        debug!(target: "git-checks", "running check {}", check.name());
274
275        check.check(ctx, topic).unwrap_or_else(|err| {
276            error!(
277                target: "git-checks",
278                "topic check {}: {:?}",
279                check.name(),
280                err,
281            );
282
283            let mut res = CheckResult::new();
284            res.add_alert(
285                format!("failed to run the {} topic check", check.name()),
286                true,
287            );
288            res
289        })
290    }
291
292    /// Run checks over a given topic and collect results from the checks.
293    fn run_topic_impl(
294        &self,
295        ctx: &GitContext,
296        base: &CommitId,
297        refs: Vec<CommitId>,
298        owner: &Identity,
299    ) -> Result<TopicCheckResult, RunError> {
300        let topic_result = refs.last().map_or_else(
301            || Ok(CheckResult::new()) as Result<_, RunError>,
302            |head_commit| {
303                // Avoid setting up the workarea if there aren't any branch or topic checks.
304                if self.checks_branch.is_empty() && self.checks_topic.is_empty() {
305                    return Ok(CheckResult::new());
306                }
307
308                let workarea = ctx.prepare(head_commit)?;
309                let check_ctx = CheckGitContext::new(workarea, owner.clone());
310                let topic = Topic::new(ctx, base, head_commit)?;
311
312                Ok(self
313                    .checks_branch
314                    .par_iter()
315                    .map(|&check| Self::run_branch_check(&check_ctx, check, head_commit))
316                    .chain(
317                        self.checks_topic
318                            .par_iter()
319                            .map(|&check| Self::run_topic_check(&check_ctx, check, &topic)),
320                    )
321                    .reduce(CheckResult::new, CheckResult::combine))
322            },
323        )?;
324        let commit_results = refs
325            .into_par_iter()
326            .map(|sha1| {
327                self.run_commit(ctx, &sha1, owner)
328                    .map(|result| (sha1, result))
329            })
330            .collect::<Vec<Result<_, RunError>>>()
331            .into_iter()
332            .collect::<Result<Vec<_>, RunError>>()?;
333
334        Ok(TopicCheckResult {
335            commit_results,
336            topic_result,
337        })
338    }
339
340    /// Run checks over a given commit.
341    pub fn run_commit(
342        &self,
343        ctx: &GitContext,
344        commit: &CommitId,
345        owner: &Identity,
346    ) -> Result<CheckResult, RunError> {
347        // Avoid setting up the workarea if there aren't any per-commit checks.
348        if self.checks.is_empty() {
349            return Ok(CheckResult::new());
350        }
351
352        let workarea = ctx.prepare(commit)?;
353        let check_ctx = CheckGitContext::new(workarea, owner.clone());
354
355        let commit = Commit::new(ctx, commit)?;
356
357        Ok(self
358            .checks
359            .par_iter()
360            .map(|&check| Self::run_check(&check_ctx, check, &commit))
361            .reduce(CheckResult::new, CheckResult::combine))
362    }
363
364    /// Run checks over a given topic and collect results from the checks.
365    pub fn run_topic<R>(
366        &self,
367        ctx: &GitContext,
368        reason: R,
369        target_branch: &CommitId,
370        topic: &CommitId,
371        owner: &Identity,
372    ) -> Result<TopicCheckResult, RunError>
373    where
374        R: AsRef<str>,
375    {
376        let refs = self.list(ctx, reason.as_ref(), target_branch, &[], topic)?;
377        self.run_topic_impl(ctx, target_branch, refs, owner)
378    }
379
380    /// Run checks over a given topic involving multiple base commits.
381    pub fn run_topic_multi_base<'b, R, B>(
382        &self,
383        ctx: &GitContext,
384        reason: R,
385        target_branch: &CommitId,
386        bases: B,
387        topic: &CommitId,
388        owner: &Identity,
389    ) -> Result<TopicCheckResult, RunError>
390    where
391        R: AsRef<str>,
392        B: IntoIterator<Item = &'b CommitId>,
393    {
394        let refs = self.list(ctx, reason.as_ref(), target_branch, bases, topic)?;
395        self.run_topic_impl(ctx, target_branch, refs, owner)
396    }
397}
398
399impl Debug for GitCheckConfiguration<'_> {
400    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
401        write!(
402            f,
403            "GitCheckConfiguration {{ {} commit checks, {} branch checks, {} topic checks }}",
404            self.checks.len(),
405            self.checks_branch.len(),
406            self.checks_topic.len(),
407        )
408    }
409}
410
411#[cfg(test)]
412mod test {
413    use std::iter;
414    use std::path::Path;
415
416    use git_workarea::{CommitId, GitContext, Identity};
417
418    use crate::run::{CheckResult, GitCheckConfiguration, TopicCheckResult};
419
420    mod checks {
421        use thiserror::Error;
422
423        use crate::impl_prelude::*;
424
425        #[derive(Debug)]
426        pub struct FailingCheck {}
427
428        #[derive(Debug, Error)]
429        #[error("the failing check did its thing")]
430        struct FailingCheckError;
431
432        impl Check for FailingCheck {
433            fn name(&self) -> &str {
434                "test-failing-check-commit"
435            }
436
437            fn check(
438                &self,
439                _: &CheckGitContext,
440                _: &Commit,
441            ) -> Result<CheckResult, Box<dyn Error>> {
442                Err(FailingCheckError.into())
443            }
444        }
445
446        impl BranchCheck for FailingCheck {
447            fn name(&self) -> &str {
448                "test-failing-check-branch"
449            }
450
451            fn check(
452                &self,
453                _: &CheckGitContext,
454                _: &CommitId,
455            ) -> Result<CheckResult, Box<dyn Error>> {
456                Err(FailingCheckError.into())
457            }
458        }
459
460        impl TopicCheck for FailingCheck {
461            fn name(&self) -> &str {
462                "test-failing-check-topic"
463            }
464
465            fn check(&self, _: &CheckGitContext, _: &Topic) -> Result<CheckResult, Box<dyn Error>> {
466                Err(FailingCheckError.into())
467            }
468        }
469    }
470
471    #[test]
472    fn test_configuration_debug() {
473        let config = GitCheckConfiguration::new();
474        assert_eq!(
475            format!("{:?}", config),
476            "GitCheckConfiguration { 0 commit checks, 0 branch checks, 0 topic checks }",
477        );
478    }
479
480    const TARGET_COMMIT: &str = "27ff3ef5532d76afa046f76f4dd8f588dc3e83c3";
481    const TOPIC_COMMIT: &str = "a61fd3759b61a4a1f740f3fe656bc42151cefbdd";
482    const TOPIC2_COMMIT: &str = "112e9b34401724bff57f68cf47c5065d4342b263";
483
484    fn git_context() -> GitContext {
485        let gitdir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../.git"));
486        if !gitdir.exists() {
487            panic!("The tests must be run from a git checkout.");
488        }
489
490        GitContext::new(gitdir)
491    }
492
493    fn run_commit(config: &GitCheckConfiguration) -> CheckResult {
494        let ctx = git_context();
495        config
496            .run_commit(
497                &ctx,
498                &CommitId::new(TOPIC_COMMIT),
499                &Identity::new(
500                    "Rust Git Checks Core Tests",
501                    "rust-git-checks@example.invalid",
502                ),
503            )
504            .unwrap()
505    }
506
507    fn run_topic(
508        test_name: &str,
509        config: &GitCheckConfiguration,
510        commit: &str,
511    ) -> TopicCheckResult {
512        let ctx = git_context();
513        config
514            .run_topic(
515                &ctx,
516                test_name,
517                &CommitId::new(TARGET_COMMIT),
518                &CommitId::new(commit),
519                &Identity::new(
520                    "Rust Git Checks Core Tests",
521                    "rust-git-checks@example.invalid",
522                ),
523            )
524            .unwrap()
525    }
526
527    fn run_topic_bases(
528        test_name: &str,
529        config: &GitCheckConfiguration,
530        commit: &str,
531        bases: &[&str],
532    ) -> TopicCheckResult {
533        let ctx = git_context();
534        let bases: Vec<_> = bases.iter().copied().map(CommitId::new).collect();
535        config
536            .run_topic_multi_base(
537                &ctx,
538                test_name,
539                &CommitId::new(TARGET_COMMIT),
540                bases.iter(),
541                &CommitId::new(commit),
542                &Identity::new(
543                    "Rust Git Checks Core Tests",
544                    "rust-git-checks@example.invalid",
545                ),
546            )
547            .unwrap()
548    }
549
550    fn no_strings<'a>() -> iter::Empty<&'a String> {
551        iter::empty()
552    }
553
554    fn check_result(result: &CheckResult, errors: &[&str]) {
555        itertools::assert_equal(result.warnings(), no_strings());
556        itertools::assert_equal(result.alerts(), errors);
557        itertools::assert_equal(result.errors(), no_strings());
558        assert!(!result.temporary());
559        assert!(!result.allowed());
560        assert!(!result.pass());
561    }
562
563    fn check_result_ok(result: &CheckResult) {
564        itertools::assert_equal(result.warnings(), no_strings());
565        itertools::assert_equal(result.alerts(), no_strings());
566        itertools::assert_equal(result.errors(), no_strings());
567        assert!(!result.temporary());
568        assert!(!result.allowed());
569        assert!(result.pass());
570    }
571
572    #[test]
573    fn test_commit_check_errors_commit() {
574        let check = self::checks::FailingCheck {};
575        let mut config = GitCheckConfiguration::new();
576        config.add_check(&check);
577
578        let result = run_commit(&config);
579
580        check_result(
581            &result,
582            &[
583                "failed to run the test-failing-check-commit check on commit \
584                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
585            ],
586        );
587    }
588
589    #[test]
590    fn test_branch_check_errors_commit() {
591        let check = self::checks::FailingCheck {};
592        let mut config = GitCheckConfiguration::new();
593        config.add_branch_check(&check);
594
595        let result = run_commit(&config);
596
597        check_result_ok(&result);
598    }
599
600    #[test]
601    fn test_topic_check_errors_commit() {
602        let check = self::checks::FailingCheck {};
603        let mut config = GitCheckConfiguration::new();
604        config.add_topic_check(&check);
605
606        let result = run_commit(&config);
607
608        check_result_ok(&result);
609    }
610
611    #[test]
612    fn test_commit_check_errors_topic() {
613        let check = self::checks::FailingCheck {};
614        let mut config = GitCheckConfiguration::new();
615        config.add_check(&check);
616
617        let result = run_topic("test_commit_check_errors_topic", &config, TOPIC_COMMIT);
618
619        let mut commit_results = result.commit_results();
620        let (commit, commit_result) = commit_results.next().unwrap();
621        assert_eq!(commit.as_str(), "a61fd3759b61a4a1f740f3fe656bc42151cefbdd");
622        check_result(
623            commit_result,
624            &[
625                "failed to run the test-failing-check-commit check on commit \
626                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
627            ],
628        );
629        if let Some(res) = commit_results.next() {
630            panic!(
631                "multiple commits returned from a single-commit topic: {:?}",
632                res,
633            );
634        }
635        check_result_ok(result.topic_result());
636        check_result(
637            &result.into(),
638            &[
639                "failed to run the test-failing-check-commit check on commit \
640                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
641            ],
642        );
643    }
644
645    #[test]
646    fn test_commit_check_errors_topic_bases() {
647        let check = self::checks::FailingCheck {};
648        let mut config = GitCheckConfiguration::new();
649        config.add_check(&check);
650
651        let result = run_topic_bases(
652            "test_commit_check_errors_topic_bases",
653            &config,
654            TOPIC2_COMMIT,
655            &[TOPIC_COMMIT],
656        );
657
658        let mut commit_results = result.commit_results();
659        let (commit, commit_result) = commit_results.next().unwrap();
660        assert_eq!(commit.as_str(), "112e9b34401724bff57f68cf47c5065d4342b263");
661        check_result(
662            commit_result,
663            &[
664                "failed to run the test-failing-check-commit check on commit \
665                 112e9b34401724bff57f68cf47c5065d4342b263",
666            ],
667        );
668        if let Some(res) = commit_results.next() {
669            panic!(
670                "multiple commits returned from a single-commit topic: {:?}",
671                res,
672            );
673        }
674        check_result_ok(result.topic_result());
675        check_result(
676            &result.into(),
677            &[
678                "failed to run the test-failing-check-commit check on commit \
679                 112e9b34401724bff57f68cf47c5065d4342b263",
680            ],
681        );
682    }
683
684    #[test]
685    fn test_branch_check_errors_topic() {
686        let check = self::checks::FailingCheck {};
687        let mut config = GitCheckConfiguration::new();
688        config.add_branch_check(&check);
689
690        let result = run_topic("test_branch_check_errors_topic", &config, TOPIC_COMMIT);
691
692        let mut commit_results = result.commit_results();
693        let (commit, commit_result) = commit_results.next().unwrap();
694        assert_eq!(commit.as_str(), "a61fd3759b61a4a1f740f3fe656bc42151cefbdd");
695        check_result_ok(commit_result);
696        if let Some(res) = commit_results.next() {
697            panic!(
698                "multiple commits returned from a single-commit topic: {:?}",
699                res,
700            );
701        }
702        check_result(
703            result.topic_result(),
704            &["failed to run the test-failing-check-branch branch check"],
705        );
706        check_result(
707            &result.into(),
708            &["failed to run the test-failing-check-branch branch check"],
709        );
710    }
711
712    #[test]
713    fn test_branch_check_errors_topic_bases() {
714        let check = self::checks::FailingCheck {};
715        let mut config = GitCheckConfiguration::new();
716        config.add_branch_check(&check);
717
718        let result = run_topic_bases(
719            "test_branch_check_errors_topic_bases",
720            &config,
721            TOPIC2_COMMIT,
722            &[TOPIC_COMMIT],
723        );
724
725        let mut commit_results = result.commit_results();
726        let (commit, commit_result) = commit_results.next().unwrap();
727        assert_eq!(commit.as_str(), "112e9b34401724bff57f68cf47c5065d4342b263");
728        check_result_ok(commit_result);
729        if let Some(res) = commit_results.next() {
730            panic!(
731                "multiple commits returned from a single-commit topic: {:?}",
732                res,
733            );
734        }
735        check_result(
736            result.topic_result(),
737            &["failed to run the test-failing-check-branch branch check"],
738        );
739        check_result(
740            &result.into(),
741            &["failed to run the test-failing-check-branch branch check"],
742        );
743    }
744
745    #[test]
746    fn test_topic_check_errors_topic() {
747        let check = self::checks::FailingCheck {};
748        let mut config = GitCheckConfiguration::new();
749        config.add_topic_check(&check);
750
751        let result = run_topic("test_topic_check_errors_topic", &config, TOPIC_COMMIT);
752
753        let mut commit_results = result.commit_results();
754        let (commit, commit_result) = commit_results.next().unwrap();
755        assert_eq!(commit.as_str(), "a61fd3759b61a4a1f740f3fe656bc42151cefbdd");
756        check_result_ok(commit_result);
757        if let Some(res) = commit_results.next() {
758            panic!(
759                "multiple commits returned from a single-commit topic: {:?}",
760                res,
761            );
762        }
763        check_result(
764            result.topic_result(),
765            &["failed to run the test-failing-check-topic topic check"],
766        );
767        check_result(
768            &result.into(),
769            &["failed to run the test-failing-check-topic topic check"],
770        );
771    }
772
773    #[test]
774    fn test_topic_check_errors_topic_bases() {
775        let check = self::checks::FailingCheck {};
776        let mut config = GitCheckConfiguration::new();
777        config.add_topic_check(&check);
778
779        let result = run_topic_bases(
780            "test_topic_check_errors_topic_bases",
781            &config,
782            TOPIC2_COMMIT,
783            &[TOPIC_COMMIT],
784        );
785
786        let mut commit_results = result.commit_results();
787        let (commit, commit_result) = commit_results.next().unwrap();
788        assert_eq!(commit.as_str(), "112e9b34401724bff57f68cf47c5065d4342b263");
789        check_result_ok(commit_result);
790        if let Some(res) = commit_results.next() {
791            panic!(
792                "multiple commits returned from a single-commit topic: {:?}",
793                res,
794            );
795        }
796        check_result(
797            result.topic_result(),
798            &["failed to run the test-failing-check-topic topic check"],
799        );
800        check_result(
801            &result.into(),
802            &["failed to run the test-failing-check-topic topic check"],
803        );
804    }
805
806    #[test]
807    fn test_multiple_check_commit() {
808        let check = self::checks::FailingCheck {};
809        let mut config = GitCheckConfiguration::new();
810        config.add_check(&check);
811        config.add_check(&check);
812        config.add_branch_check(&check);
813        config.add_branch_check(&check);
814        config.add_topic_check(&check);
815        config.add_topic_check(&check);
816
817        let result = run_commit(&config);
818
819        check_result(
820            &result,
821            &[
822                "failed to run the test-failing-check-commit check on commit \
823                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
824                "failed to run the test-failing-check-commit check on commit \
825                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
826            ],
827        );
828    }
829
830    #[test]
831    fn test_multiple_check_topic() {
832        let check = self::checks::FailingCheck {};
833        let mut config = GitCheckConfiguration::new();
834        config.add_check(&check);
835        config.add_check(&check);
836        config.add_branch_check(&check);
837        config.add_branch_check(&check);
838        config.add_topic_check(&check);
839        config.add_topic_check(&check);
840
841        let result = run_topic("test_multiple_check_topic", &config, TOPIC_COMMIT);
842
843        let mut commit_results = result.commit_results();
844        let (commit, commit_result) = commit_results.next().unwrap();
845        assert_eq!(commit.as_str(), "a61fd3759b61a4a1f740f3fe656bc42151cefbdd");
846        check_result(
847            commit_result,
848            &[
849                "failed to run the test-failing-check-commit check on commit \
850                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
851                "failed to run the test-failing-check-commit check on commit \
852                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
853            ],
854        );
855        if let Some(res) = commit_results.next() {
856            panic!(
857                "multiple commits returned from a single-commit topic: {:?}",
858                res,
859            );
860        }
861        check_result(
862            result.topic_result(),
863            &[
864                "failed to run the test-failing-check-branch branch check",
865                "failed to run the test-failing-check-branch branch check",
866                "failed to run the test-failing-check-topic topic check",
867                "failed to run the test-failing-check-topic topic check",
868            ],
869        );
870        check_result(
871            &result.into(),
872            &[
873                "failed to run the test-failing-check-commit check on commit \
874                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
875                "failed to run the test-failing-check-commit check on commit \
876                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
877                "failed to run the test-failing-check-branch branch check",
878                "failed to run the test-failing-check-branch branch check",
879                "failed to run the test-failing-check-topic topic check",
880                "failed to run the test-failing-check-topic topic check",
881            ],
882        );
883    }
884
885    #[test]
886    fn test_check_multiple_topic() {
887        let check = self::checks::FailingCheck {};
888        let mut config = GitCheckConfiguration::new();
889        config.add_check(&check);
890        config.add_branch_check(&check);
891        config.add_topic_check(&check);
892
893        let result = run_topic("test_check_multiple_topic", &config, TOPIC2_COMMIT);
894
895        let mut commit_results = result.commit_results();
896        let (commit, commit_result) = commit_results.next().unwrap();
897        assert_eq!(commit.as_str(), "a61fd3759b61a4a1f740f3fe656bc42151cefbdd");
898        check_result(
899            commit_result,
900            &[
901                "failed to run the test-failing-check-commit check on commit \
902                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
903            ],
904        );
905        let (commit, commit_result) = commit_results.next().unwrap();
906        assert_eq!(commit.as_str(), "112e9b34401724bff57f68cf47c5065d4342b263");
907        check_result(
908            commit_result,
909            &[
910                "failed to run the test-failing-check-commit check on commit \
911                 112e9b34401724bff57f68cf47c5065d4342b263",
912            ],
913        );
914        if let Some(res) = commit_results.next() {
915            panic!(
916                "multiple commits returned from a single-commit topic: {:?}",
917                res,
918            );
919        }
920        check_result(
921            result.topic_result(),
922            &[
923                "failed to run the test-failing-check-branch branch check",
924                "failed to run the test-failing-check-topic topic check",
925            ],
926        );
927        check_result(
928            &result.into(),
929            &[
930                "failed to run the test-failing-check-commit check on commit \
931                 a61fd3759b61a4a1f740f3fe656bc42151cefbdd",
932                "failed to run the test-failing-check-commit check on commit \
933                 112e9b34401724bff57f68cf47c5065d4342b263",
934                "failed to run the test-failing-check-branch branch check",
935                "failed to run the test-failing-check-topic topic check",
936            ],
937        );
938    }
939}