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