blob: 586534a8d99dbf9692043f4f649b9279dc8d027c [file] [log] [blame]
Stephen Martinis3c3b0e52019-03-06 18:39:511#!/usr/bin/env vpython
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import cStringIO
7import json
8import logging
9import os
10import shutil
11import sys
12import tempfile
13import unittest
14
15import common_merge_script_tests
16
17THIS_DIR = os.path.dirname(os.path.abspath(__file__))
18
19# For 'standard_gtest_merge.py'.
20sys.path.insert(
21 0, os.path.abspath(os.path.join(THIS_DIR, '..', 'resources')))
22
23import mock
24
25import standard_gtest_merge
26
27
28# gtest json output for successfully finished shard #0.
29GOOD_GTEST_JSON_0 = {
30 'all_tests': [
31 'AlignedMemoryTest.DynamicAllocation',
32 'AlignedMemoryTest.ScopedDynamicAllocation',
33 'AlignedMemoryTest.StackAlignment',
34 'AlignedMemoryTest.StaticAlignment',
35 ],
36 'disabled_tests': [
37 'ConditionVariableTest.TimeoutAcrossSetTimeOfDay',
38 'FileTest.TouchGetInfo',
39 'MessageLoopTestTypeDefault.EnsureDeletion',
40 ],
41 'global_tags': ['CPU_64_BITS', 'MODE_DEBUG', 'OS_LINUX', 'OS_POSIX'],
42 'per_iteration_data': [{
43 'AlignedMemoryTest.DynamicAllocation': [{
44 'elapsed_time_ms': 0,
45 'losless_snippet': True,
46 'output_snippet': 'blah\\n',
47 'output_snippet_base64': 'YmxhaAo=',
48 'status': 'SUCCESS',
49 }],
50 'AlignedMemoryTest.ScopedDynamicAllocation': [{
51 'elapsed_time_ms': 0,
52 'losless_snippet': True,
53 'output_snippet': 'blah\\n',
54 'output_snippet_base64': 'YmxhaAo=',
55 'status': 'SUCCESS',
56 }],
57 }],
58 'test_locations': {
59 'AlignedMemoryTest.DynamicAllocation': {
60 'file': 'foo/bar/allocation_test.cc',
61 'line': 123,
62 },
63 'AlignedMemoryTest.ScopedDynamicAllocation': {
64 'file': 'foo/bar/allocation_test.cc',
65 'line': 456,
66 },
67 # This is a test from a different shard, but this happens in practice and we
68 # should not fail if information is repeated.
69 'AlignedMemoryTest.StaticAlignment': {
70 'file': 'foo/bar/allocation_test.cc',
71 'line': 12,
72 },
73 },
74}
75
76
77# gtest json output for successfully finished shard #1.
78GOOD_GTEST_JSON_1 = {
79 'all_tests': [
80 'AlignedMemoryTest.DynamicAllocation',
81 'AlignedMemoryTest.ScopedDynamicAllocation',
82 'AlignedMemoryTest.StackAlignment',
83 'AlignedMemoryTest.StaticAlignment',
84 ],
85 'disabled_tests': [
86 'ConditionVariableTest.TimeoutAcrossSetTimeOfDay',
87 'FileTest.TouchGetInfo',
88 'MessageLoopTestTypeDefault.EnsureDeletion',
89 ],
90 'global_tags': ['CPU_64_BITS', 'MODE_DEBUG', 'OS_LINUX', 'OS_POSIX'],
91 'per_iteration_data': [{
92 'AlignedMemoryTest.StackAlignment': [{
93 'elapsed_time_ms': 0,
94 'losless_snippet': True,
95 'output_snippet': 'blah\\n',
96 'output_snippet_base64': 'YmxhaAo=',
97 'status': 'SUCCESS',
98 }],
99 'AlignedMemoryTest.StaticAlignment': [{
100 'elapsed_time_ms': 0,
101 'losless_snippet': True,
102 'output_snippet': 'blah\\n',
103 'output_snippet_base64': 'YmxhaAo=',
104 'status': 'SUCCESS',
105 }],
106 }],
107 'test_locations': {
108 'AlignedMemoryTest.StackAlignment': {
109 'file': 'foo/bar/allocation_test.cc',
110 'line': 789,
111 },
112 'AlignedMemoryTest.StaticAlignment': {
113 'file': 'foo/bar/allocation_test.cc',
114 'line': 12,
115 },
116 },
117}
118
119
120TIMED_OUT_GTEST_JSON_1 = {
121 'disabled_tests': [],
122 'global_tags': [],
123 'all_tests': [
124 'AlignedMemoryTest.DynamicAllocation',
125 'AlignedMemoryTest.ScopedDynamicAllocation',
126 'AlignedMemoryTest.StackAlignment',
127 'AlignedMemoryTest.StaticAlignment',
128 ],
129 'per_iteration_data': [{
130 'AlignedMemoryTest.StackAlignment': [{
131 'elapsed_time_ms': 54000,
132 'losless_snippet': True,
133 'output_snippet': 'timed out',
134 'output_snippet_base64': '',
135 'status': 'FAILURE',
136 }],
137 'AlignedMemoryTest.StaticAlignment': [{
138 'elapsed_time_ms': 0,
139 'losless_snippet': True,
140 'output_snippet': '',
141 'output_snippet_base64': '',
142 'status': 'NOTRUN',
143 }],
144 }],
145 'test_locations': {
146 'AlignedMemoryTest.StackAlignment': {
147 'file': 'foo/bar/allocation_test.cc',
148 'line': 789,
149 },
150 'AlignedMemoryTest.StaticAlignment': {
151 'file': 'foo/bar/allocation_test.cc',
152 'line': 12,
153 },
154 },
155}
156
157# GOOD_GTEST_JSON_0 and GOOD_GTEST_JSON_1 merged.
158GOOD_GTEST_JSON_MERGED = {
159 'all_tests': [
160 'AlignedMemoryTest.DynamicAllocation',
161 'AlignedMemoryTest.ScopedDynamicAllocation',
162 'AlignedMemoryTest.StackAlignment',
163 'AlignedMemoryTest.StaticAlignment',
164 ],
165 'disabled_tests': [
166 'ConditionVariableTest.TimeoutAcrossSetTimeOfDay',
167 'FileTest.TouchGetInfo',
168 'MessageLoopTestTypeDefault.EnsureDeletion',
169 ],
170 'global_tags': ['CPU_64_BITS', 'MODE_DEBUG', 'OS_LINUX', 'OS_POSIX'],
171 'missing_shards': [],
172 'per_iteration_data': [{
173 'AlignedMemoryTest.DynamicAllocation': [{
174 'elapsed_time_ms': 0,
175 'losless_snippet': True,
176 'output_snippet': 'blah\\n',
177 'output_snippet_base64': 'YmxhaAo=',
178 'status': 'SUCCESS',
179 }],
180 'AlignedMemoryTest.ScopedDynamicAllocation': [{
181 'elapsed_time_ms': 0,
182 'losless_snippet': True,
183 'output_snippet': 'blah\\n',
184 'output_snippet_base64': 'YmxhaAo=',
185 'status': 'SUCCESS',
186 }],
187 'AlignedMemoryTest.StackAlignment': [{
188 'elapsed_time_ms': 0,
189 'losless_snippet': True,
190 'output_snippet': 'blah\\n',
191 'output_snippet_base64': 'YmxhaAo=',
192 'status': 'SUCCESS',
193 }],
194 'AlignedMemoryTest.StaticAlignment': [{
195 'elapsed_time_ms': 0,
196 'losless_snippet': True,
197 'output_snippet': 'blah\\n',
198 'output_snippet_base64': 'YmxhaAo=',
199 'status': 'SUCCESS',
200 }],
201 }],
202 'swarming_summary': {
203 u'shards': [
204 {
205 u'state': u'COMPLETED',
206 u'outputs_ref': {
207 u'view_url': u'blah',
208 },
209 }
210 ],
211 },
212 'test_locations': {
213 'AlignedMemoryTest.StackAlignment': {
214 'file': 'foo/bar/allocation_test.cc',
215 'line': 789,
216 },
217 'AlignedMemoryTest.StaticAlignment': {
218 'file': 'foo/bar/allocation_test.cc',
219 'line': 12,
220 },
221 'AlignedMemoryTest.DynamicAllocation': {
222 'file': 'foo/bar/allocation_test.cc',
223 'line': 123,
224 },
225 'AlignedMemoryTest.ScopedDynamicAllocation': {
226 'file': 'foo/bar/allocation_test.cc',
227 'line': 456,
228 },
229 },
230}
231
232
233# Only shard #1 finished. UNRELIABLE_RESULTS is set.
234BAD_GTEST_JSON_ONLY_1_SHARD = {
235 'all_tests': [
236 'AlignedMemoryTest.DynamicAllocation',
237 'AlignedMemoryTest.ScopedDynamicAllocation',
238 'AlignedMemoryTest.StackAlignment',
239 'AlignedMemoryTest.StaticAlignment',
240 ],
241 'disabled_tests': [
242 'ConditionVariableTest.TimeoutAcrossSetTimeOfDay',
243 'FileTest.TouchGetInfo',
244 'MessageLoopTestTypeDefault.EnsureDeletion',
245 ],
246 'global_tags': [
247 'CPU_64_BITS',
248 'MODE_DEBUG',
249 'OS_LINUX',
250 'OS_POSIX',
251 'UNRELIABLE_RESULTS',
252 ],
253 'missing_shards': [0],
254 'per_iteration_data': [{
255 'AlignedMemoryTest.StackAlignment': [{
256 'elapsed_time_ms': 0,
257 'losless_snippet': True,
258 'output_snippet': 'blah\\n',
259 'output_snippet_base64': 'YmxhaAo=',
260 'status': 'SUCCESS',
261 }],
262 'AlignedMemoryTest.StaticAlignment': [{
263 'elapsed_time_ms': 0,
264 'losless_snippet': True,
265 'output_snippet': 'blah\\n',
266 'output_snippet_base64': 'YmxhaAo=',
267 'status': 'SUCCESS',
268 }],
269 }],
270 'test_locations': {
271 'AlignedMemoryTest.StackAlignment': {
272 'file': 'foo/bar/allocation_test.cc',
273 'line': 789,
274 },
275 'AlignedMemoryTest.StaticAlignment': {
276 'file': 'foo/bar/allocation_test.cc',
277 'line': 12,
278 },
279 },
280}
281
282
283# GOOD_GTEST_JSON_0 and TIMED_OUT_GTEST_JSON_1 merged.
284TIMED_OUT_GTEST_JSON_MERGED = {
285 'all_tests': [
286 'AlignedMemoryTest.DynamicAllocation',
287 'AlignedMemoryTest.ScopedDynamicAllocation',
288 'AlignedMemoryTest.StackAlignment',
289 'AlignedMemoryTest.StaticAlignment',
290 ],
291 'disabled_tests': [
292 'ConditionVariableTest.TimeoutAcrossSetTimeOfDay',
293 'FileTest.TouchGetInfo',
294 'MessageLoopTestTypeDefault.EnsureDeletion',
295 ],
296 'global_tags': ['CPU_64_BITS', 'MODE_DEBUG', 'OS_LINUX', 'OS_POSIX'],
297 'missing_shards': [],
298 'per_iteration_data': [{
299 'AlignedMemoryTest.DynamicAllocation': [{
300 'elapsed_time_ms': 0,
301 'losless_snippet': True,
302 'output_snippet': 'blah\\n',
303 'output_snippet_base64': 'YmxhaAo=',
304 'status': 'SUCCESS',
305 }],
306 'AlignedMemoryTest.ScopedDynamicAllocation': [{
307 'elapsed_time_ms': 0,
308 'losless_snippet': True,
309 'output_snippet': 'blah\\n',
310 'output_snippet_base64': 'YmxhaAo=',
311 'status': 'SUCCESS',
312 }],
313 'AlignedMemoryTest.StackAlignment': [{
314 'elapsed_time_ms': 54000,
315 'losless_snippet': True,
316 'output_snippet': 'timed out',
317 'output_snippet_base64': '',
318 'status': 'FAILURE',
319 }],
320 'AlignedMemoryTest.StaticAlignment': [{
321 'elapsed_time_ms': 0,
322 'losless_snippet': True,
323 'output_snippet': '',
324 'output_snippet_base64': '',
325 'status': 'NOTRUN',
326 }],
327 }],
328 'swarming_summary': {
329 u'shards': [
330 {
331 u'state': u'COMPLETED',
332 },
333 {
334 u'state': u'TIMED_OUT',
335 },
336 ],
337 },
338 'test_locations': {
339 'AlignedMemoryTest.StackAlignment': {
340 'file': 'foo/bar/allocation_test.cc',
341 'line': 789,
342 },
343 'AlignedMemoryTest.StaticAlignment': {
344 'file': 'foo/bar/allocation_test.cc',
345 'line': 12,
346 },
347 'AlignedMemoryTest.DynamicAllocation': {
348 'file': 'foo/bar/allocation_test.cc',
349 'line': 123,
350 },
351 'AlignedMemoryTest.ScopedDynamicAllocation': {
352 'file': 'foo/bar/allocation_test.cc',
353 'line': 456,
354 },
355 },
356}
357
358
359class _StandardGtestMergeTest(unittest.TestCase):
360
361 def setUp(self):
362 self.temp_dir = tempfile.mkdtemp()
363
364 def tearDown(self):
365 shutil.rmtree(self.temp_dir)
366
367 def _write_temp_file(self, path, content):
368 abs_path = os.path.join(self.temp_dir, path.replace('/', os.sep))
369 if not os.path.exists(os.path.dirname(abs_path)):
370 os.makedirs(os.path.dirname(abs_path))
371 with open(abs_path, 'w') as f:
372 if isinstance(content, dict):
373 json.dump(content, f)
374 else:
375 assert isinstance(content, str)
376 f.write(content)
377 return abs_path
378
379
380class LoadShardJsonTest(_StandardGtestMergeTest):
381
382 def test_double_digit_jsons(self):
383 jsons_to_merge = []
384 for i in xrange(15):
385 json_dir = os.path.join(self.temp_dir, str(i))
386 json_path = os.path.join(json_dir, 'output.json')
387 if not os.path.exists(json_dir):
388 os.makedirs(json_dir)
389 with open(json_path, 'w') as f:
390 json.dump({'all_tests': ['LoadShardJsonTest.test%d' % i]}, f)
391 jsons_to_merge.append(json_path)
392
393 content, err = standard_gtest_merge.load_shard_json(
394 0, None, jsons_to_merge)
395 self.assertEqual({'all_tests': ['LoadShardJsonTest.test0']}, content)
396 self.assertIsNone(err)
397
398 content, err = standard_gtest_merge.load_shard_json(
399 12, None, jsons_to_merge)
400 self.assertEqual({'all_tests': ['LoadShardJsonTest.test12']}, content)
401 self.assertIsNone(err)
402
403 def test_double_task_id_jsons(self):
404 jsons_to_merge = []
405 for i in xrange(15):
406 json_dir = os.path.join(self.temp_dir, 'deadbeef%d' % i)
407 json_path = os.path.join(json_dir, 'output.json')
408 if not os.path.exists(json_dir):
409 os.makedirs(json_dir)
410 with open(json_path, 'w') as f:
411 json.dump({'all_tests': ['LoadShardJsonTest.test%d' % i]}, f)
412 jsons_to_merge.append(json_path)
413
414 content, err = standard_gtest_merge.load_shard_json(
415 0, 'deadbeef0', jsons_to_merge)
416 self.assertEqual({'all_tests': ['LoadShardJsonTest.test0']},
417 content)
418 self.assertIsNone(err)
419
420 content, err = standard_gtest_merge.load_shard_json(
421 12, 'deadbeef12', jsons_to_merge)
422 self.assertEqual({'all_tests': ['LoadShardJsonTest.test12']},
423 content)
424 self.assertIsNone(err)
425
426
427class MergeShardResultsTest(_StandardGtestMergeTest):
428 """Tests for merge_shard_results function."""
429
430 def setUp(self):
431 super(MergeShardResultsTest, self).setUp()
432 self.summary = None
433 self.test_files = []
434
435 def stage(self, summary, files):
436 self.summary = self._write_temp_file('summary.json', summary)
437 for path, content in files.iteritems():
438 abs_path = self._write_temp_file(path, content)
439 self.test_files.append(abs_path)
440
441 def call(self):
442 stdout = cStringIO.StringIO()
443 with mock.patch('sys.stdout', stdout):
444 merged = standard_gtest_merge.merge_shard_results(
445 self.summary, self.test_files)
446 return merged, stdout.getvalue().strip()
447
448 def assertUnicodeEquals(self, expectation, result):
449 def convert_to_unicode(key_or_value):
450 if isinstance(key_or_value, str):
451 return unicode(key_or_value)
452 if isinstance(key_or_value, dict):
453 return {convert_to_unicode(k): convert_to_unicode(v)
454 for k, v in key_or_value.items()}
455 if isinstance(key_or_value, list):
456 return [convert_to_unicode(x) for x in key_or_value]
457 return key_or_value
458
459 unicode_expectations = convert_to_unicode(expectation)
460 unicode_result = convert_to_unicode(result)
461 self.assertEquals(unicode_expectations, unicode_result)
462
463 def test_ok(self):
464 # Two shards, both successfully finished.
465 self.stage({
466 u'shards': [
467 {
468 u'state': u'COMPLETED',
469 },
470 {
471 u'state': u'COMPLETED',
472 },
473 ],
474 },
475 {
476 '0/output.json': GOOD_GTEST_JSON_0,
477 '1/output.json': GOOD_GTEST_JSON_1,
478 })
479 merged, stdout = self.call()
480 merged['swarming_summary'] = {
481 'shards': [
482 {
483 u'state': u'COMPLETED',
484 u'outputs_ref': {
485 u'view_url': u'blah',
486 },
487 }
488 ],
489 }
490 self.assertUnicodeEquals(GOOD_GTEST_JSON_MERGED, merged)
491 self.assertEqual('', stdout)
492
493 def test_timed_out(self):
494 # Two shards, both successfully finished.
495 self.stage({
496 'shards': [
497 {
498 'state': 'COMPLETED',
499 },
500 {
501 'state': 'TIMED_OUT',
502 },
503 ],
504 },
505 {
506 '0/output.json': GOOD_GTEST_JSON_0,
507 '1/output.json': TIMED_OUT_GTEST_JSON_1,
508 })
509 merged, stdout = self.call()
510
511 self.assertUnicodeEquals(TIMED_OUT_GTEST_JSON_MERGED, merged)
512 self.assertIn(
513 'Test runtime exceeded allocated time\n', stdout)
514
515 def test_missing_summary_json(self):
516 # summary.json is missing, should return None and emit warning.
517 self.summary = os.path.join(self.temp_dir, 'summary.json')
518 merged, output = self.call()
519 self.assertEqual(None, merged)
520 self.assertIn('@@@STEP_WARNINGS@@@', output)
521 self.assertIn('summary.json is missing or can not be read', output)
522
523 def test_unfinished_shards(self):
524 # Only one shard (#1) finished. Shard #0 did not.
525 self.stage({
526 u'shards': [
527 None,
528 {
529 u'state': u'COMPLETED',
530 },
531 ],
532 },
533 {
534 u'1/output.json': GOOD_GTEST_JSON_1,
535 })
536 merged, stdout = self.call()
537 merged.pop('swarming_summary')
538 self.assertUnicodeEquals(BAD_GTEST_JSON_ONLY_1_SHARD, merged)
539 self.assertIn(
540 '@@@STEP_WARNINGS@@@\nsome shards did not complete: 0\n', stdout)
541 self.assertIn(
542 '@@@STEP_LOG_LINE@some shards did not complete: 0@'
543 'Missing results from the following shard(s): 0@@@\n', stdout)
544
545 def test_missing_output_json(self):
546 # Shard #0 output json is missing.
547 self.stage({
548 u'shards': [
549 {
550 u'state': u'COMPLETED',
551 },
552 {
553 u'state': u'COMPLETED',
554 },
555 ],
556 },
557 {
558 u'1/output.json': GOOD_GTEST_JSON_1,
559 })
560 merged, stdout = self.call()
561 merged.pop('swarming_summary')
562 self.assertUnicodeEquals(BAD_GTEST_JSON_ONLY_1_SHARD, merged)
563 self.assertIn(
564 'No result was found: '
565 'shard 0 test output was missing', stdout)
566
567 def test_large_output_json(self):
568 # a shard is too large.
569 self.stage({
570 u'shards': [
571 {
572 u'state': u'COMPLETED',
573 },
574 {
575 u'state': u'COMPLETED',
576 },
577 ],
578 },
579 {
580 '0/output.json': GOOD_GTEST_JSON_0,
581 '1/output.json': GOOD_GTEST_JSON_1,
582 })
583 old_json_limit = standard_gtest_merge.OUTPUT_JSON_SIZE_LIMIT
584 len0 = len(json.dumps(GOOD_GTEST_JSON_0))
585 len1 = len(json.dumps(GOOD_GTEST_JSON_1))
586 large_shard = "0" if len0 > len1 else "1"
587 try:
588 # Override max output.json size just for this test.
589 standard_gtest_merge.OUTPUT_JSON_SIZE_LIMIT = min(len0,len1)
590 merged, stdout = self.call()
591 merged.pop('swarming_summary')
592 self.assertUnicodeEquals(BAD_GTEST_JSON_ONLY_1_SHARD, merged)
593 self.assertIn(
594 'No result was found: '
595 'shard %s test output exceeded the size limit' % large_shard, stdout)
596 finally:
597 standard_gtest_merge.OUTPUT_JSON_SIZE_LIMIT = old_json_limit
598
599
600class CommandLineTest(common_merge_script_tests.CommandLineTest):
601
602 def __init__(self, methodName='runTest'):
603 super(CommandLineTest, self).__init__(methodName, standard_gtest_merge)
604
605
606if __name__ == '__main__':
607 logging.basicConfig(
608 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
609 if '-v' in sys.argv:
610 unittest.TestCase.maxDiff = None
611 unittest.main()