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()