| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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 import os | 5 import os |
| 6 import re | 6 import re |
| 7 | 7 |
| 8 from google.appengine.ext import testbed | 8 from google.appengine.ext import testbed |
| 9 import webapp2 | 9 import webapp2 |
| 10 import webtest | 10 import webtest |
| 11 | 11 |
| 12 from testing_utils import testing | 12 from testing_utils import testing |
| 13 | 13 |
| 14 from handlers import build_failure | 14 from handlers import build_failure |
| 15 from handlers import handlers_util |
| 16 from handlers import result_status |
| 15 from model.wf_analysis import WfAnalysis | 17 from model.wf_analysis import WfAnalysis |
| 16 from model import wf_analysis_status | 18 from model import wf_analysis_status |
| 17 from waterfall import buildbot | 19 from waterfall import buildbot |
| 18 from waterfall import waterfall_config | 20 from waterfall import waterfall_config |
| 19 | 21 |
| 20 | 22 |
| 21 # Root directory appengine/findit. | 23 # Root directory appengine/findit. |
| 22 ROOT_DIR = os.path.join(os.path.dirname(__file__), | 24 ROOT_DIR = os.path.join(os.path.dirname(__file__), |
| 23 os.path.pardir, os.path.pardir) | 25 os.path.pardir, os.path.pardir) |
| 24 | 26 |
| 27 SAMPLE_TRY_JOB_INFO = { |
| 28 'm/b/119': { |
| 29 'step1 on platform':{ |
| 30 'try_jobs': [ |
| 31 { |
| 32 'ref_name': 'step1', |
| 33 'try_job_key': 'm/b/119', |
| 34 'task_id': 'task1', |
| 35 'task_url': 'url/task1', |
| 36 'status': wf_analysis_status.ANALYZED, |
| 37 'try_job_url': ( |
| 38 'http://build.chromium.org/p/tryserver.chromium.' |
| 39 'linux/builders/linux_variable/builds/121'), |
| 40 'try_job_build_number': 121, |
| 41 'tests': ['test3'], |
| 42 'culprit': {} |
| 43 }, |
| 44 { |
| 45 'ref_name': 'step1', |
| 46 'try_job_key': 'm/b/119', |
| 47 'task_id': 'task1', |
| 48 'task_url': 'url/task1', |
| 49 'status': wf_analysis_status.ANALYZED, |
| 50 'try_job_url': ( |
| 51 'http://build.chromium.org/p/tryserver.chromium.' |
| 52 'linux/builders/linux_variable/builds/121'), |
| 53 'try_job_build_number': 121, |
| 54 'culprit': { |
| 55 'revision': 'rev2', |
| 56 'commit_position': '2', |
| 57 'review_url': 'url_2' |
| 58 }, |
| 59 'tests': ['test2'] |
| 60 }, |
| 61 { |
| 62 'ref_name': 'step1', |
| 63 'try_job_key': 'm/b/119', |
| 64 'status': result_status.FLAKY, |
| 65 'task_id': 'task1', |
| 66 'task_url': 'url/task1', |
| 67 'tests': ['test4'] |
| 68 }, |
| 69 { |
| 70 'ref_name': 'step1', |
| 71 'try_job_key': 'm/b/120', |
| 72 'status': result_status.NO_TRY_JOB_REASON_MAP[ |
| 73 wf_analysis_status.PENDING], |
| 74 'task_id': 'task2', |
| 75 'task_url': 'url/task2', |
| 76 'tests': ['test1'] |
| 77 } |
| 78 ] |
| 79 } |
| 80 }, |
| 81 'm/b/120': { |
| 82 'compile': { |
| 83 'try_jobs': [ |
| 84 { |
| 85 'try_job_key': 'm/b/120', |
| 86 'status': wf_analysis_status.ANALYZED, |
| 87 'try_job_build_number': 120, |
| 88 'try_job_url': ( |
| 89 'http://build.chromium.org/p/tryserver.chromium.' |
| 90 'linux/builders/linux_variable/builds/120'), |
| 91 'culprit': { |
| 92 'revision': 'rev2', |
| 93 'commit_position': '2', |
| 94 'review_url': 'url_2' |
| 95 } |
| 96 } |
| 97 ] |
| 98 } |
| 99 } |
| 100 } |
| 101 |
| 25 | 102 |
| 26 class BuildFailureTest(testing.AppengineTestCase): | 103 class BuildFailureTest(testing.AppengineTestCase): |
| 27 app_module = webapp2.WSGIApplication([ | 104 app_module = webapp2.WSGIApplication([ |
| 28 ('/build-failure', build_failure.BuildFailure), | 105 ('/build-failure', build_failure.BuildFailure), |
| 29 ], debug=True) | 106 ], debug=True) |
| 30 | 107 |
| 31 def setUp(self): | 108 def setUp(self): |
| 32 super(BuildFailureTest, self).setUp() | 109 super(BuildFailureTest, self).setUp() |
| 33 | 110 |
| 34 # Setup clean task queues. | 111 # Setup clean task queues. |
| 35 self.testbed.init_taskqueue_stub(root_path=ROOT_DIR) | 112 self.testbed.init_taskqueue_stub(root_path=ROOT_DIR) |
| 36 self.taskqueue_stub = self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME) | 113 self.taskqueue_stub = self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME) |
| 37 for queue in self.taskqueue_stub.GetQueues(): | 114 for queue in self.taskqueue_stub.GetQueues(): |
| 38 self.taskqueue_stub.FlushQueue(queue['name']) | 115 self.taskqueue_stub.FlushQueue(queue['name']) |
| 39 | 116 |
| 117 def MockedGetAllTryJobResults(master_name, builder_name, build_number): |
| 118 build_key = '%s/%s/%d' % (master_name, builder_name, build_number) |
| 119 return SAMPLE_TRY_JOB_INFO.get(build_key, None) |
| 120 self.mock(handlers_util, 'GetAllTryJobResults', MockedGetAllTryJobResults) |
| 121 |
| 40 def testGetTriageHistoryWhenUserIsNotAdmin(self): | 122 def testGetTriageHistoryWhenUserIsNotAdmin(self): |
| 41 analysis = WfAnalysis.Create('m', 'b', 1) | 123 analysis = WfAnalysis.Create('m', 'b', 1) |
| 42 analysis.status = wf_analysis_status.ANALYZED | 124 analysis.status = wf_analysis_status.ANALYZED |
| 43 analysis.triage_history = [ | 125 analysis.triage_history = [ |
| 44 { | 126 { |
| 45 'triage_timestamp': 1438380761, | 127 'triage_timestamp': 1438380761, |
| 46 'user_name': 'test', | 128 'user_name': 'test', |
| 47 'result_status': 'dummy status', | 129 'result_status': 'dummy status', |
| 48 'version': 'dummy version', | 130 'version': 'dummy version', |
| 49 } | 131 } |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 138 master_name, builder_name, build_number) | 220 master_name, builder_name, build_number) |
| 139 | 221 |
| 140 def MockMasterIsSupported(*_): | 222 def MockMasterIsSupported(*_): |
| 141 return True | 223 return True |
| 142 self.mock(waterfall_config, 'MasterIsSupported', MockMasterIsSupported) | 224 self.mock(waterfall_config, 'MasterIsSupported', MockMasterIsSupported) |
| 143 | 225 |
| 144 response = self.test_app.get('/build-failure', params={'url': build_url}) | 226 response = self.test_app.get('/build-failure', params={'url': build_url}) |
| 145 self.assertEquals(200, response.status_int) | 227 self.assertEquals(200, response.status_int) |
| 146 | 228 |
| 147 self.assertEqual(1, len(self.taskqueue_stub.get_filtered_tasks())) | 229 self.assertEqual(1, len(self.taskqueue_stub.get_filtered_tasks())) |
| 230 |
| 231 def testGetOrganizedAnalysisResultBySuspectedCLNonSwarming(self): |
| 232 analysis_result = { |
| 233 'failures': [ |
| 234 { |
| 235 'step_name': 'a', |
| 236 'first_failure': 98, |
| 237 'last_pass': None, |
| 238 'supported': True, |
| 239 'suspected_cls': [ |
| 240 { |
| 241 'build_number': 99, |
| 242 'repo_name': 'chromium', |
| 243 'revision': 'r99_2', |
| 244 'commit_position': None, |
| 245 'url': None, |
| 246 'score': 2, |
| 247 'hints': { |
| 248 'modified f99_2.cc (and it was in log)': 2, |
| 249 }, |
| 250 } |
| 251 ], |
| 252 } |
| 253 ] |
| 254 } |
| 255 |
| 256 result = build_failure._GetOrganizedAnalysisResultBySuspectedCL( |
| 257 analysis_result) |
| 258 |
| 259 expected_result = { |
| 260 'a': [ |
| 261 { |
| 262 'first_failure': 98, |
| 263 'last_pass': None, |
| 264 'supported': True, |
| 265 'suspected_cls': [ |
| 266 { |
| 267 'build_number': 99, |
| 268 'repo_name': 'chromium', |
| 269 'revision': 'r99_2', |
| 270 'commit_position': None, |
| 271 'url': None, |
| 272 'score': 2, |
| 273 'hints': { |
| 274 'modified f99_2.cc (and it was in log)': 2, |
| 275 }, |
| 276 } |
| 277 ], |
| 278 'tests': [] |
| 279 } |
| 280 ] |
| 281 } |
| 282 self.assertEqual(expected_result, result) |
| 283 |
| 284 def testGetOrganizedAnalysisResultBySuspectedCLSwarming(self): |
| 285 analysis_result = { |
| 286 'failures': [ |
| 287 { |
| 288 'step_name': 'b', |
| 289 'first_failure': 98, |
| 290 'last_pass': 96, |
| 291 'supported': True, |
| 292 'suspected_cls': [ |
| 293 { |
| 294 'build_number': 98, |
| 295 'repo_name': 'chromium', |
| 296 'revision': 'r98_1', |
| 297 'commit_position': None, |
| 298 'url': None, |
| 299 'score': 4, |
| 300 'hints': { |
| 301 'modified f98.cc[123, 456] (and it was in log)': 4, |
| 302 }, |
| 303 } |
| 304 ], |
| 305 'tests': [ |
| 306 { |
| 307 'test_name': 'Unittest2.Subtest1', |
| 308 'first_failure': 98, |
| 309 'last_pass': 97, |
| 310 'suspected_cls': [ |
| 311 { |
| 312 'build_number': 98, |
| 313 'repo_name': 'chromium', |
| 314 'revision': 'r98_1', |
| 315 'commit_position': None, |
| 316 'url': None, |
| 317 'score': 4, |
| 318 'hints': { |
| 319 ('modified f98.cc[123] ' |
| 320 '(and it was in log)'): 4, |
| 321 }, |
| 322 } |
| 323 ] |
| 324 }, |
| 325 { |
| 326 'test_name': 'Unittest3.Subtest2', |
| 327 'first_failure': 98, |
| 328 'last_pass': 96, |
| 329 'suspected_cls': [ |
| 330 { |
| 331 'build_number': 98, |
| 332 'repo_name': 'chromium', |
| 333 'revision': 'r98_1', |
| 334 'commit_position': None, |
| 335 'url': None, |
| 336 'score': 4, |
| 337 'hints': { |
| 338 ('modified f98.cc[456] ' |
| 339 '(and it was in log)'): 4, |
| 340 }, |
| 341 } |
| 342 ] |
| 343 }, |
| 344 { |
| 345 'test_name': 'Unittest3.Subtest3', |
| 346 'first_failure': 98, |
| 347 'last_pass': 96, |
| 348 'suspected_cls': [] |
| 349 } |
| 350 ] |
| 351 } |
| 352 ] |
| 353 } |
| 354 |
| 355 result = build_failure._GetOrganizedAnalysisResultBySuspectedCL( |
| 356 analysis_result) |
| 357 |
| 358 expected_result = { |
| 359 'b': [ |
| 360 { |
| 361 'supported': True, |
| 362 'first_failure': 98, |
| 363 'last_pass': 97, |
| 364 'suspected_cls': [ |
| 365 { |
| 366 'build_number': 98, |
| 367 'repo_name': 'chromium', |
| 368 'revision': 'r98_1', |
| 369 'commit_position': None, |
| 370 'url': None, |
| 371 'score': 4, |
| 372 'hints': { |
| 373 'modified f98.cc[123, 456] (and it was in log)': 4, |
| 374 }, |
| 375 } |
| 376 ], |
| 377 'tests': ['Unittest2.Subtest1', 'Unittest3.Subtest2'] |
| 378 }, |
| 379 { |
| 380 'first_failure': 98, |
| 381 'last_pass': 96, |
| 382 'supported': True, |
| 383 'suspected_cls': [], |
| 384 'tests': ['Unittest3.Subtest3'] |
| 385 } |
| 386 ] |
| 387 } |
| 388 self.assertEqual(expected_result, result) |
| 389 |
| 390 def testGetAnalysisResultWithTryJobInfo(self): |
| 391 master_name = 'm' |
| 392 builder_name = 'b' |
| 393 build_number = 119 |
| 394 organized_results = { |
| 395 'step1 on platform': [ |
| 396 { |
| 397 'supported': True, |
| 398 'first_failure': 119, |
| 399 'last_pass': 118, |
| 400 'suspected_cls': [ |
| 401 { |
| 402 'build_number': 119, |
| 403 'repo_name': 'chromium', |
| 404 'revision': 'r98_1', |
| 405 'commit_position': None, |
| 406 'url': None, |
| 407 'score': 4, |
| 408 'hints': { |
| 409 'modified f98.cc[123, 456] (and it was in log)': 4, |
| 410 }, |
| 411 } |
| 412 ], |
| 413 'tests': ['test2', 'test3'] |
| 414 }, |
| 415 { |
| 416 'first_failure': 119, |
| 417 'last_pass': 118, |
| 418 'supported': True, |
| 419 'suspected_cls': [], |
| 420 'tests': ['test4'] |
| 421 }, |
| 422 { |
| 423 'first_failure': 120, |
| 424 'last_pass': 119, |
| 425 'supported': True, |
| 426 'suspected_cls': [], |
| 427 'tests': ['test1'] |
| 428 } |
| 429 ] |
| 430 } |
| 431 |
| 432 updated_result = build_failure._GetAnalysisResultWithTryJobInfo( |
| 433 organized_results, master_name, builder_name, build_number) |
| 434 |
| 435 expected_result = { |
| 436 'step1 on platform':{ |
| 437 'results': { |
| 438 'reliable_failures': [ |
| 439 { |
| 440 'try_job':{ |
| 441 'ref_name': 'step1', |
| 442 'try_job_key': 'm/b/119', |
| 443 'task_id': 'task1', |
| 444 'task_url': 'url/task1', |
| 445 'status': wf_analysis_status.ANALYZED, |
| 446 'try_job_url': ( |
| 447 'http://build.chromium.org/p/tryserver.chromium' |
| 448 '.linux/builders/linux_variable/builds/121'), |
| 449 'try_job_build_number': 121, |
| 450 'tests': ['test3'], |
| 451 'culprit': {} |
| 452 }, |
| 453 'heuristic_analysis': { |
| 454 'suspected_cls': [ |
| 455 { |
| 456 'build_number': 119, |
| 457 'repo_name': 'chromium', |
| 458 'revision': 'r98_1', |
| 459 'commit_position': None, |
| 460 'url': None, |
| 461 'score': 4, |
| 462 'hints': { |
| 463 ('modified f98.cc[123, 456] ' |
| 464 '(and it was in log)'): 4, |
| 465 }, |
| 466 } |
| 467 ] |
| 468 }, |
| 469 'tests': ['test3'], |
| 470 'first_failure': 119, |
| 471 'last_pass': 118, |
| 472 'supported': True |
| 473 }, |
| 474 { |
| 475 'try_job':{ |
| 476 'ref_name': 'step1', |
| 477 'try_job_key': 'm/b/119', |
| 478 'task_id': 'task1', |
| 479 'task_url': 'url/task1', |
| 480 'status': wf_analysis_status.ANALYZED, |
| 481 'try_job_url': ( |
| 482 'http://build.chromium.org/p/tryserver.chromium' |
| 483 '.linux/builders/linux_variable/builds/121'), |
| 484 'try_job_build_number': 121, |
| 485 'culprit': { |
| 486 'revision': 'rev2', |
| 487 'commit_position': '2', |
| 488 'review_url': 'url_2' |
| 489 }, |
| 490 'tests': ['test2'] |
| 491 }, |
| 492 'heuristic_analysis': { |
| 493 'suspected_cls': [ |
| 494 { |
| 495 'build_number': 119, |
| 496 'repo_name': 'chromium', |
| 497 'revision': 'r98_1', |
| 498 'commit_position': None, |
| 499 'url': None, |
| 500 'score': 4, |
| 501 'hints': { |
| 502 ('modified f98.cc[123, 456] ' |
| 503 '(and it was in log)'): 4, |
| 504 }, |
| 505 } |
| 506 ] |
| 507 }, |
| 508 'tests': ['test2'], |
| 509 'first_failure': 119, |
| 510 'last_pass': 118, |
| 511 'supported': True |
| 512 } |
| 513 ], |
| 514 'flaky_failures': [ |
| 515 { |
| 516 'try_job':{ |
| 517 'ref_name': 'step1', |
| 518 'try_job_key': 'm/b/119', |
| 519 'status': result_status.FLAKY, |
| 520 'task_id': 'task1', |
| 521 'task_url': 'url/task1', |
| 522 'tests': ['test4'] |
| 523 }, |
| 524 'heuristic_analysis': { |
| 525 'suspected_cls': [] |
| 526 }, |
| 527 'tests': ['test4'], |
| 528 'first_failure': 119, |
| 529 'last_pass': 118, |
| 530 'supported': True |
| 531 } |
| 532 ], |
| 533 'unclassified_failures': [ |
| 534 { |
| 535 'try_job':{ |
| 536 'ref_name': 'step1', |
| 537 'try_job_key': 'm/b/120', |
| 538 'status': result_status.NO_TRY_JOB_REASON_MAP[ |
| 539 wf_analysis_status.PENDING], |
| 540 'task_id': 'task2', |
| 541 'task_url': 'url/task2', |
| 542 'tests': ['test1'] |
| 543 }, |
| 544 'heuristic_analysis': { |
| 545 'suspected_cls': [] |
| 546 }, |
| 547 'tests': ['test1'], |
| 548 'first_failure': 120, |
| 549 'last_pass': 119, |
| 550 'supported': True |
| 551 } |
| 552 ] |
| 553 } |
| 554 } |
| 555 } |
| 556 |
| 557 self.assertEqual(expected_result, updated_result) |
| 558 |
| 559 def testGetAnalysisResultWithTryJobInfoNoTryJobInfo(self): |
| 560 organized_results = { |
| 561 'step1 on platform':{} |
| 562 } |
| 563 result = build_failure._GetAnalysisResultWithTryJobInfo( |
| 564 organized_results, 'n', 'b', 120) |
| 565 self.assertEqual({}, result) |
| 566 |
| 567 def testGetAnalysisResultWithTryJobInfoCompile(self): |
| 568 organized_results = { |
| 569 'compile': [ |
| 570 { |
| 571 'first_failure': 120, |
| 572 'last_pass': 119, |
| 573 'supported': True, |
| 574 'suspected_cls': [ |
| 575 { |
| 576 'build_number': 120, |
| 577 'repo_name': 'chromium', |
| 578 'revision': 'rev2', |
| 579 'commit_position': None, |
| 580 'url': None, |
| 581 'score': 2, |
| 582 'hints': { |
| 583 'modified f99_2.cc (and it was in log)': 2, |
| 584 }, |
| 585 } |
| 586 ], |
| 587 'tests': [] |
| 588 } |
| 589 ] |
| 590 } |
| 591 result = build_failure._GetAnalysisResultWithTryJobInfo( |
| 592 organized_results, 'm', 'b', 120) |
| 593 |
| 594 expected_result = { |
| 595 'compile':{ |
| 596 'results': { |
| 597 'reliable_failures': [ |
| 598 { |
| 599 'try_job': { |
| 600 'try_job_key': 'm/b/120', |
| 601 'status': wf_analysis_status.ANALYZED, |
| 602 'try_job_build_number': 120, |
| 603 'try_job_url': ( |
| 604 'http://build.chromium.org/p/tryserver.chromium' |
| 605 '.linux/builders/linux_variable/builds/120'), |
| 606 'culprit': { |
| 607 'revision': 'rev2', |
| 608 'commit_position': '2', |
| 609 'review_url': 'url_2' |
| 610 } |
| 611 }, |
| 612 'heuristic_analysis': { |
| 613 'suspected_cls': [ |
| 614 { |
| 615 'build_number': 120, |
| 616 'repo_name': 'chromium', |
| 617 'revision': 'rev2', |
| 618 'commit_position': None, |
| 619 'url': None, |
| 620 'score': 2, |
| 621 'hints': {('modified f99_2.cc ' |
| 622 '(and it was in log)'): 2 |
| 623 }, |
| 624 } |
| 625 ] |
| 626 }, |
| 627 'tests': [], |
| 628 'first_failure': 120, |
| 629 'last_pass': 119, |
| 630 'supported': True |
| 631 } |
| 632 ] |
| 633 } |
| 634 } |
| 635 } |
| 636 self.assertEqual(expected_result, result) |
| OLD | NEW |