github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/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 json
    18  import unittest
    19  
    20  import classifier
    21  
    22  
    23  class DeduperTest(unittest.TestCase):
    24      @staticmethod
    25      def dedup(obj):
    26          return classifier.Deduper().dedup(obj)
    27  
    28      def test_types(self):
    29          a = (u'foo', 2, {'bar': ['foo', 'bar']})
    30          self.assertEqual(self.dedup(a), a)
    31  
    32      def test_dedupe(self):
    33          # Python interns strings in structs, so...
    34          a = ['foo', 'foo']
    35          self.assertIs(a[0], a[1])
    36  
    37          # Use json.loads to get around it
    38          b = json.loads('["foo", "foo"]')
    39          self.assertIsNot(b[0], b[1])
    40  
    41          # When deduplicated, the strings are now the same object.
    42          c = self.dedup(b)
    43          self.assertIs(c[0], c[1])
    44  
    45  
    46  class MergedTest(unittest.TestCase):
    47      def test_merged(self):
    48          self.assertEqual(classifier.get_merged(zip('abcd', [
    49              {'issue': {'n': 1, 'a': 2}},
    50              {'pull_request': {'n': 2, 'b': 3}},
    51              {'c': 4},
    52              {'issue': {'n': 3, 'd': 4},
    53               'pull_request': {'n': 4, 'e': 5}}
    54          ], [0] * 4)), {'n': 4, 'a': 2, 'b': 3, 'd': 4, 'e': 5})
    55  
    56  
    57  def diffs_to_events(*diffs):
    58      events = []
    59      for diff in diffs:
    60          label = {'name': diff[1:], 'color': '#fff'}
    61          if diff[0] == '+':
    62              action = 'labeled'
    63          elif diff[0] == '-':
    64              action = 'unlabeled'
    65          events.append(('pull_request',
    66                         {'action': action,
    67                          'label': label}, 0))
    68      return events
    69  
    70  
    71  class LabelsTest(unittest.TestCase):
    72      def expect_labels(self, events, names):
    73          labels = classifier.get_labels(events)
    74          self.assertEqual(sorted(labels.keys()), sorted(names))
    75  
    76      def test_empty(self):
    77          self.expect_labels([('comment', {'body': 'no labels here'}, 0)], [])
    78  
    79      def test_colors(self):
    80          self.assertEqual(classifier.get_labels(
    81                  [('c', {'issue':
    82                          {'labels': [{'name': 'foo', 'color': '#abc'}]}
    83                          }, 0)]),
    84              {'foo': '#abc'})
    85  
    86      def test_labeled_action(self):
    87          self.expect_labels(diffs_to_events('+a'), ['a'])
    88          self.expect_labels(diffs_to_events('+a', '+a'), ['a'])
    89          self.expect_labels(diffs_to_events('+a', '-a'), [])
    90          self.expect_labels(diffs_to_events('+a', '+b', '-c', '-b'), ['a'])
    91  
    92      def test_issue_overrides_action(self):
    93          labels = [{'name': 'x', 'color': 'y'}]
    94          self.expect_labels(diffs_to_events('+a') +
    95              [('other_event', {'issue': {'labels': labels}}, 0)], ['x'])
    96  
    97      def test_labeled_action_missing_label(self):
    98          self.expect_labels([('pull_request', {'action': 'labeled'}, 0)], [])
    99  
   100  
   101  def make_comment_event(num, name, msg='', event='issue_comment',
   102                         action='created', ts=None):
   103      return event, {
   104          'action': action,
   105          'sender': {'login': name},
   106          'comment': {
   107              'id': num,
   108              'user': {'login': name},
   109              'body': msg,
   110              'created_at': ts,
   111          }
   112      }, ts
   113  
   114  
   115  class CalculateTest(unittest.TestCase):
   116      def test_classify(self):
   117          # A quick integration test to ensure that all the sub-parts are included.
   118          # If this test fails, a smaller unit test SHOULD fail as well.
   119          self.assertEqual(classifier.classify([
   120                  ('pull_request', {
   121                      'pull_request': {
   122                          'state': 'open',
   123                          'user': {'login': 'a'},
   124                          'assignees': [{'login': 'b'}],
   125                          'title': 'some fix',
   126                          'head': {'sha': 'abcdef'},
   127                          'additions': 1,
   128                          'deletions': 1,
   129                      }
   130                  }, 1),
   131                  make_comment_event(1, 'k8s-bot',
   132                      'failure in https://k8s-gubernator.appspot.com/build/bucket/job/123/', ts=2),
   133                  ('pull_request', {
   134                      'action': 'labeled',
   135                      'label': {'name': 'release-note-none', 'color': 'orange'},
   136                  }, 3),
   137                  make_comment_event(2, 'k8s-merge-robot', '<!-- META={"approvers":["o"]} -->', ts=4),
   138              ], {'e2e': ['failure', None, 'stuff is broken']}
   139          ),
   140          (True, True, ['a', 'b', 'o'],
   141           {
   142              'author': 'a',
   143              'approvers': ['o'],
   144              'assignees': ['b'],
   145              'additions': 1,
   146              'deletions': 1,
   147              'attn': {'a': 'fix tests', 'b': 'needs review#0#0', 'o': 'needs approval'},
   148              'title': 'some fix',
   149              'labels': {'release-note-none': 'orange'},
   150              'head': 'abcdef',
   151              'needs_rebase': False,
   152              'status': {'e2e': ['failure', None, 'stuff is broken']},
   153              'xrefs': ['/bucket/job/123'],
   154          }))
   155  
   156      def test_distill(self):
   157          self.assertEqual(classifier.distill_events([
   158              make_comment_event(1, 'a', ts=1),
   159              make_comment_event(2, 'b', ts=2),
   160              make_comment_event(1, 'a', action='deleted', ts=3),
   161              make_comment_event(3, 'c', event='pull_request_review_comment', ts=4),
   162              make_comment_event(4, 'k8s-bot', ts=4),
   163              ('pull_request', {'action': 'synchronize', 'sender': {'login': 'auth'}}, 5),
   164              ('pull_request', {'action': 'labeled', 'sender': {'login': 'rev'},
   165                  'label': {'name': 'lgtm'}}, 6),
   166          ]),
   167          [
   168              ('comment', 'b', 2),
   169              ('comment', 'c', 4),
   170              ('push', 'auth', 5),
   171              ('label lgtm', 'rev', 6),
   172          ])
   173  
   174      def test_calculate_attention(self):
   175          def expect(payload, events, expected_attn):
   176              self.assertEqual(classifier.calculate_attention(events, payload),
   177                               expected_attn)
   178  
   179          def make_payload(author, assignees=None, labels=None, **kwargs):
   180              ret = {'author': author, 'assignees': assignees or [], 'labels': labels or []}
   181              ret.update(kwargs)
   182              return ret
   183  
   184          expect(make_payload('alpha', needs_rebase=True), [],
   185              {'alpha': 'needs rebase'})
   186          expect(make_payload('beta', labels={'release-note-label-needed'}), [],
   187              {'beta': 'needs release-note label'})
   188          expect(make_payload('gamma', status={'ci': ['failure', '', '']}), [],
   189              {'gamma': 'fix tests'})
   190          expect(make_payload('gamma', status={'ci': ['failure', '', '']}),
   191              [('comment', 'other', 1)],
   192              {'gamma': 'address comments#1#1'})
   193          expect(make_payload('delta', ['epsilon']), [],
   194              {'epsilon': 'needs review#0#0'})
   195  
   196          expect(make_payload('alpha', ['alpha']), [('comment', 'other', 1)],
   197              {'alpha': 'address comments#1#1'})
   198  
   199          expect(make_payload('alpha', approvers=['owner']), [],
   200              {'owner': 'needs approval'})
   201  
   202      def test_author_state(self):
   203          def expect(events, result):
   204              self.assertEqual(classifier.get_author_state('author', events),
   205                               result)
   206          expect([], ('waiting', 0, 0))
   207          expect([('comment', 'author', 1)], ('waiting', 0, 0))
   208          expect([('comment', 'other', 1)], ('address comments', 1, 1))
   209          expect([('comment', 'other', 1), ('push', 'author', 2)], ('waiting', 2, 2))
   210          expect([('comment', 'other', 1), ('comment', 'author', 2)], ('waiting', 2, 2))
   211          expect([('comment', 'other', 1), ('comment', 'other', 2)], ('address comments', 1, 2))
   212  
   213      def test_assignee_state(self):
   214          def expect(events, result):
   215              self.assertEqual(classifier.get_assignee_state('me', 'author', events),
   216                               result)
   217          expect([], ('needs review', 0, 0))
   218          expect([('comment', 'other', 1)], ('needs review', 0, 0))
   219          expect([('comment', 'me', 1)], ('waiting', 1, 1))
   220          expect([('label lgtm', 'other', 1)], ('needs review', 0, 0))
   221          expect([('label lgtm', 'me', 1)], ('waiting', 1, 1))
   222          expect([('comment', 'me', 1), ('push', 'author', 2)], ('needs review', 2, 2))
   223          expect([('comment', 'me', 1), ('comment', 'author', 2)], ('needs review', 2, 2))
   224          expect([('comment', 'me', 1), ('comment', 'author', 2), ('comment', 'author', 3)],
   225                 ('needs review', 2, 3))
   226  
   227      def test_xrefs(self):
   228          def expect(body, comments, result):
   229              self.assertEqual(result, classifier.get_xrefs(
   230                  [{'comment': c} for c in comments], {'body': body}))
   231          def fail(path):
   232              return 'foobar https://k8s-gubernator.appspot.com/build%s asdf' % path
   233          expect(None, [], [])
   234          expect('something', [], [])
   235          expect(fail('/a/b/34/'), [], ['/a/b/34'])
   236          expect(None, [fail('/a/b/34/')], ['/a/b/34'])
   237          expect(fail('/a/b/34/'), [fail('/a/b/34]')], ['/a/b/34'])
   238          expect(fail('/a/b/34/)'), [fail('/a/b/35]')], ['/a/b/34', '/a/b/35'])
   239  
   240      def test_reviewers(self):
   241          def expect(events, result):
   242              self.assertEqual(result, classifier.get_reviewers(events))
   243  
   244          def mk(*specs):
   245              out = []
   246              for event, action, body in specs:
   247                  body = dict(body)  # copy
   248                  body['action'] = action
   249                  out.append((event, body, 0))
   250              return out
   251  
   252          expect([], set())
   253  
   254          user_a = {'requested_reviewer': {'login': 'a'}}
   255          expect(mk(('pull_request', 'review_requested', user_a)), {'a'})
   256          expect(mk(('pull_request', 'review_request_removed', user_a)), set())
   257          expect(mk(('pull_request', 'review_requested', user_a),
   258                    ('pull_request', 'review_request_removed', user_a)), set())
   259  
   260      def test_approvers(self):
   261          def expect(comment, result):
   262              self.assertEqual(result, classifier.get_approvers([{
   263                  'author': 'k8s-merge-robot', 'comment': comment}]))
   264  
   265          expect('nothing', [])
   266          expect('before\n<!-- META={approvers:[someone]} -->', ['someone'])
   267          expect('<!-- META={approvers:[someone,else]} -->', ['someone', 'else'])
   268          expect('<!-- META={approvers:[someone,else]} -->', ['someone', 'else'])
   269  
   270          # The META format is *supposed* to be JSON, but a recent change broke it.
   271          # Support both formats so it can be fixed in the future.
   272          expect('<!-- META={"approvers":["username"]} -->\n', ['username'])
   273  
   274  
   275  class CommentsTest(unittest.TestCase):
   276      def test_basic(self):
   277          self.assertEqual(classifier.get_comments([make_comment_event(1, 'aaa', 'msg', ts=2016)]),
   278              [{'author': 'aaa', 'comment': 'msg', 'timestamp': 2016}])
   279  
   280      def test_deleted(self):
   281          self.assertEqual(classifier.get_comments([
   282              make_comment_event(1, 'aaa', 'msg', 2016),
   283              make_comment_event(1, None, None, None, action='deleted'),
   284              make_comment_event(2, '', '', '', action='deleted')]),
   285              [])
   286  
   287      def test_edited(self):
   288          self.assertEqual(classifier.get_comments([
   289              make_comment_event(1, 'aaa', 'msg', ts=2016),
   290              make_comment_event(1, 'aaa', 'redacted', ts=2016.1, action='edited')]),
   291              [{'author': 'aaa', 'comment': 'redacted', 'timestamp': 2016.1}])
   292  
   293  
   294  if __name__ == '__main__':
   295      unittest.main()