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