github.com/containers/podman/v4@v4.9.4/contrib/cirrus/pr-removes-fixed-skips (about)

     1  #!/usr/bin/perl
     2  #
     3  # pr-removes-fixed-skips - if PR says "Fixes: #123", no skips should mention 123
     4  #
     5  package Podman::CI::PrRemovesFixedSkips;
     6  
     7  use v5.14;
     8  use utf8;
     9  
    10  # Grumble. CI system doesn't have 'open'
    11  binmode STDIN,  ':utf8';
    12  binmode STDOUT, ':utf8';
    13  
    14  use strict;
    15  use warnings;
    16  
    17  (our $ME = $0) =~ s|.*/||;
    18  our $VERSION = '0.1';
    19  
    20  ###############################################################################
    21  # BEGIN boilerplate args checking, usage messages
    22  
    23  sub usage {
    24      print  <<"END_USAGE";
    25  Usage: $ME [OPTIONS]
    26  
    27  $ME reads a GitHub PR message, looks for
    28  Fixed/Resolved/Closed issue IDs, then greps for test files
    29  containing 'Skip' instructions or FIXME comments referencing
    30  those IDs. If we find any, we abort with a loud and hopefully
    31  useful message.
    32  
    33  $ME is intended to run from Cirrus CI.
    34  
    35  OPTIONS:
    36  
    37    --help         display this message
    38    --version      display program name and version
    39  END_USAGE
    40  
    41      exit;
    42  }
    43  
    44  # Command-line options.  Note that this operates directly on @ARGV !
    45  our $debug   = 0;
    46  sub handle_opts {
    47      use Getopt::Long;
    48      GetOptions(
    49          'debug!'     => \$debug,
    50  
    51          help         => \&usage,
    52          version      => sub { print "$ME version $VERSION\n"; exit 0 },
    53      ) or die "Try `$ME --help' for help\n";
    54  }
    55  
    56  # END   boilerplate args checking, usage messages
    57  ###############################################################################
    58  
    59  ############################## CODE BEGINS HERE ###############################
    60  
    61  # The term is "modulino".
    62  __PACKAGE__->main()                                     unless caller();
    63  
    64  # Main code.
    65  sub main {
    66      # Note that we operate directly on @ARGV, not on function parameters.
    67      # This is deliberate: it's because Getopt::Long only operates on @ARGV
    68      # and there's no clean way to make it use @_.
    69      handle_opts();                      # will set package globals
    70  
    71      die "$ME: This script takes no arguments; try $ME --help\n"  if @ARGV;
    72  
    73      # Check commit messages from both github and git; they often differ
    74      my @issues = fixed_issues(cirrus_change_message(), git_commit_messages())
    75          or exit 0;
    76  
    77      my @found = unremoved_skips(@issues)
    78          or exit 0;
    79  
    80      # Found unremoved skips. Fail loudly.
    81      my $issues = "issue #$issues[0]";
    82      if (@issues > 1) {
    83          $issues = "issues #" . join ", #", @issues;
    84      }
    85  
    86      warn "$ME: Your PR claims to resolve $issues\n";
    87      warn "    ...but does not remove associated Skips/FIXMEs:\n";
    88      warn "\n";
    89      warn "   $_\n" for @found;
    90      warn "\n";
    91      warn <<"END_ADVICE";
    92  Please do not leave Skips or FIXMEs for closed issues.
    93  
    94  If an issue is truly fixed, please remove all Skips referencing it.
    95  
    96  If an issue is only PARTIALLY fixed, please file a new issue for the
    97  remaining problem, and update remaining Skips to point to that issue.
    98  
    99  And if the issue is fixed but the Skip needs to remain for other
   100  reasons, again, please update the Skip message accordingly.
   101  END_ADVICE
   102      exit 1;
   103  }
   104  
   105  #####################
   106  #  unremoved_skips  #  Returns list of <path>:<lineno>:<skip string> matches
   107  #####################
   108  sub unremoved_skips {
   109      my $issues = join('|', @_);
   110  
   111      my $re = "(^\\s\+skip|fixme).*#($issues)[^0-9]";
   112      # FIXME FIXME FIXME: use File::Find instead of enumerating directories
   113      # (the important thing here is to exclude vendor)
   114      my @grep = ('grep', '-E', '-rin', $re, "test", "cmd", "libpod", "pkg");
   115  
   116      my @skips;
   117      open my $grep_fh, '-|', @grep
   118          or die "$ME: Could not fork: $!\n";
   119      while (my $line = <$grep_fh>) {
   120          chomp $line;
   121  
   122          # e.g., test/system/030-run.bats:809:   skip "FIXME: #12345 ..."
   123          $line =~ m!^(\S+):\d+:\s!
   124              or die "$ME: Internal error: output from grep does not match <path>:<lineno>:<space>: '$line'";
   125          my $path = $1;
   126  
   127          # Any .go or .bats file, or the apply-podman-deltas script
   128          if ($path =~ /\.(go|bats)$/ || $path =~ m!/apply-podman-deltas$!) {
   129              push @skips, $line;
   130          }
   131  
   132          # Anything else is probably a backup file, or something else
   133          # we don't care about. (We won't see these in CI, but might
   134          # in a user devel environment)
   135          elsif ($debug) {
   136              print "[ ignoring: $line ]\n";
   137          }
   138      }
   139      close $grep_fh;
   140  
   141      return sort @skips;
   142  }
   143  
   144  ##################
   145  #  fixed_issues  #  Parses change message, looks for Fixes/Closes/Resolves
   146  ##################
   147  sub fixed_issues {
   148      my @issues;
   149  
   150      for my $msg (@_) {
   151          # https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
   152          #
   153          #                 1               1              2   2
   154          while ($msg =~ /\b(Fix|Clos|Resolv)[esd]*[:\s]+\#(\d+)/gis) {
   155              # Skip dups: we're probably checking both github and git messages
   156              push @issues, $2
   157                  unless grep { $_ eq $2 } @issues;
   158          }
   159      }
   160  
   161      return @issues;
   162  }
   163  
   164  ###########################
   165  #  cirrus_change_message  #  this is the one from *GitHub*, not *git*
   166  ###########################
   167  sub cirrus_change_message {
   168      my $change_message = $ENV{CIRRUS_CHANGE_MESSAGE}
   169          or do {
   170              # OK for it to be unset if we're not running CI on a PR
   171              return if ! $ENV{CIRRUS_PR};
   172              # But if we _are_ running on a PR, something went badly wrong.
   173              die "$ME: \$CIRRUS_CHANGE_MESSAGE is undefined\n";
   174          };
   175  
   176      return $change_message;
   177  }
   178  
   179  #########################
   180  #  git_commit_messages  #  the ones from the *git history*
   181  #########################
   182  sub git_commit_messages {
   183      # Probably the same as HEAD, but use Cirrus-defined value if available
   184      my $head = $ENV{CIRRUS_CHANGE_IN_REPO} || 'HEAD';
   185  
   186      # Base of this PR. Here we absolutely rely on cirrus.
   187      return if ! $ENV{DEST_BRANCH};
   188      chomp(my $base = qx{git merge-base $ENV{DEST_BRANCH} $head});
   189  
   190      qx{git log --format=%B $base..$head};
   191  }
   192  
   193  1;