• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env vpython3
2# Copyright 2021 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import json
7import os
8import shutil
9import tempfile
10import unittest
11
12import merge_js_lib as merger
13from pathlib import Path
14import node
15
16_HERE_DIR = Path(__file__).parent.resolve()
17_SOURCE_MAP_PROCESSOR = (_HERE_DIR.parent.parent.parent /
18                         'tools' / 'code_coverage' /
19                         'js_source_maps' / 'create_js_source_maps' /
20                         'create_js_source_maps.js').resolve()
21
22@unittest.skipIf(os.name == 'nt', 'Not intended to work on Windows')
23class ConvertToIstanbulTest(unittest.TestCase):
24    _TEST_SOURCE_A = """function add(a, b) {
25  return a + b;
26}
27
28function subtract(a, b) {
29  return a - b;
30}
31
32subtract(5, 2);
33"""
34    _INVALID_MAPPING_A = (
35        "//# sourceMappingURL=data:application/json;base64,"
36        "eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpb"
37        "mdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Oz"
38        "s7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUN"
39        "BIiwiZmlsZSI6Ii91c3IvbG9jYWwvZ29vZ2xlL2hvbWUvc3Jpbml2YXNoZWdkZS9jaHJv"
40        "bWl1bS9zcmMvZm9vX3ByZS50cyIsInNvdXJjZVJvb3QiOiIvdXNyL2xvY2FsL2dvb2dsZ"
41        "S9ob21lL3NyaW5pdmFzaGVnZGUvY2hyb21pdW0vc3JjIiwic291cmNlc0NvbnRlbnQiOl"
42        "siZnVuY3Rpb24gYWRkKGEsIGIpIHtcbiAgcmV0dXJuIGEgKyBiO1xufVxuXG5mdW5jdGl"
43        "vbiBzdWJ0cmFjdChhLCBiKSB7XG4gIHJldHVybiBhIC0gYjtcbn1cblxuc3VidHJhY3Qo"
44        "NSwgMik7XG4iXX0="
45        )
46
47    _TEST_COVERAGE_A = """{
48  "result": [
49    {
50      "scriptId":"72",
51      "url":"//file.js",
52      "functions":[
53        {
54          "functionName":"",
55          "ranges":[
56            {"startOffset":0,"endOffset":101,"count":1}
57          ],
58          "isBlockCoverage":true
59        },
60        {
61          "functionName":"add",
62          "ranges":[
63            {"startOffset":0,"endOffset":38,"count":0}
64          ],
65          "isBlockCoverage":false
66        },
67        {
68          "functionName":"subtract",
69          "ranges":[
70            {"startOffset":40,"endOffset":83,"count":1}
71          ],
72          "isBlockCoverage":true
73        }
74      ]
75    }
76  ]
77}
78"""
79
80    _TEST_COVERAGE_INVALID = """{
81  "scriptId":"72",
82  "url":"//file.js",
83  "functions":[
84    {
85      "functionName":"",
86      "ranges":[
87        {"startOffset":0,"endOffset":101,"count":1}
88      ],
89      "isBlockCoverage":true
90    },
91    {
92      "functionName":"add",
93      "ranges":[
94        {"startOffset":0,"endOffset":38,"count":0}
95      ],
96      "isBlockCoverage":false
97    },
98    {
99      "functionName":"subtract",
100      "ranges":[
101        {"startOffset":40,"endOffset":83,"count":1}
102      ],
103      "isBlockCoverage":true
104    }
105  ]
106}
107"""
108
109    _TEST_SOURCE_B = """const {subtract} = require('./test1.js');
110
111function add(a, b) {
112  return a + b;
113}
114
115subtract(5, 2);
116
117"""
118
119    _TEST_SOURCE_C = """exports.subtract = function(a, b) {
120  return a - b;
121}
122"""
123
124    _TEST_COVERAGE_B = """{
125  "result":[
126    {
127      "scriptId":"72",
128      "url":"//test.js",
129      "functions":[
130        {
131          "functionName":"",
132          "ranges":[
133            {"startOffset":0,"endOffset":99,"count":1}
134          ],
135          "isBlockCoverage":true
136        },
137        {
138          "functionName":"add",
139          "ranges":[
140            {"startOffset":43,"endOffset":81,"count":0}
141          ],
142          "isBlockCoverage":false
143        }
144      ]
145    },
146    {
147      "scriptId":"73",
148      "url":"//test1.js",
149      "functions":[
150        {
151          "functionName":"",
152          "ranges":[
153            {"startOffset":0,"endOffset":54,"count":1}
154          ],
155          "isBlockCoverage":true
156        },
157        {
158          "functionName":"exports.subtract",
159          "ranges":[
160            {"startOffset":19,"endOffset":53,"count":1}
161          ],
162          "isBlockCoverage":true
163        }
164      ]
165    }
166  ]
167}
168"""
169
170    _TEST_COVERAGE_NO_LEADING_SLASH = """{
171  "result":[
172    {
173      "scriptId":"72",
174      "url":"file:///usr/local/google/home/benreich/v8-to-istanbul/test.js",
175      "functions":[
176        {
177          "functionName":"",
178          "ranges":[
179            {"startOffset":0,"endOffset":99,"count":1}
180          ],
181          "isBlockCoverage":true
182        },
183        {
184          "functionName":"add",
185          "ranges":[
186            {"startOffset":43,"endOffset":81,"count":0}
187          ],
188          "isBlockCoverage":false
189        }
190      ]
191    },
192    {
193      "scriptId":"73",
194      "url":"//test1.js",
195      "functions":[
196        {
197          "functionName":"",
198          "ranges":[
199            {"startOffset":0,"endOffset":54,"count":1}
200          ],
201          "isBlockCoverage":true
202        },
203        {
204          "functionName":"exports.subtract",
205          "ranges":[
206            {"startOffset":19,"endOffset":53,"count":1}
207          ],
208          "isBlockCoverage":true
209        }
210      ]
211    }
212  ]
213}
214"""
215
216    _TEST_COVERAGE_DUPLICATE_SINGLE = """{
217  "result":[
218    {
219      "scriptId":"73",
220      "url":"//test1.js",
221      "functions":[
222        {
223          "functionName":"",
224          "ranges":[
225            {"startOffset":0,"endOffset":54,"count":1}
226          ],
227          "isBlockCoverage":true
228        },
229        {
230          "functionName":"exports.subtract",
231          "ranges":[
232            {"startOffset":19,"endOffset":53,"count":1}
233          ],
234          "isBlockCoverage":true
235        }
236      ]
237    }
238  ]
239}
240"""
241
242    _TEST_COVERAGE_DUPLICATE_DOUBLE = """{
243  "result":[
244    {
245      "scriptId":"72",
246      "url":"//test.js",
247      "functions":[
248        {
249          "functionName":"",
250          "ranges":[
251            {"startOffset":0,"endOffset":99,"count":1}
252          ],
253          "isBlockCoverage":true
254        },
255        {
256          "functionName":"add",
257          "ranges":[
258            {"startOffset":43,"endOffset":81,"count":0}
259          ],
260          "isBlockCoverage":false
261        }
262      ]
263    },
264    {
265      "scriptId":"73",
266      "url":"//test1.js",
267      "functions":[
268        {
269          "functionName":"",
270          "ranges":[
271            {"startOffset":0,"endOffset":54,"count":1}
272          ],
273          "isBlockCoverage":true
274        },
275        {
276          "functionName":"exports.subtract",
277          "ranges":[
278            {"startOffset":19,"endOffset":53,"count":1}
279          ],
280          "isBlockCoverage":true
281        }
282      ]
283    }
284  ]
285}
286"""
287
288    def setUp(self):
289        self.task_output_dir = tempfile.mkdtemp()
290        self.coverage_dir = os.path.join(self.task_output_dir, 'coverages')
291        self.source_dir = os.path.join(self.task_output_dir, 'source')
292        self.out_dir = os.path.join(self.task_output_dir, 'out')
293        self.sourceRoot = '/'
294
295        os.makedirs(self.coverage_dir)
296        os.makedirs(self.source_dir)
297        os.makedirs(self.out_dir)
298
299    def tearDown(self):
300        shutil.rmtree(self.task_output_dir)
301
302    def list_files(self, absolute_path):
303        actual_files = []
304        for root, _, files in os.walk(absolute_path):
305            actual_files.extend(
306                [os.path.join(root, file_name) for file_name in files])
307
308        return actual_files
309
310    def _write_files(self, root_dir, *file_path_contents):
311        for data in file_path_contents:
312            file_path, contents = data
313            with open(os.path.join(root_dir, file_path), 'w') as f:
314                f.write(contents)
315
316    def _write_transformations(
317        self, source_dir, out_dir,
318        original_file_name, input_file_name, output_file_name):
319        original_file = os.path.join(source_dir, original_file_name)
320        input_file = os.path.join(source_dir, input_file_name)
321        output_file = os.path.join(out_dir, output_file_name)
322        node.RunNode([
323            str(_SOURCE_MAP_PROCESSOR),
324            "--originals={}".format(" ".join([original_file])),
325            "--inputs={}".format(" ".join([input_file])),
326            "--outputs={}".format(" ".join([output_file])),
327            "--inline-sourcemaps",
328            "--sourceRoot={}".format(self.sourceRoot),
329        ])
330
331    def write_sources(self, *file_path_contents):
332        url_to_path_map = {}
333        for path_url, contents in file_path_contents:
334            file_path, url = path_url
335            url_to_path_map[file_path] = url
336            self._write_files(self.source_dir, (url, contents))
337            self._write_files(self.out_dir, (url, contents))
338            self._write_transformations(
339              self.source_dir, self.out_dir, url, url, url)
340        with open(os.path.join(self.out_dir, 'parsed_scripts.json'),
341                  'w',
342                  encoding='utf-8') as f:
343            f.write(json.dumps(url_to_path_map))
344
345    def write_coverages(self, *file_path_contents):
346        self._write_files(self.coverage_dir, *file_path_contents)
347
348    def test_happy_path(self):
349        self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A))
350        self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A))
351
352        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
353                                                self.out_dir,
354                                                self.task_output_dir)
355
356        istanbul_files = self.list_files(
357            os.path.join(self.task_output_dir, 'istanbul'))
358        self.assertEqual(len(istanbul_files), 1)
359
360    def test_invalid_mapping(self):
361        self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A))
362        self._write_files(
363            self.out_dir, (
364            'file.js', self._TEST_SOURCE_A + '\n' + self._INVALID_MAPPING_A))
365        self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A))
366
367
368        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
369                                                self.out_dir,
370                                                self.task_output_dir)
371
372        istanbul_files = self.list_files(
373            os.path.join(self.task_output_dir, 'istanbul'))
374        self.assertEqual(len(istanbul_files), 0)
375
376    def test_no_coverages_in_file(self):
377        coverage_file = """{
378      "result": []
379    }
380    """
381
382        self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A))
383        self.write_coverages(('test_coverage.cov.json', coverage_file))
384
385        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
386                                                self.out_dir,
387                                                self.task_output_dir)
388
389        istanbul_files = self.list_files(
390            os.path.join(self.task_output_dir, 'istanbul'))
391        self.assertEqual(len(istanbul_files), 0)
392
393    def test_invalid_coverage_file(self):
394        self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A))
395        self.write_coverages(
396            ('test_coverage.cov.json', self._TEST_COVERAGE_INVALID))
397
398        with self.assertRaises(RuntimeError):
399            merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
400                                                    self.out_dir,
401                                                    self.task_output_dir)
402
403    def test_multiple_coverages_single_file(self):
404        self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B),
405                           (('//test1.js', 'test1.js'), self._TEST_SOURCE_C))
406        self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_B))
407
408        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
409                                                self.out_dir,
410                                                self.task_output_dir)
411
412        istanbul_files = self.list_files(
413            os.path.join(self.task_output_dir, 'istanbul'))
414        self.assertEqual(len(istanbul_files), 2)
415
416    def test_multiple_coverages_no_leading_double_slash(self):
417        self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B),
418                           (('//test1.js', 'test1.js'), self._TEST_SOURCE_C))
419        self.write_coverages(
420            ('test_coverage.cov.json', self._TEST_COVERAGE_NO_LEADING_SLASH))
421
422        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
423                                                self.out_dir,
424                                                self.task_output_dir)
425
426        istanbul_files = self.list_files(
427            os.path.join(self.task_output_dir, 'istanbul'))
428        self.assertEqual(len(istanbul_files), 1)
429
430
431    def test_multiple_duplicate_coverages_flattened(self):
432        self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B),
433                           (('//test1.js', 'test1.js'), self._TEST_SOURCE_C))
434        self.write_coverages(
435            ('test_coverage_1.cov.json', self._TEST_COVERAGE_B))
436        self.write_coverages(
437            ('test_coverage_2.cov.json', self._TEST_COVERAGE_DUPLICATE_DOUBLE))
438
439        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
440                                                self.out_dir,
441                                                self.task_output_dir)
442
443        istanbul_files = self.list_files(
444            os.path.join(self.task_output_dir, 'istanbul'))
445        self.assertEqual(len(istanbul_files), 2)
446
447
448    def test_original_source_missing(self):
449        self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A))
450        self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A))
451        os.remove(os.path.join(self.source_dir, "file.js"))
452
453        merger.convert_raw_coverage_to_istanbul([self.coverage_dir],
454                                                self.out_dir,
455                                                self.task_output_dir)
456
457        istanbul_files = self.list_files(
458            os.path.join(self.task_output_dir, 'istanbul'))
459        self.assertEqual(len(istanbul_files), 0)
460
461
462    def test_multiple_coverages_in_multiple_shards(self):
463        coverage_dir_1 = os.path.join(self.coverage_dir, 'coverage1')
464        coverage_dir_2 = os.path.join(self.coverage_dir, 'coverage2')
465        os.makedirs(coverage_dir_1)
466        os.makedirs(coverage_dir_2)
467
468        self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B),
469                           (('//test1.js', 'test1.js'), self._TEST_SOURCE_C))
470        self._write_files(coverage_dir_1,
471                          ('test_coverage_1.cov.json', self._TEST_COVERAGE_B))
472        self._write_files(
473            coverage_dir_2,
474            ('test_coverage_2.cov.json', self._TEST_COVERAGE_DUPLICATE_DOUBLE))
475
476        merger.convert_raw_coverage_to_istanbul(
477            [coverage_dir_1, coverage_dir_2], self.out_dir,
478            self.task_output_dir)
479
480        istanbul_files = self.list_files(
481            os.path.join(self.task_output_dir, 'istanbul'))
482        self.assertEqual(len(istanbul_files), 2)
483
484
485if __name__ == '__main__':
486    unittest.main()
487