| 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 import json | 5 import json |
| 6 import mock | 6 import mock |
| 7 import re | 7 import re |
| 8 | 8 |
| 9 import endpoints | |
| 10 from google.appengine.api import taskqueue | 9 from google.appengine.api import taskqueue |
| 11 import webtest | 10 import webtest |
| 12 | 11 |
| 13 from testing_utils import testing | 12 from testing_utils import testing |
| 14 | 13 |
| 15 from common.waterfall import failure_type | 14 from common.waterfall import failure_type |
| 16 import findit_api | 15 import findit_api |
| 17 from findit_api import FindItApi | 16 from findit_api import FindItApi |
| 17 from model import analysis_approach_type |
| 18 from model.base_build_model import BaseBuildModel |
| 18 from model.wf_analysis import WfAnalysis | 19 from model.wf_analysis import WfAnalysis |
| 20 from model.wf_suspected_cl import WfSuspectedCL |
| 19 from model.wf_swarming_task import WfSwarmingTask | 21 from model.wf_swarming_task import WfSwarmingTask |
| 20 from model.wf_try_job import WfTryJob | 22 from model.wf_try_job import WfTryJob |
| 21 from model import analysis_status | 23 from model import analysis_status |
| 24 from waterfall import suspected_cl_util |
| 22 from waterfall import waterfall_config | 25 from waterfall import waterfall_config |
| 23 | 26 |
| 24 | 27 |
| 25 class FinditApiTest(testing.EndpointsTestCase): | 28 class FinditApiTest(testing.EndpointsTestCase): |
| 26 api_service_cls = FindItApi | 29 api_service_cls = FindItApi |
| 27 | 30 |
| 28 def setUp(self): | 31 def setUp(self): |
| 29 super(FinditApiTest, self).setUp() | 32 super(FinditApiTest, self).setUp() |
| 30 self.taskqueue_requests = [] | 33 self.taskqueue_requests = [] |
| 31 def Mocked_taskqueue_add(**kwargs): | 34 def Mocked_taskqueue_add(**kwargs): |
| (...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 212 'builder_name': builder_name, | 215 'builder_name': builder_name, |
| 213 'build_number': build_number, | 216 'build_number': build_number, |
| 214 'step_name': 'a', | 217 'step_name': 'a', |
| 215 'is_sub_test': False, | 218 'is_sub_test': False, |
| 216 'first_known_failed_build_number': 23, | 219 'first_known_failed_build_number': 23, |
| 217 'suspected_cls': [ | 220 'suspected_cls': [ |
| 218 { | 221 { |
| 219 'repo_name': 'chromium', | 222 'repo_name': 'chromium', |
| 220 'revision': 'git_hash', | 223 'revision': 'git_hash', |
| 221 'commit_position': 123, | 224 'commit_position': 123, |
| 225 'analysis_approach': 'HEURISTIC' |
| 222 } | 226 } |
| 223 ], | 227 ], |
| 224 'analysis_approach': 'HEURISTIC', | 228 'analysis_approach': 'HEURISTIC', |
| 229 'try_job_status': 'FINISHED', |
| 230 'is_flaky_test': False |
| 225 }, | 231 }, |
| 226 ] | 232 ] |
| 227 | 233 |
| 228 analysis = WfAnalysis.Create(master_name, builder_name, build_number) | 234 analysis = WfAnalysis.Create(master_name, builder_name, build_number) |
| 229 analysis.status = analysis_status.RUNNING | 235 analysis.status = analysis_status.RUNNING |
| 230 analysis.result = analysis_result | 236 analysis.result = analysis_result |
| 231 analysis.put() | 237 analysis.put() |
| 232 | 238 |
| 233 response = self.call_api('AnalyzeBuildFailures', body=builds) | 239 response = self.call_api('AnalyzeBuildFailures', body=builds) |
| 234 self.assertEqual(200, response.status_int) | 240 self.assertEqual(200, response.status_int) |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 331 'builder_name': builder_name, | 337 'builder_name': builder_name, |
| 332 'build_number': build_number, | 338 'build_number': build_number, |
| 333 'step_name': 'test', | 339 'step_name': 'test', |
| 334 'is_sub_test': False, | 340 'is_sub_test': False, |
| 335 'first_known_failed_build_number': 3, | 341 'first_known_failed_build_number': 3, |
| 336 'suspected_cls': [ | 342 'suspected_cls': [ |
| 337 { | 343 { |
| 338 'repo_name': 'chromium', | 344 'repo_name': 'chromium', |
| 339 'revision': 'git_hash1', | 345 'revision': 'git_hash1', |
| 340 'commit_position': 234, | 346 'commit_position': 234, |
| 347 'analysis_approach': 'HEURISTIC' |
| 341 }, | 348 }, |
| 342 { | 349 { |
| 343 'repo_name': 'chromium', | 350 'repo_name': 'chromium', |
| 344 'revision': 'git_hash2', | 351 'revision': 'git_hash2', |
| 345 'commit_position': 288, | 352 'commit_position': 288, |
| 353 'analysis_approach': 'HEURISTIC' |
| 346 } | 354 } |
| 347 ], | 355 ], |
| 348 'analysis_approach': 'HEURISTIC', | 356 'analysis_approach': 'HEURISTIC', |
| 357 'is_flaky_test': False, |
| 358 'try_job_status': 'FINISHED' |
| 349 } | 359 } |
| 350 ] | 360 ] |
| 351 | 361 |
| 352 self._MockMasterIsSupported(supported=True) | 362 self._MockMasterIsSupported(supported=True) |
| 353 | 363 |
| 354 response = self.call_api('AnalyzeBuildFailures', body=builds) | 364 response = self.call_api('AnalyzeBuildFailures', body=builds) |
| 355 self.assertEqual(200, response.status_int) | 365 self.assertEqual(200, response.status_int) |
| 356 self.assertEqual(expected_results, response.json_body.get('results')) | 366 self.assertEqual(expected_results, response.json_body.get('results')) |
| 357 | 367 |
| 358 def testTryJobResultReturnedForCompileFailure(self): | 368 def testTryJobResultReturnedForCompileFailure(self): |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 422 'builder_name': builder_name, | 432 'builder_name': builder_name, |
| 423 'build_number': build_number, | 433 'build_number': build_number, |
| 424 'step_name': 'compile', | 434 'step_name': 'compile', |
| 425 'is_sub_test': False, | 435 'is_sub_test': False, |
| 426 'first_known_failed_build_number': 3, | 436 'first_known_failed_build_number': 3, |
| 427 'suspected_cls': [ | 437 'suspected_cls': [ |
| 428 { | 438 { |
| 429 'repo_name': 'chromium', | 439 'repo_name': 'chromium', |
| 430 'revision': 'r3', | 440 'revision': 'r3', |
| 431 'commit_position': 3, | 441 'commit_position': 3, |
| 442 'analysis_approach': 'TRY_JOB' |
| 432 }, | 443 }, |
| 433 ], | 444 ], |
| 434 'analysis_approach': 'TRY_JOB', | 445 'analysis_approach': 'TRY_JOB', |
| 446 'is_flaky_test': False, |
| 447 'try_job_status': 'FINISHED' |
| 435 } | 448 } |
| 436 ] | 449 ] |
| 437 | 450 |
| 438 self._MockMasterIsSupported(supported=True) | 451 self._MockMasterIsSupported(supported=True) |
| 439 | 452 |
| 440 response = self.call_api('AnalyzeBuildFailures', body=builds) | 453 response = self.call_api('AnalyzeBuildFailures', body=builds) |
| 441 self.assertEqual(200, response.status_int) | 454 self.assertEqual(200, response.status_int) |
| 442 self.assertEqual(expected_results, response.json_body.get('results')) | 455 self.assertEqual(expected_results, response.json_body.get('results')) |
| 443 | 456 |
| 444 def testTestLevelResultIsReturned(self): | 457 def testTryJobIsRunning(self): |
| 445 master_name = 'm' | 458 master_name = 'm' |
| 446 builder_name = 'b' | 459 builder_name = 'b' |
| 447 build_number = 5 | 460 build_number = 5 |
| 461 |
| 462 master_url = 'https://build.chromium.org/p/%s' % master_name |
| 463 builds = { |
| 464 'builds': [ |
| 465 { |
| 466 'master_url': master_url, |
| 467 'builder_name': builder_name, |
| 468 'build_number': build_number |
| 469 } |
| 470 ] |
| 471 } |
| 472 |
| 473 try_job = WfTryJob.Create(master_name, builder_name, 3) |
| 474 try_job.status = analysis_status.RUNNING |
| 475 try_job.put() |
| 476 |
| 477 analysis = WfAnalysis.Create(master_name, builder_name, build_number) |
| 478 analysis.status = analysis_status.COMPLETED |
| 479 analysis.build_failure_type = failure_type.COMPILE |
| 480 analysis.failure_result_map = { |
| 481 'compile': '/'.join([master_name, builder_name, '3']), |
| 482 } |
| 483 analysis.result = { |
| 484 'failures': [ |
| 485 { |
| 486 'step_name': 'compile', |
| 487 'first_failure': 3, |
| 488 'last_pass': 1, |
| 489 'suspected_cls': [ |
| 490 { |
| 491 'build_number': 3, |
| 492 'repo_name': 'chromium', |
| 493 'revision': 'git_hash2', |
| 494 'commit_position': 288, |
| 495 'score': 1, |
| 496 'hints': { |
| 497 'modify d/e/f.cc': 1, |
| 498 } |
| 499 } |
| 500 ] |
| 501 } |
| 502 ] |
| 503 } |
| 504 analysis.put() |
| 505 |
| 506 expected_results = [ |
| 507 { |
| 508 'master_url': master_url, |
| 509 'builder_name': builder_name, |
| 510 'build_number': build_number, |
| 511 'step_name': 'compile', |
| 512 'is_sub_test': False, |
| 513 'first_known_failed_build_number': 3, |
| 514 'suspected_cls': [ |
| 515 { |
| 516 'repo_name': 'chromium', |
| 517 'revision': 'git_hash2', |
| 518 'commit_position': 288, |
| 519 'analysis_approach': 'HEURISTIC' |
| 520 }, |
| 521 ], |
| 522 'analysis_approach': 'HEURISTIC', |
| 523 'is_flaky_test': False, |
| 524 'try_job_status': 'RUNNING' |
| 525 } |
| 526 ] |
| 527 |
| 528 self._MockMasterIsSupported(supported=True) |
| 529 |
| 530 response = self.call_api('AnalyzeBuildFailures', body=builds) |
| 531 self.assertEqual(200, response.status_int) |
| 532 self.assertEqual(expected_results, response.json_body.get('results')) |
| 533 |
| 534 def testTestIsFlaky(self): |
| 535 master_name = 'm' |
| 536 builder_name = 'b' |
| 537 build_number = 5 |
| 538 |
| 539 master_url = 'https://build.chromium.org/p/%s' % master_name |
| 540 builds = { |
| 541 'builds': [ |
| 542 { |
| 543 'master_url': master_url, |
| 544 'builder_name': builder_name, |
| 545 'build_number': build_number |
| 546 } |
| 547 ] |
| 548 } |
| 549 |
| 550 task = WfSwarmingTask.Create(master_name, builder_name, 3, 'b on platform') |
| 551 task.tests_statuses = { |
| 552 'Unittest3.Subtest1': { |
| 553 'total_run': 4, |
| 554 'SUCCESS': 2, |
| 555 'FAILURE': 2 |
| 556 } |
| 557 } |
| 558 task.put() |
| 559 |
| 560 analysis = WfAnalysis.Create(master_name, builder_name, build_number) |
| 561 analysis.status = analysis_status.COMPLETED |
| 562 analysis.failure_result_map = { |
| 563 'b on platform': { |
| 564 'Unittest3.Subtest1': '/'.join([master_name, builder_name, '3']), |
| 565 }, |
| 566 } |
| 567 analysis.result = { |
| 568 'failures': [ |
| 569 { |
| 570 'step_name': 'b on platform', |
| 571 'first_failure': 3, |
| 572 'last_pass': 2, |
| 573 'suspected_cls': [], |
| 574 'tests': [ |
| 575 { |
| 576 'test_name': 'Unittest3.Subtest1', |
| 577 'first_failure': 3, |
| 578 'last_pass': 2, |
| 579 'suspected_cls': [] |
| 580 } |
| 581 ] |
| 582 } |
| 583 ] |
| 584 } |
| 585 analysis.put() |
| 586 |
| 587 expected_results = [ |
| 588 { |
| 589 'master_url': master_url, |
| 590 'builder_name': builder_name, |
| 591 'build_number': build_number, |
| 592 'step_name': 'b on platform', |
| 593 'is_sub_test': True, |
| 594 'test_name': 'Unittest3.Subtest1', |
| 595 'first_known_failed_build_number': 3, |
| 596 'analysis_approach': 'HEURISTIC', |
| 597 'is_flaky_test': True, |
| 598 'try_job_status': 'FINISHED' |
| 599 } |
| 600 ] |
| 601 |
| 602 self._MockMasterIsSupported(supported=True) |
| 603 |
| 604 response = self.call_api('AnalyzeBuildFailures', body=builds) |
| 605 self.assertEqual(200, response.status_int) |
| 606 self.assertEqual(expected_results, response.json_body.get('results')) |
| 607 |
| 608 @mock.patch.object(suspected_cl_util, 'GetSuspectedCLConfidenceScore') |
| 609 def testTestLevelResultIsReturned(self, mock_fn): |
| 610 master_name = 'm' |
| 611 builder_name = 'b' |
| 612 build_number = 5 |
| 448 | 613 |
| 449 master_url = 'https://build.chromium.org/p/%s' % master_name | 614 master_url = 'https://build.chromium.org/p/%s' % master_name |
| 450 builds = { | 615 builds = { |
| 451 'builds': [ | 616 'builds': [ |
| 452 { | 617 { |
| 453 'master_url': master_url, | 618 'master_url': master_url, |
| 454 'builder_name': builder_name, | 619 'builder_name': builder_name, |
| 455 'build_number': build_number | 620 'build_number': build_number |
| 456 } | 621 } |
| 457 ] | 622 ] |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 586 'first_failure': 4, | 751 'first_failure': 4, |
| 587 'last_pass': 2, | 752 'last_pass': 2, |
| 588 'suspected_cls': [] | 753 'suspected_cls': [] |
| 589 } | 754 } |
| 590 ] | 755 ] |
| 591 } | 756 } |
| 592 ] | 757 ] |
| 593 } | 758 } |
| 594 analysis.put() | 759 analysis.put() |
| 595 | 760 |
| 761 suspected_cl_42 = WfSuspectedCL.Create('chromium', 'r4_2', 42) |
| 762 suspected_cl_42.builds = { |
| 763 BaseBuildModel.CreateBuildId(master_name, builder_name, 4): { |
| 764 'approaches': [analysis_approach_type.TRY_JOB] |
| 765 } |
| 766 } |
| 767 suspected_cl_42.put() |
| 768 |
| 769 suspected_cl_21 = WfSuspectedCL.Create('chromium', 'r2_1', None) |
| 770 suspected_cl_21.builds = { |
| 771 BaseBuildModel.CreateBuildId(master_name, builder_name, build_number): { |
| 772 'approaches': [analysis_approach_type.HEURISTIC], |
| 773 'top_score': 5 |
| 774 } |
| 775 } |
| 776 suspected_cl_21.put() |
| 777 |
| 778 def confidence_side_effect(_, build_info): |
| 779 if build_info.get('top_score'): |
| 780 return 90 |
| 781 return 98 |
| 782 |
| 783 mock_fn.side_effect = confidence_side_effect |
| 784 |
| 596 expected_results = [ | 785 expected_results = [ |
| 597 { | 786 { |
| 598 'master_url': master_url, | 787 'master_url': master_url, |
| 599 'builder_name': builder_name, | 788 'builder_name': builder_name, |
| 600 'build_number': build_number, | 789 'build_number': build_number, |
| 601 'step_name': 'a', | 790 'step_name': 'a', |
| 602 'is_sub_test': False, | 791 'is_sub_test': False, |
| 603 'first_known_failed_build_number': 4, | 792 'first_known_failed_build_number': 4, |
| 604 'suspected_cls': [ | 793 'suspected_cls': [ |
| 605 { | 794 { |
| 606 'repo_name': 'chromium', | 795 'repo_name': 'chromium', |
| 607 'revision': 'r4_2', | 796 'revision': 'r4_2', |
| 608 'commit_position': 42, | 797 'commit_position': 42, |
| 798 'confidence': 98, |
| 799 'analysis_approach': 'TRY_JOB' |
| 609 } | 800 } |
| 610 ], | 801 ], |
| 611 'analysis_approach': 'TRY_JOB', | 802 'analysis_approach': 'TRY_JOB', |
| 803 'is_flaky_test': False, |
| 804 'try_job_status': 'FINISHED' |
| 612 }, | 805 }, |
| 613 { | 806 { |
| 614 'master_url': master_url, | 807 'master_url': master_url, |
| 615 'builder_name': builder_name, | 808 'builder_name': builder_name, |
| 616 'build_number': build_number, | 809 'build_number': build_number, |
| 617 'step_name': 'b on platform', | 810 'step_name': 'b on platform', |
| 618 'is_sub_test': True, | 811 'is_sub_test': True, |
| 619 'test_name': 'Unittest1.Subtest1', | 812 'test_name': 'Unittest1.Subtest1', |
| 620 'first_known_failed_build_number': 3, | 813 'first_known_failed_build_number': 3, |
| 621 'suspected_cls': [ | 814 'suspected_cls': [ |
| 622 { | 815 { |
| 623 'repo_name': 'chromium', | 816 'repo_name': 'chromium', |
| 624 'revision': 'r2_1', | 817 'revision': 'r2_1', |
| 818 'confidence': 90, |
| 819 'analysis_approach': 'HEURISTIC' |
| 625 } | 820 } |
| 626 ], | 821 ], |
| 627 'analysis_approach': 'HEURISTIC', | 822 'analysis_approach': 'HEURISTIC', |
| 823 'is_flaky_test': False, |
| 824 'try_job_status': 'FINISHED' |
| 628 }, | 825 }, |
| 629 { | 826 { |
| 630 'master_url': master_url, | 827 'master_url': master_url, |
| 631 'builder_name': builder_name, | 828 'builder_name': builder_name, |
| 632 'build_number': build_number, | 829 'build_number': build_number, |
| 633 'step_name': 'b on platform', | 830 'step_name': 'b on platform', |
| 634 'is_sub_test': True, | 831 'is_sub_test': True, |
| 635 'test_name': 'Unittest2.Subtest1', | 832 'test_name': 'Unittest2.Subtest1', |
| 636 'first_known_failed_build_number': 4, | 833 'first_known_failed_build_number': 4, |
| 637 'suspected_cls': [ | 834 'suspected_cls': [ |
| 638 { | 835 { |
| 639 'repo_name': 'chromium', | 836 'repo_name': 'chromium', |
| 640 'revision': 'r2_1', | 837 'revision': 'r2_1', |
| 838 'confidence': 90, |
| 839 'analysis_approach': 'HEURISTIC' |
| 641 } | 840 } |
| 642 ], | 841 ], |
| 643 'analysis_approach': 'HEURISTIC', | 842 'analysis_approach': 'HEURISTIC', |
| 843 'is_flaky_test': False, |
| 844 'try_job_status': 'FINISHED' |
| 644 }, | 845 }, |
| 645 { | 846 { |
| 646 'master_url': master_url, | 847 'master_url': master_url, |
| 647 'builder_name': builder_name, | 848 'builder_name': builder_name, |
| 648 'build_number': build_number, | 849 'build_number': build_number, |
| 649 'step_name': 'b on platform', | 850 'step_name': 'b on platform', |
| 650 'is_sub_test': True, | 851 'is_sub_test': True, |
| 651 'test_name': 'Unittest3.Subtest1', | 852 'test_name': 'Unittest3.Subtest1', |
| 652 'first_known_failed_build_number': 4, | 853 'first_known_failed_build_number': 4, |
| 653 'suspected_cls': [ | 854 'suspected_cls': [ |
| 654 { | 855 { |
| 655 'repo_name': 'chromium', | 856 'repo_name': 'chromium', |
| 656 'revision': 'r4_10', | 857 'revision': 'r4_10', |
| 657 'commit_position': 410, | 858 'commit_position': 410, |
| 859 'analysis_approach': 'TRY_JOB' |
| 658 } | 860 } |
| 659 ], | 861 ], |
| 660 'analysis_approach': 'TRY_JOB', | 862 'analysis_approach': 'TRY_JOB', |
| 863 'is_flaky_test': False, |
| 864 'try_job_status': 'FINISHED' |
| 661 } | 865 } |
| 662 ] | 866 ] |
| 663 | 867 |
| 664 self._MockMasterIsSupported(supported=True) | 868 self._MockMasterIsSupported(supported=True) |
| 665 | 869 |
| 666 response = self.call_api('AnalyzeBuildFailures', body=builds) | 870 response = self.call_api('AnalyzeBuildFailures', body=builds) |
| 667 self.assertEqual(200, response.status_int) | 871 self.assertEqual(200, response.status_int) |
| 668 self.assertEqual(expected_results, response.json_body.get('results')) | 872 self.assertEqual(expected_results, response.json_body.get('results')) |
| 669 | 873 |
| 670 def testAnalysisRequestQueuedAsExpected(self): | 874 def testAnalysisRequestQueuedAsExpected(self): |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 748 'builder_name': 'b', | 952 'builder_name': 'b', |
| 749 'build_number': 456, | 953 'build_number': 456, |
| 750 'step_name': 'name (with patch) on Windows-7-SP1', | 954 'step_name': 'name (with patch) on Windows-7-SP1', |
| 751 } | 955 } |
| 752 ] | 956 ] |
| 753 } | 957 } |
| 754 | 958 |
| 755 response = self.call_api('AnalyzeFlake', body=flake) | 959 response = self.call_api('AnalyzeFlake', body=flake) |
| 756 self.assertEqual(200, response.status_int) | 960 self.assertEqual(200, response.status_int) |
| 757 self.assertTrue(response.json_body.get('analysis_triggered')) | 961 self.assertTrue(response.json_body.get('analysis_triggered')) |
| OLD | NEW |