| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 from datetime import datetime |
| 6 from datetime import timedelta |
| 7 |
| 5 from google.appengine.ext import ndb | 8 from google.appengine.ext import ndb |
| 6 import webapp2 | 9 import webapp2 |
| 7 | 10 |
| 8 from testing_utils import testing | 11 from testing_utils import testing |
| 9 | 12 |
| 10 from handlers import triage_analysis | 13 from handlers import triage_analysis |
| 14 from model import analysis_status |
| 15 from model import result_status |
| 11 from model.wf_analysis import WfAnalysis | 16 from model.wf_analysis import WfAnalysis |
| 12 from model import result_status | |
| 13 from model import analysis_status | |
| 14 from waterfall import buildbot | 17 from waterfall import buildbot |
| 15 | 18 |
| 16 | 19 |
| 17 class TriageAnalysisTest(testing.AppengineTestCase): | 20 class TriageAnalysisTest(testing.AppengineTestCase): |
| 18 app_module = webapp2.WSGIApplication([ | 21 app_module = webapp2.WSGIApplication([ |
| 19 ('/triage-analysis', triage_analysis.TriageAnalysis), | 22 ('/triage-analysis', triage_analysis.TriageAnalysis), |
| 20 ], debug=True) | 23 ], debug=True) |
| 21 | 24 |
| 22 def setUp(self): | 25 def setUp(self): |
| 23 super(TriageAnalysisTest, self).setUp() | 26 super(TriageAnalysisTest, self).setUp() |
| 24 self.master_name = 'm' | 27 self.master_name = 'm' |
| 25 self.builder_name = 'b' | 28 self.builder_name = 'b' |
| 26 self.build_number_incomplete = 120 # Analysis is not completed yet. | 29 self.build_number_incomplete = 120 # Analysis is not completed yet. |
| 27 self.build_number_found = 122 # Suspected CLs are found for this build. | 30 self.build_number_found = 122 # Suspected CLs are found for this build. |
| 28 self.build_number_not_found = 123 # No suspected CLs found. | 31 self.build_number_not_found = 123 # No suspected CLs found. |
| 29 self.suspected_cls = [{ | 32 self.suspected_cls = [{ |
| 30 'repo_name': 'chromium', | 33 'repo_name': 'chromium', |
| 31 'revision': 'r1', | 34 'revision': 'r1', |
| 32 'commit_position': 123, | 35 'commit_position': 123, |
| 33 'url': 'https://codereview.chromium.org/123', | 36 'url': 'https://codereview.chromium.org/123', |
| 34 }] | 37 }] |
| 35 | 38 |
| 39 self.build_start_time = (datetime.utcnow() - timedelta(2)).replace( |
| 40 hour=12, minute=0, second=0, microsecond=0) # Two days ago, UTC Noon. |
| 41 |
| 36 analysis = WfAnalysis.Create( | 42 analysis = WfAnalysis.Create( |
| 37 self.master_name, self.builder_name, self.build_number_incomplete) | 43 self.master_name, self.builder_name, self.build_number_incomplete) |
| 38 analysis.status = analysis_status.RUNNING | 44 analysis.status = analysis_status.RUNNING |
| 39 analysis.put() | 45 analysis.put() |
| 40 | 46 |
| 41 analysis = WfAnalysis.Create( | 47 analysis = WfAnalysis.Create( |
| 42 self.master_name, self.builder_name, self.build_number_found) | 48 self.master_name, self.builder_name, self.build_number_found) |
| 43 analysis.status = analysis_status.COMPLETED | 49 analysis.status = analysis_status.COMPLETED |
| 44 analysis.suspected_cls = self.suspected_cls | 50 analysis.suspected_cls = self.suspected_cls |
| 51 analysis.build_start_time = self.build_start_time |
| 45 analysis.put() | 52 analysis.put() |
| 46 | 53 |
| 47 analysis = WfAnalysis.Create( | 54 analysis = WfAnalysis.Create( |
| 48 self.master_name, self.builder_name, self.build_number_not_found) | 55 self.master_name, self.builder_name, self.build_number_not_found) |
| 49 analysis.status = analysis_status.COMPLETED | 56 analysis.status = analysis_status.COMPLETED |
| 50 analysis.put() | 57 analysis.put() |
| 51 | 58 |
| 52 self.mock_current_user(user_email='test@chromium.org', is_admin=True) | 59 self.mock_current_user(user_email='test@chromium.org', is_admin=True) |
| 53 | 60 |
| 54 def testUpdateAnalysisResultStatusWhenAnalysisIsIncomplete(self): | 61 def testUpdateAnalysisResultStatusWhenAnalysisIsIncomplete(self): |
| 55 success = triage_analysis._UpdateAnalysisResultStatus( | 62 success, _ = triage_analysis._UpdateAnalysisResultStatus( |
| 56 self.master_name, self.builder_name, self.build_number_incomplete, True) | 63 self.master_name, self.builder_name, self.build_number_incomplete, True) |
| 57 self.assertFalse(success) | 64 self.assertFalse(success) |
| 58 analysis = WfAnalysis.Get( | 65 analysis = WfAnalysis.Get( |
| 59 self.master_name, self.builder_name, self.build_number_found) | 66 self.master_name, self.builder_name, self.build_number_found) |
| 60 self.assertIsNone(analysis.result_status) | 67 self.assertIsNone(analysis.result_status) |
| 61 | 68 |
| 62 def testUpdateAnalysisResultStatusWhenFoundAndCorrect(self): | 69 def testUpdateAnalysisResultStatusWhenFoundAndCorrect(self): |
| 63 success = triage_analysis._UpdateAnalysisResultStatus( | 70 success = triage_analysis._UpdateAnalysisResultStatus( |
| 64 self.master_name, self.builder_name, self.build_number_found, True) | 71 self.master_name, self.builder_name, self.build_number_found, True) |
| 65 self.assertTrue(success) | 72 self.assertTrue(success) |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 123 self.assertEquals({'success': False}, response.json_body) | 130 self.assertEquals({'success': False}, response.json_body) |
| 124 | 131 |
| 125 def testSuccessfulTriage(self): | 132 def testSuccessfulTriage(self): |
| 126 build_url = buildbot.CreateBuildUrl( | 133 build_url = buildbot.CreateBuildUrl( |
| 127 self.master_name, self.builder_name, self.build_number_found) | 134 self.master_name, self.builder_name, self.build_number_found) |
| 128 response = self.test_app.get( | 135 response = self.test_app.get( |
| 129 '/triage-analysis', | 136 '/triage-analysis', |
| 130 params={'url': build_url, 'correct': True, 'format': 'json'}) | 137 params={'url': build_url, 'correct': True, 'format': 'json'}) |
| 131 self.assertEquals(200, response.status_int) | 138 self.assertEquals(200, response.status_int) |
| 132 self.assertEquals({'success': True}, response.json_body) | 139 self.assertEquals({'success': True}, response.json_body) |
| 140 |
| 141 def testIncompleteTriage(self): |
| 142 build_url = buildbot.CreateBuildUrl( |
| 143 self.master_name, self.builder_name, self.build_number_incomplete) |
| 144 response = self.test_app.get( |
| 145 '/triage-analysis', |
| 146 params={'url': build_url, 'correct': True, 'format': 'json'}) |
| 147 self.assertEquals(200, response.status_int) |
| 148 self.assertEquals({'success': False}, response.json_body) |
| 149 |
| 150 def testAnalysesMatch(self): |
| 151 analysis_with_empty_failures = WfAnalysis.Create( |
| 152 self.master_name, self.builder_name, 200) |
| 153 analysis_with_empty_failures.result = { |
| 154 'failures': [] |
| 155 } |
| 156 analysis_with_empty_failures.put() |
| 157 |
| 158 analysis_with_no_suspected_cls = WfAnalysis.Create( |
| 159 self.master_name, self.builder_name, 201) |
| 160 analysis_with_no_suspected_cls.result = { |
| 161 'failures': [ |
| 162 { |
| 163 'suspected_cls': [] |
| 164 }, |
| 165 { |
| 166 'suspected_cls': [] |
| 167 }, |
| 168 ] |
| 169 } |
| 170 analysis_with_no_suspected_cls.put() |
| 171 |
| 172 analysis_with_suspected_cls_1 = WfAnalysis.Create( |
| 173 self.master_name, self.builder_name, 202) |
| 174 analysis_with_suspected_cls_1.result = { |
| 175 'failures': [ |
| 176 { |
| 177 'step_name': 'step1', |
| 178 'suspected_cls': [ |
| 179 { |
| 180 'revision': 'rev1', |
| 181 } |
| 182 ], |
| 183 } |
| 184 ] |
| 185 } |
| 186 analysis_with_suspected_cls_1.put() |
| 187 |
| 188 analysis_with_suspected_cls_2 = WfAnalysis.Create( |
| 189 self.master_name, self.builder_name, 203) |
| 190 analysis_with_suspected_cls_2.result = { |
| 191 'failures': [ |
| 192 { |
| 193 'suspected_cls': [], |
| 194 'step_name': 'step2' |
| 195 }, |
| 196 { |
| 197 'suspected_cls': [ |
| 198 { |
| 199 'revision': 'rev2', |
| 200 } |
| 201 ], |
| 202 'step_name': 'step3' |
| 203 } |
| 204 ] |
| 205 } |
| 206 analysis_with_suspected_cls_2.put() |
| 207 |
| 208 analysis_with_suspected_cls_3 = WfAnalysis.Create( |
| 209 self.master_name, self.builder_name, 204) |
| 210 analysis_with_suspected_cls_3.result = { |
| 211 'failures': [ |
| 212 { |
| 213 'suspected_cls': [], |
| 214 'step_name': 'step2', |
| 215 }, |
| 216 { |
| 217 'suspected_cls': [ |
| 218 { |
| 219 'revision': 'rev2', |
| 220 }, |
| 221 { |
| 222 'revision': 'rev3', |
| 223 }, |
| 224 { |
| 225 'revision': 'rev4', |
| 226 } |
| 227 ], |
| 228 'step_name': 'step3', |
| 229 } |
| 230 ] |
| 231 } |
| 232 analysis_with_suspected_cls_3.result_status = result_status.FOUND_UNTRIAGED |
| 233 analysis_with_suspected_cls_3.build_start_time = self.build_start_time |
| 234 analysis_with_suspected_cls_3.put() |
| 235 |
| 236 analysis_with_suspected_cls_4 = WfAnalysis.Create( |
| 237 self.master_name, self.builder_name, 205) |
| 238 analysis_with_suspected_cls_4.result = { |
| 239 'failures': [ |
| 240 { |
| 241 'suspected_cls': [], |
| 242 'step_name': 'step2', |
| 243 }, |
| 244 { |
| 245 'suspected_cls': [ |
| 246 { |
| 247 'revision': 'rev2', |
| 248 }, |
| 249 { |
| 250 'revision': 'rev3', |
| 251 }, |
| 252 { |
| 253 'revision': 'rev4', |
| 254 } |
| 255 ], |
| 256 'step_name': 'step3', |
| 257 } |
| 258 ] |
| 259 } |
| 260 analysis_with_suspected_cls_4.result_status = result_status.FOUND_UNTRIAGED |
| 261 analysis_with_suspected_cls_4.build_start_time = self.build_start_time |
| 262 analysis_with_suspected_cls_4.put() |
| 263 |
| 264 analysis_with_tests_1 = WfAnalysis.Create( |
| 265 self.master_name, self.builder_name, 206) |
| 266 analysis_with_tests_1.result = { |
| 267 'failures': [ |
| 268 { |
| 269 'tests': [ |
| 270 { |
| 271 'test_name': 'super_test_1', |
| 272 'suspected_cls': [ |
| 273 { |
| 274 'revision': 'abc' |
| 275 } |
| 276 ] |
| 277 }, { |
| 278 'test_name': 'super_test_2', |
| 279 'suspected_cls': [ |
| 280 { |
| 281 'revision': 'def' |
| 282 }, |
| 283 { |
| 284 'revision': 'ghi' |
| 285 } |
| 286 ] |
| 287 } |
| 288 ], |
| 289 'step_name': 'step1', |
| 290 'suspected_cls': [ |
| 291 { |
| 292 'revision': 'rev1', |
| 293 } |
| 294 ], |
| 295 } |
| 296 ] |
| 297 } |
| 298 analysis_with_tests_1.put() |
| 299 |
| 300 analysis_with_tests_2 = WfAnalysis.Create( |
| 301 self.master_name, self.builder_name, 207) |
| 302 analysis_with_tests_2.result = { |
| 303 'failures': [ |
| 304 { |
| 305 'tests': [ |
| 306 { |
| 307 'test_name': 'super_test_3', |
| 308 'suspected_cls': [ |
| 309 { |
| 310 'revision': 'ab' |
| 311 }, |
| 312 { |
| 313 'revision': 'cd' |
| 314 }, |
| 315 { |
| 316 'revision': 'ef' |
| 317 } |
| 318 ] |
| 319 } |
| 320 ], |
| 321 'step_name': 'step1', |
| 322 'suspected_cls': [ |
| 323 { |
| 324 'revision': 'rev1', |
| 325 } |
| 326 ], |
| 327 } |
| 328 ] |
| 329 } |
| 330 analysis_with_tests_2.put() |
| 331 |
| 332 # Empty failures list. |
| 333 self.assertFalse(triage_analysis._DoAnalysesMatch( |
| 334 analysis_with_empty_failures, |
| 335 analysis_with_empty_failures)) |
| 336 # Zero culprit-tuples. |
| 337 self.assertFalse(triage_analysis._DoAnalysesMatch( |
| 338 analysis_with_no_suspected_cls, |
| 339 analysis_with_no_suspected_cls)) |
| 340 # Zero culprit-tuples and some culprit-tuples. |
| 341 self.assertFalse(triage_analysis._DoAnalysesMatch( |
| 342 analysis_with_no_suspected_cls, |
| 343 analysis_with_suspected_cls_1)) |
| 344 # Has step-level culprit-tuples, and should detect match. |
| 345 self.assertTrue(triage_analysis._DoAnalysesMatch( |
| 346 analysis_with_suspected_cls_2, |
| 347 analysis_with_suspected_cls_2)) |
| 348 # Two different step-level culprit-tuples, and should fail to match. |
| 349 self.assertFalse(triage_analysis._DoAnalysesMatch( |
| 350 analysis_with_suspected_cls_2, |
| 351 analysis_with_suspected_cls_3)) |
| 352 # Has test-level culprit-tuples, and should detect match. |
| 353 self.assertTrue(triage_analysis._DoAnalysesMatch( |
| 354 analysis_with_tests_1, |
| 355 analysis_with_tests_1)) |
| 356 # Two different test-level culprit-tuples, and should fail to match. |
| 357 self.assertFalse(triage_analysis._DoAnalysesMatch( |
| 358 analysis_with_tests_1, |
| 359 analysis_with_tests_2)) |
| 360 |
| 361 def _createAnalysis(self, build_number, build_start_time): |
| 362 analysis = WfAnalysis.Create( |
| 363 self.master_name, self.builder_name, build_number) |
| 364 analysis.result = { |
| 365 'failures': [ |
| 366 { |
| 367 'suspected_cls': [ |
| 368 { |
| 369 'revision': 'abc', |
| 370 } |
| 371 ], |
| 372 'step_name': 'step_4', |
| 373 } |
| 374 ] |
| 375 } |
| 376 analysis.result_status = result_status.FOUND_UNTRIAGED |
| 377 analysis.build_start_time = build_start_time |
| 378 analysis.status = analysis_status.COMPLETED |
| 379 analysis.put() |
| 380 return analysis |
| 381 |
| 382 def testGetDuplicateAnalysesTooEarly(self): |
| 383 # Two days ago, UTC Noon. |
| 384 original_time = (datetime.utcnow() - timedelta(days=2)).replace( |
| 385 hour=12, minute=0, second=0, microsecond=0) |
| 386 analysis_original = self._createAnalysis(300, original_time) |
| 387 |
| 388 # An earlier time, outside bounds. |
| 389 too_early_time = (original_time - timedelta( |
| 390 hours=triage_analysis.MATCHING_ANALYSIS_HOURS_AGO_START*2)) |
| 391 self._createAnalysis(301, too_early_time) |
| 392 |
| 393 self.assertEquals( |
| 394 len(triage_analysis._GetDuplicateAnalyses(analysis_original)), 0) |
| 395 |
| 396 def testGetDuplicateAnalysesEarlier(self): |
| 397 # Two days ago, UTC Noon. |
| 398 original_time = (datetime.utcnow() - timedelta(days=2)).replace( |
| 399 hour=12, minute=0, second=0, microsecond=0) |
| 400 analysis_original = self._createAnalysis(302, original_time) |
| 401 |
| 402 # An earlier time, within bounds. |
| 403 earlier_time = (original_time - timedelta( |
| 404 hours=triage_analysis.MATCHING_ANALYSIS_HOURS_AGO_START/2)) |
| 405 self._createAnalysis(303, earlier_time) |
| 406 |
| 407 self.assertEquals( |
| 408 len(triage_analysis._GetDuplicateAnalyses(analysis_original)), 1) |
| 409 |
| 410 def testGetDuplicateAnalysesLater(self): |
| 411 # Two days ago, UTC Noon. |
| 412 original_time = (datetime.utcnow() - timedelta(days=2)).replace( |
| 413 hour=12, minute=0, second=0, microsecond=0) |
| 414 analysis_original = self._createAnalysis(304, original_time) |
| 415 |
| 416 # A later time, within bounds. |
| 417 later_time = (original_time + timedelta( |
| 418 hours=triage_analysis.MATCHING_ANALYSIS_HOURS_AGO_START/2)) |
| 419 self._createAnalysis(305, later_time) |
| 420 |
| 421 self.assertEquals( |
| 422 len(triage_analysis._GetDuplicateAnalyses(analysis_original)), 1) |
| 423 |
| 424 def testGetDuplicateAnalysesTooLate(self): |
| 425 # Two days ago, UTC Noon. |
| 426 original_time = (datetime.utcnow() - timedelta(days=2)).replace( |
| 427 hour=12, minute=0, second=0, microsecond=0) |
| 428 analysis_original = self._createAnalysis(306, original_time) |
| 429 |
| 430 # A later time, outside bounds. |
| 431 too_late_time = (original_time + timedelta( |
| 432 hours=triage_analysis.MATCHING_ANALYSIS_HOURS_AGO_START*2)) |
| 433 self._createAnalysis(307, too_late_time) |
| 434 |
| 435 self.assertEquals( |
| 436 len(triage_analysis._GetDuplicateAnalyses(analysis_original)), 0) |
| 437 |
| 438 def testGetDuplicateAnalysesNotToday(self): |
| 439 # Tomorrow, UTC Noon. |
| 440 original_time = (datetime.utcnow() + timedelta(days=1)).replace( |
| 441 hour=12, minute=0, second=0, microsecond=0) |
| 442 analysis_original = self._createAnalysis(308, original_time) |
| 443 |
| 444 # Create another analysis at the same time (also tomorrow). |
| 445 self._createAnalysis(309, original_time) |
| 446 |
| 447 self.assertEquals( |
| 448 len(triage_analysis._GetDuplicateAnalyses(analysis_original)), 0) |
| 449 |
| 450 def testTriageDuplicateResults(self): |
| 451 # Two days ago, UTC Noon. |
| 452 original_time = (datetime.utcnow() - timedelta(days=2)).replace( |
| 453 hour=12, minute=0, second=0, microsecond=0) |
| 454 analysis_original = self._createAnalysis(310, original_time) |
| 455 |
| 456 # Create another analysis at the same time (also two days ago). |
| 457 self._createAnalysis(311, original_time) |
| 458 |
| 459 triage_analysis._TriageDuplicateResults(analysis_original, True) |
| 460 |
| 461 second_analysis = WfAnalysis.Get(self.master_name, self.builder_name, 311) |
| 462 |
| 463 self.assertEquals(result_status.NOT_FOUND_CORRECT, |
| 464 second_analysis.result_status) |
| 465 |
| OLD | NEW |