github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/gubernator/github/classifier_test.py (about)

     1  #!/usr/bin/env python
     2  
     3  # Copyright 2016 The Kubernetes Authors.
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #     http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  
    17  import unittest
    18  
    19  import classifier
    20  
    21  
    22  class MergedTest(unittest.TestCase):
    23      def test_merged(self):
    24          self.assertEqual(classifier.get_merged(zip('abcd', [
    25              {'issue': {'n': 1, 'a': 2}},
    26              {'pull_request': {'n': 2, 'b': 3}},
    27              {'c': 4},
    28              {'issue': {'n': 3, 'd': 4},
    29               'pull_request': {'n': 4, 'e': 5}}
    30          ], [0] * 4)), {'n': 4, 'a': 2, 'b': 3, 'd': 4, 'e': 5})
    31  
    32  
    33  def diffs_to_events(*diffs):
    34      events = []
    35      for diff in diffs:
    36          label = {'name': diff[1:], 'color': '#fff'}
    37          if diff[0] == '+':
    38              action = 'labeled'
    39          elif diff[0] == '-':
    40              action = 'unlabeled'
    41          events.append(('pull_request',
    42                         {'action': action,
    43                          'label': label}, 0))
    44      return events
    45  
    46  
    47  class LabelsTest(unittest.TestCase):
    48      def expect_labels(self, events, names, extra_events=None):
    49          labels = classifier.get_labels(events)
    50          if extra_events:
    51              labels = classifier.get_labels(extra_events, labels)
    52          self.assertEqual(sorted(labels.keys()), sorted(names))
    53  
    54      def test_empty(self):
    55          self.expect_labels([('comment', {'body': 'no labels here'}, 0)], [])
    56  
    57      def test_colors(self):
    58          self.assertEqual(classifier.get_labels(
    59                  [('c', {'issue':
    60                          {'labels': [{'name': 'foo', 'color': '#abc'}]}
    61                          }, 0)]),
    62              {'foo': '#abc'})
    63  
    64      def test_labeled_action(self):
    65          self.expect_labels(diffs_to_events('+a'), ['a'])
    66          self.expect_labels(diffs_to_events('+a', '+a'), ['a'])
    67          self.expect_labels(diffs_to_events('+a', '-a'), [])
    68          self.expect_labels(diffs_to_events('+a', '+b', '-c', '-b'), ['a'])
    69          self.expect_labels(diffs_to_events('+a', '+b', '-c'), ['a'],
    70                             extra_events=diffs_to_events('-b'))
    71  
    72      def test_issue_overrides_action(self):
    73          labels = [{'name': 'x', 'color': 'y'}]
    74          self.expect_labels(diffs_to_events('+a') +
    75              [('other_event', {'issue': {'labels': labels}}, 0)], ['x'])
    76  
    77      def test_labeled_action_missing_label(self):
    78          self.expect_labels([('pull_request', {'action': 'labeled'}, 0)], [])
    79  
    80  
    81  def make_comment_event(num, name, msg='', event='issue_comment',
    82                         action='created', ts=None):
    83      return event, {
    84          'action': action,
    85          'sender': {'login': name},
    86          'comment': {
    87              'id': num,
    88              'user': {'login': name},
    89              'body': msg,
    90              'created_at': ts,
    91          }
    92      }, ts
    93  
    94  
    95  class CalculateTest(unittest.TestCase):
    96      def test_classify(self):
    97          # A quick integration test to ensure that all the sub-parts are included.
    98          # If this test fails, a smaller unit test SHOULD fail as well.
    99          self.assertEqual(classifier.classify([
   100                  ('pull_request', {
   101                      'pull_request': {
   102                          'state': 'open',
   103                          'user': {'login': 'a'},
   104                          'assignees': [{'login': 'b'}],
   105                          'title': 'some fix',
   106                          'head': {'sha': 'abcdef'},
   107                          'additions': 1,
   108                          'deletions': 1,
   109                          'milestone': {'title': 'v1.10'},
   110                      }
   111                  }, 1),
   112                  make_comment_event(1, 'k8s-bot',
   113                      'failure in https://gubernator.k8s.io/build/bucket/job/123/', ts=2),
   114                  ('pull_request', {
   115                      'action': 'labeled',
   116                      'label': {'name': 'release-note-none', 'color': 'orange'},
   117                  }, 3),
   118                  make_comment_event(2, 'k8s-merge-robot', '<!-- META={"approvers":["o"]} -->', ts=4),
   119              ], status_fetcher={'abcdef': {'e2e': ['failure', None, 'stuff is broken']}}.get
   120          ),
   121          (True, True, ['a', 'b', 'o'],
   122           {
   123              'author': 'a',
   124              'approvers': ['o'],
   125              'assignees': ['b'],
   126              'additions': 1,
   127              'deletions': 1,
   128              'attn': {'a': 'fix tests', 'b': 'needs review#0#0', 'o': 'needs approval'},
   129              'title': 'some fix',
   130              'labels': {'release-note-none': 'orange'},
   131              'head': 'abcdef',
   132              'needs_rebase': False,
   133              'status': {'e2e': ['failure', None, 'stuff is broken']},
   134              'xrefs': ['/bucket/job/123'],
   135              'milestone': 'v1.10',
   136          }))
   137  
   138      def test_distill(self):
   139          self.assertEqual(classifier.distill_events([
   140              make_comment_event(1, 'a', ts=1),
   141              make_comment_event(2, 'b', ts=2),
   142              make_comment_event(1, 'a', action='deleted', ts=3),
   143              make_comment_event(3, 'c', event='pull_request_review_comment', ts=4),
   144              make_comment_event(4, 'k8s-bot', ts=4),
   145              ('pull_request', {'action': 'synchronize', 'sender': {'login': 'auth'}}, 5),
   146              ('pull_request', {'action': 'labeled', 'sender': {'login': 'rev'},
   147                  'label': {'name': 'lgtm'}}, 6),
   148          ]),
   149          [
   150              ('comment', 'b', 2),
   151              ('comment', 'c', 4),
   152              ('push', 'auth', 5),
   153              ('label lgtm', 'rev', 6),
   154          ])
   155  
   156      def test_calculate_attention(self):
   157          def expect(payload, events, expected_attn):
   158              self.assertEqual(classifier.calculate_attention(events, payload),
   159                               expected_attn)
   160  
   161          def make_payload(author, assignees=None, labels=None, **kwargs):
   162              ret = {'author': author, 'assignees': assignees or [], 'labels': labels or []}
   163              ret.update(kwargs)
   164              return ret
   165  
   166          expect(make_payload('alpha', needs_rebase=True), [],
   167              {'alpha': 'needs rebase'})
   168          expect(make_payload('beta', labels={'do-not-merge/release-note-label-needed'}), [],
   169              {'beta': 'needs release-note label'})
   170          expect(make_payload('gamma', status={'ci': ['failure', '', '']}), [],
   171              {'gamma': 'fix tests'})
   172          expect(make_payload('gamma', status={'ci': ['failure', '', '']}),
   173              [('comment', 'other', 1)],
   174              {'gamma': 'address comments#1#1'})
   175          expect(make_payload('delta', ['epsilon']), [],
   176              {'epsilon': 'needs review#0#0'})
   177  
   178          expect(make_payload('alpha', ['alpha']), [('comment', 'other', 1)],
   179              {'alpha': 'address comments#1#1'})
   180  
   181          expect(make_payload('alpha', approvers=['owner']), [],
   182              {'owner': 'needs approval'})
   183  
   184      def test_author_state(self):
   185          def expect(events, result):
   186              self.assertEqual(classifier.get_author_state('author', events),
   187                               result)
   188          expect([], ('waiting', 0, 0))
   189          expect([('comment', 'author', 1)], ('waiting', 0, 0))
   190          expect([('comment', 'other', 1)], ('address comments', 1, 1))
   191          expect([('comment', 'other', 1), ('push', 'author', 2)], ('waiting', 2, 2))
   192          expect([('comment', 'other', 1), ('comment', 'author', 2)], ('waiting', 2, 2))
   193          expect([('comment', 'other', 1), ('comment', 'other', 2)], ('address comments', 1, 2))
   194  
   195      def test_assignee_state(self):
   196          def expect(events, result):
   197              self.assertEqual(classifier.get_assignee_state('me', 'author', events),
   198                               result)
   199          expect([], ('needs review', 0, 0))
   200          expect([('comment', 'other', 1)], ('needs review', 0, 0))
   201          expect([('comment', 'me', 1)], ('waiting', 1, 1))
   202          expect([('label lgtm', 'other', 1)], ('needs review', 0, 0))
   203          expect([('label lgtm', 'me', 1)], ('waiting', 1, 1))
   204          expect([('comment', 'me', 1), ('push', 'author', 2)], ('needs review', 2, 2))
   205          expect([('comment', 'me', 1), ('comment', 'author', 2)], ('needs review', 2, 2))
   206          expect([('comment', 'me', 1), ('comment', 'author', 2), ('comment', 'author', 3)],
   207                 ('needs review', 2, 3))
   208  
   209      def test_xrefs(self):
   210          def expect(body, comments, result):
   211              self.assertEqual(result, classifier.get_xrefs(
   212                  [{'comment': c} for c in comments], {'body': body}))
   213          def fail(path):
   214              return 'foobar https://gubernator.k8s.io/build%s asdf' % path
   215          expect(None, [], [])
   216          expect('something', [], [])
   217          expect(fail('/a/b/34/'), [], ['/a/b/34'])
   218          expect(None, [fail('/a/b/34/')], ['/a/b/34'])
   219          expect(fail('/a/b/34/'), [fail('/a/b/34]')], ['/a/b/34'])
   220          expect(fail('/a/b/34/)'), [fail('/a/b/35]')], ['/a/b/34', '/a/b/35'])
   221  
   222      def test_reviewers(self):
   223          def expect(events, result):
   224              self.assertEqual(result, classifier.get_reviewers(events))
   225  
   226          def mk(*specs):
   227              out = []
   228              for event, action, body in specs:
   229                  body = dict(body)  # copy
   230                  body['action'] = action
   231                  out.append((event, body, 0))
   232              return out
   233  
   234          expect([], set())
   235  
   236          user_a = {'requested_reviewer': {'login': 'a'}}
   237          expect(mk(('pull_request', 'review_requested', user_a)), {'a'})
   238          expect(mk(('pull_request', 'review_request_removed', user_a)), set())
   239          expect(mk(('pull_request', 'review_requested', user_a),
   240                    ('pull_request', 'review_request_removed', user_a)), set())
   241          expect(mk(('pull_request_review', 'submitted', {'sender': {'login': 'a'}})), {'a'})
   242  
   243      def test_approvers(self):
   244          def expect(comment, result):
   245              self.assertEqual(result, classifier.get_approvers([{
   246                  'author': 'k8s-merge-robot', 'comment': comment}]))
   247  
   248          expect('nothing', [])
   249          expect('before\n<!-- META={approvers:[someone]} -->', ['someone'])
   250          expect('<!-- META={approvers:[someone,else]} -->', ['someone', 'else'])
   251          expect('<!-- META={approvers:[someone,else]} -->', ['someone', 'else'])
   252  
   253          # The META format is *supposed* to be JSON, but a recent change broke it.
   254          # Support both formats so it can be fixed in the future.
   255          expect('<!-- META={"approvers":["username"]} -->\n', ['username'])
   256  
   257  
   258  class CommentsTest(unittest.TestCase):
   259      def test_basic(self):
   260          self.assertEqual(classifier.get_comments([make_comment_event(1, 'aaa', 'msg', ts=2016)]),
   261              [{'id': 1, 'author': 'aaa', 'comment': 'msg', 'timestamp': 2016}])
   262  
   263      def test_deleted(self):
   264          self.assertEqual(classifier.get_comments([
   265              make_comment_event(1, 'aaa', 'msg', 2016),
   266              make_comment_event(1, None, None, None, action='deleted'),
   267              make_comment_event(2, '', '', '', action='deleted')]),
   268              [])
   269  
   270      def test_edited(self):
   271          self.assertEqual(classifier.get_comments([
   272              make_comment_event(1, 'aaa', 'msg', ts=2016),
   273              make_comment_event(1, 'aaa', 'redacted', ts=2016.1, action='edited')]),
   274              [{'id': 1, 'author': 'aaa', 'comment': 'redacted', 'timestamp': 2016.1}])
   275  
   276  
   277  if __name__ == '__main__':
   278      unittest.main()