Index: appengine/chromium_try_flakes/handlers/test/flake_issues_test.py |
diff --git a/appengine/chromium_try_flakes/handlers/test/flake_issues_test.py b/appengine/chromium_try_flakes/handlers/test/flake_issues_test.py |
index ea39daf614a1c81302051ff2197d9c8360b1de3f..bbdcfc907db849ddfbccf33812beb6033cec2a38 100644 |
--- a/appengine/chromium_try_flakes/handlers/test/flake_issues_test.py |
+++ b/appengine/chromium_try_flakes/handlers/test/flake_issues_test.py |
@@ -3,6 +3,7 @@ |
# found in the LICENSE file. |
import datetime |
+import json |
import mock |
from google.appengine.datastore import datastore_stub_util |
@@ -16,6 +17,59 @@ from testing_utils import testing |
from time_functions.testing import mock_datetime_utc |
+TEST_BUILDBOT_JSON_REPLY = json.dumps({ |
+ 'steps': [ |
+ # Simple case. |
+ {'results': [2], 'name': 'foo1', 'text': ['bar1']}, |
+ |
+ # Invalid test results. |
+ {'results': [2], 'name': 'foo2', 'text': ['TEST RESULTS WERE INVALID']}, |
+ |
+ # GTest tests. |
+ { |
+ 'results': [2], |
+ 'name': 'foo3', |
+ 'text': ['failures:<br/>bar2<br/>bar3<br/><br/>ignored:<br/>bar4'] |
+ }, |
+ |
+ # GPU tests. |
+ { |
+ 'results': [2], |
+ 'name': 'foo4', |
+ 'text': ['<"http://url/path?query&tests=bar5,bar6,,bar7">'] |
+ }, |
+ |
+ # Ignore non-success non-failure results (7 is TRY_PENDING). |
+ {'results': [7], 'name': 'foo5', 'text': ['bar8']}, |
+ |
+ # Ignore steps that are failing without patch too (ToT is broken). |
+ {'results': [2], 'name': 'foo6 (with patch)', 'text': ['bar9']}, |
+ {'results': [2], 'name': 'foo6 (without patch)', 'text': ['bar9']}, |
+ |
+ # Ignore steps that are duplicating error in another step. |
+ {'results': [2], 'name': 'steps', 'text': ['bar10']}, |
+ {'results': [2], 'name': '[swarming] foo7', 'text': ['bar11']}, |
+ {'results': [2], 'name': 'presubmit', 'text': ['bar12']}, |
+ {'results': [2], 'name': 'recipe failure reason', 'text': ['bar12a']}, |
+ {'results': [2], 'name': 'test results', 'text': ['bar12b']}, |
+ {'results': [2], 'name': 'Uncaught Exception', 'text': ['bar12c']}, |
+ {'results': [2], 'name': 'bot_update', 'text': ['bot_update PATCH FAILED']}, |
+ |
+ # Only count first step (with patch) and ignore summary step. |
+ {'results': [2], 'name': 'foo8 (with patch)', 'text': ['bar13']}, |
+ {'results': [0], 'name': 'foo8 (without patch)', 'text': ['bar14']}, |
+ {'results': [2], 'name': 'foo8', 'text': ['bar15']}, |
+ |
+ # GTest without flakes. |
+ { |
+ 'results': [2], |
+ 'name': 'foo9', |
+ 'text': ['failures:<br/><br/><br/>'] |
+ }, |
+ ] |
+}) |
+ |
+ |
class MockComment(object): |
def __init__(self, created, author, comment=None): |
self.created = created |
@@ -539,3 +593,90 @@ class FlakeIssuesTestCase(testing.AppengineTestCase): |
ProcessIssue._get_first_flake_occurrence_time( |
Flake(name='foo', occurrences=[fr5])), |
datetime.datetime(2015, 10, 19, 11, 0, 0)) |
+ |
+class CreateFlakyRunTestCase(testing.AppengineTestCase): |
+ app_module = main.app |
+ |
+ # This is needed to be able to test handlers using cross-group transactions. |
+ datastore_stub_consistency_policy = ( |
+ datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=1)) |
+ |
+ def _create_build_runs(self, ts, tf): |
+ pbr = PatchsetBuilderRuns( |
+ issue=123456789, patchset=20001, master='test.master', |
+ builder='test-builder').put() |
+ br_f = BuildRun(parent=pbr, buildnumber=100, result=2, time_started=ts, |
+ time_finished=tf).put() |
+ br_s = BuildRun(parent=pbr, buildnumber=101, result=0, time_started=ts, |
+ time_finished=tf).put() |
+ return br_f, br_s |
+ |
+ def test_get_flaky_run_reason_ignores_invalid_json(self): |
+ now = datetime.datetime.utcnow() |
+ br_f, br_s = self._create_build_runs(now - datetime.timedelta(hours=1), now) |
+ |
+ urlfetch_mock = mock.Mock() |
+ urlfetch_mock.return_value.content = 'invalid-json' |
+ |
+ with mock.patch('google.appengine.api.urlfetch.fetch', urlfetch_mock): |
+ self.test_app.post('/issues/create_flaky_run', |
+ {'failure_run_key': br_f.urlsafe(), |
+ 'success_run_key': br_s.urlsafe()}) |
+ |
+ def test_handles_incorrect_parameters(self): |
+ self.test_app.post('/issues/create_flaky_run', {}, status=400) |
+ |
+ def test_get_flaky_run_reason(self): |
+ now = datetime.datetime.utcnow() |
+ br_f, br_s = self._create_build_runs(now - datetime.timedelta(hours=1), now) |
+ |
+ urlfetch_mock = mock.Mock() |
+ urlfetch_mock.return_value.content = TEST_BUILDBOT_JSON_REPLY |
+ |
+ # We also create one Flake to test that it is correctly updated. Other Flake |
+ # entities will be created automatically. |
+ Flake(id='bar5', name='bar5', occurrences=[], |
+ last_time_seen=datetime.datetime.min).put() |
+ |
+ with mock.patch('google.appengine.api.urlfetch.fetch', urlfetch_mock): |
+ self.test_app.post('/issues/create_flaky_run', |
+ {'failure_run_key': br_f.urlsafe(), |
+ 'success_run_key': br_s.urlsafe()}) |
+ |
+ flaky_runs = FlakyRun.query().fetch(100) |
+ self.assertEqual(len(flaky_runs), 1) |
+ flaky_run = flaky_runs[0] |
+ self.assertEqual(flaky_run.failure_run, br_f) |
+ self.assertEqual(flaky_run.success_run, br_s) |
+ self.assertEqual(flaky_run.failure_run_time_finished, now) |
+ self.assertEqual(flaky_run.failure_run_time_started, |
+ now - datetime.timedelta(hours=1)) |
+ |
+ # Verify that we've used correct URL to access buildbot JSON endpoint. |
+ urlfetch_mock.assert_called_once_with( |
+ 'http://build.chromium.org/p/test.master/json/builders/test-builder/' |
+ 'builds/100') |
+ |
+ # Expected flakes to be found: list of (step_name, test_name). |
+ expected_flakes = [ |
+ ('foo1', 'bar1'), ('foo2', 'TEST RESULTS WERE INVALID'), |
+ ('foo3', 'bar2'), ('foo3', 'bar3'), ('foo4', 'bar5'), ('foo4', 'bar6'), |
+ ('foo4', 'bar7'), ('foo8 (with patch)', 'bar13'), |
+ ] |
+ |
+ flake_occurrences = flaky_run.flakes |
+ self.assertEqual(len(flake_occurrences), len(expected_flakes)) |
+ actual_flake_occurrences = [ |
+ (fo.name, fo.failure) for fo in flake_occurrences] |
+ self.assertEqual(expected_flakes, actual_flake_occurrences) |
+ |
+ # We compare sets below, because order of flakes returned by datastore |
+ # doesn't have to be same as steps above. |
+ flakes = Flake.query().fetch() |
+ self.assertEqual(len(flakes), len(expected_flakes)) |
+ expected_flake_names = set([ef[1] for ef in expected_flakes]) |
+ actual_flake_names = set([f.name for f in flakes]) |
+ self.assertEqual(expected_flake_names, actual_flake_names) |
+ |
+ for flake in flakes: |
+ self.assertEqual(flake.occurrences, [flaky_run.key]) |