diff options
Diffstat (limited to 'python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py')
-rw-r--r-- | python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py b/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py new file mode 100644 index 0000000000..9009300951 --- /dev/null +++ b/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py @@ -0,0 +1,444 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import shutil +import unittest +from io import StringIO +from tempfile import NamedTemporaryFile + +import buildconfig +import mozunit + +from mozbuild.codecoverage import chrome_map, lcov_rewriter + +here = os.path.dirname(__file__) + +BUILDCONFIG = { + "topobjdir": buildconfig.topobjdir, + "MOZ_APP_NAME": buildconfig.substs.get("MOZ_APP_NAME", "nightly"), + "OMNIJAR_NAME": buildconfig.substs.get("OMNIJAR_NAME", "omni.ja"), + "MOZ_MACBUNDLE_NAME": buildconfig.substs.get("MOZ_MACBUNDLE_NAME", "Nightly.app"), +} + +basic_file = """TN:Compartment_5f7f5c30251800 +SF:resource://gre/modules/osfile.jsm +FN:1,top-level +FNDA:1,top-level +FNF:1 +FNH:1 +BRDA:9,0,61,1 +BRF:1 +BRH:1 +DA:9,1 +DA:24,1 +LF:2 +LH:2 +end_of_record +""" + +# These line numbers are (synthetically) sorted. +multiple_records = """SF:resource://gre/modules/workers/require.js +FN:1,top-level +FN:80,.get +FN:95,require +FNDA:1,top-level +FNF:3 +FNH:1 +BRDA:46,0,16,- +BRDA:135,225,446,- +BRF:2 +BRH:0 +DA:43,1 +DA:46,1 +DA:152,0 +DA:163,1 +LF:4 +LH:3 +end_of_record +SF:resource://gre/modules/osfile/osfile_async_worker.js +FN:12,top-level +FN:30,worker.dispatch +FN:34,worker.postMessage +FN:387,do_close +FN:392,exists +FN:394,do_exists +FN:400,unixSymLink +FNDA:1,do_exists +FNDA:1,exists +FNDA:1,top-level +FNDA:594,worker.dispatch +FNF:7 +FNH:4 +BRDA:6,0,30,1 +BRDA:365,0,103,- +BRF:2 +BRH:1 +DA:6,1 +DA:7,0 +DA:12,1 +DA:18,1 +DA:19,1 +DA:20,1 +DA:23,1 +DA:25,1 +DA:401,0 +DA:407,1 +LF:10 +LH:8 +end_of_record +""" + +fn_with_multiple_commas = """TN:Compartment_5f7f5c30251800 +SF:resource://gre/modules/osfile.jsm +FN:1,function,name,with,commas +FNDA:1,function,name,with,commas +FNF:1 +FNH:1 +BRDA:9,0,61,1 +BRF:1 +BRH:1 +DA:9,1 +DA:24,1 +LF:2 +LH:2 +end_of_record +""" + + +class TempFile: + def __init__(self, content): + self.file = NamedTemporaryFile(mode="w", delete=False, encoding="utf-8") + self.file.write(content) + self.file.close() + + def __enter__(self): + return self.file.name + + def __exit__(self, *args): + os.remove(self.file.name) + + +class TestLcovParser(unittest.TestCase): + def parser_roundtrip(self, lcov_string): + with TempFile(lcov_string) as fname: + file_obj = lcov_rewriter.LcovFile([fname]) + out = StringIO() + file_obj.print_file(out, lambda s: (s, None), lambda x, pp: x) + + return out.getvalue() + + def test_basic_parse(self): + output = self.parser_roundtrip(basic_file) + self.assertEqual(basic_file, output) + + output = self.parser_roundtrip(multiple_records) + self.assertEqual(multiple_records, output) + + def test_multiple_commas(self): + output = self.parser_roundtrip(fn_with_multiple_commas) + self.assertEqual(fn_with_multiple_commas, output) + + +multiple_included_files = """//@line 1 "/src/dir/foo.js" +bazfoobar +//@line 2 "/src/dir/path/bar.js" +@foo@ +//@line 3 "/src/dir/foo.js" +bazbarfoo +//@line 2 "/src/dir/path/bar.js" +foobarbaz +//@line 3 "/src/dir/path2/test.js" +barfoobaz +//@line 1 "/src/dir/path/baz.js" +baz +//@line 6 "/src/dir/f.js" +fin +""" + +srcdir_prefix_files = """//@line 1 "/src/dir/foo.js" +bazfoobar +//@line 2 "$SRCDIR/path/file.js" +@foo@ +//@line 3 "/src/dir/foo.js" +bazbarfoo +""" + + +class TestLineRemapping(unittest.TestCase): + def setUp(self): + chrome_map_file = os.path.join(buildconfig.topobjdir, "chrome-map.json") + self._old_chrome_info_file = None + if os.path.isfile(chrome_map_file): + backup_file = os.path.join(buildconfig.topobjdir, "chrome-map-backup.json") + self._old_chrome_info_file = backup_file + self._chrome_map_file = chrome_map_file + shutil.move(chrome_map_file, backup_file) + + empty_chrome_info = [ + {}, + {}, + {}, + BUILDCONFIG, + ] + with open(chrome_map_file, "w") as fh: + json.dump(empty_chrome_info, fh) + + self.lcov_rewriter = lcov_rewriter.LcovFileRewriter(chrome_map_file, "", "", []) + self.pp_rewriter = self.lcov_rewriter.pp_rewriter + + def tearDown(self): + if self._old_chrome_info_file: + shutil.move(self._old_chrome_info_file, self._chrome_map_file) + + def test_map_multiple_included(self): + with TempFile(multiple_included_files) as fname: + actual = chrome_map.generate_pp_info(fname, "/src/dir") + expected = { + "2,3": ("foo.js", 1), + "4,5": ("path/bar.js", 2), + "6,7": ("foo.js", 3), + "8,9": ("path/bar.js", 2), + "10,11": ("path2/test.js", 3), + "12,13": ("path/baz.js", 1), + "14,15": ("f.js", 6), + } + + self.assertEqual(actual, expected) + + def test_map_srcdir_prefix(self): + with TempFile(srcdir_prefix_files) as fname: + actual = chrome_map.generate_pp_info(fname, "/src/dir") + expected = { + "2,3": ("foo.js", 1), + "4,5": ("path/file.js", 2), + "6,7": ("foo.js", 3), + } + + self.assertEqual(actual, expected) + + def test_remap_lcov(self): + pp_remap = { + "1941,2158": ("dropPreview.js", 6), + "2159,2331": ("updater.js", 6), + "2584,2674": ("intro.js", 6), + "2332,2443": ("undo.js", 6), + "864,985": ("cells.js", 6), + "2444,2454": ("search.js", 6), + "1567,1712": ("drop.js", 6), + "2455,2583": ("customize.js", 6), + "1713,1940": ("dropTargetShim.js", 6), + "1402,1548": ("drag.js", 6), + "1549,1566": ("dragDataHelper.js", 6), + "453,602": ("page.js", 141), + "2675,2678": ("newTab.js", 70), + "56,321": ("transformations.js", 6), + "603,863": ("grid.js", 6), + "322,452": ("page.js", 6), + "986,1401": ("sites.js", 6), + } + + fpath = os.path.join(here, "sample_lcov.info") + + # Read original records + lcov_file = lcov_rewriter.LcovFile([fpath]) + records = [lcov_file.parse_record(r) for _, _, r in lcov_file.iterate_records()] + + # This summarization changes values due multiple reports per line coming + # from the JS engine (bug 1198356). + for r in records: + r.resummarize() + original_line_count = r.line_count + original_covered_line_count = r.covered_line_count + original_function_count = r.function_count + original_covered_function_count = r.covered_function_count + + self.assertEqual(len(records), 1) + + # Rewrite preprocessed entries. + lcov_file = lcov_rewriter.LcovFile([fpath]) + r_num = [] + + def rewrite_source(s): + r_num.append(1) + return s, pp_remap + + out = StringIO() + lcov_file.print_file(out, rewrite_source, self.pp_rewriter.rewrite_record) + self.assertEqual(len(r_num), 1) + + # Read rewritten lcov. + with TempFile(out.getvalue()) as fname: + lcov_file = lcov_rewriter.LcovFile([fname]) + records = [ + lcov_file.parse_record(r) for _, _, r in lcov_file.iterate_records() + ] + + self.assertEqual(len(records), 17) + + # Lines/functions are only "moved" between records, not duplicated or omited. + self.assertEqual(original_line_count, sum(r.line_count for r in records)) + self.assertEqual( + original_covered_line_count, sum(r.covered_line_count for r in records) + ) + self.assertEqual( + original_function_count, sum(r.function_count for r in records) + ) + self.assertEqual( + original_covered_function_count, + sum(r.covered_function_count for r in records), + ) + + +class TestUrlFinder(unittest.TestCase): + def setUp(self): + chrome_map_file = os.path.join(buildconfig.topobjdir, "chrome-map.json") + self._old_chrome_info_file = None + if os.path.isfile(chrome_map_file): + backup_file = os.path.join(buildconfig.topobjdir, "chrome-map-backup.json") + self._old_chrome_info_file = backup_file + self._chrome_map_file = chrome_map_file + shutil.move(chrome_map_file, backup_file) + + dummy_chrome_info = [ + { + "resource://activity-stream/": [ + "dist/bin/browser/chrome/browser/res/activity-stream", + ], + "chrome://browser/content/": [ + "dist/bin/browser/chrome/browser/content/browser", + ], + }, + { + "chrome://global/content/license.html": "chrome://browser/content/license.html", + }, + { + "dist/bin/components/MainProcessSingleton.js": ["path1", None], + "dist/bin/browser/features/firefox@getpocket.com/bootstrap.js": [ + "path4", + None, + ], + "dist/bin/modules/osfile/osfile_async_worker.js": [ + "toolkit/components/osfile/modules/osfile_async_worker.js", + None, + ], + "dist/bin/browser/chrome/browser/res/activity-stream/lib/": [ + "browser/components/newtab/lib/*", + None, + ], + "dist/bin/browser/chrome/browser/content/browser/license.html": [ + "browser/base/content/license.html", + None, + ], + "dist/bin/modules/AppConstants.sys.mjs": [ + "toolkit/modules/AppConstants.sys.mjs", + { + "101,102": ["toolkit/modules/AppConstants.sys.mjs", 135], + }, + ], + }, + BUILDCONFIG, + ] + with open(chrome_map_file, "w") as fh: + json.dump(dummy_chrome_info, fh) + + def tearDown(self): + if self._old_chrome_info_file: + shutil.move(self._old_chrome_info_file, self._chrome_map_file) + + def test_jar_paths(self): + app_name = BUILDCONFIG["MOZ_APP_NAME"] + omnijar_name = BUILDCONFIG["OMNIJAR_NAME"] + + paths = [ + ( + "jar:file:///home/worker/workspace/build/application/" + + app_name + + "/" + + omnijar_name + + "!/components/MainProcessSingleton.js", + "path1", + ), + ( + "jar:file:///home/worker/workspace/build/application/" + + app_name + + "/browser/features/firefox@getpocket.com.xpi!/bootstrap.js", + "path4", + ), + ] + + url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, "", "", []) + for path, expected in paths: + self.assertEqual(url_finder.rewrite_url(path)[0], expected) + + def test_wrong_scheme_paths(self): + paths = [ + "http://www.mozilla.org/aFile.js", + "https://www.mozilla.org/aFile.js", + "data:something", + "about:newtab", + "javascript:something", + ] + + url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, "", "", []) + for path in paths: + self.assertIsNone(url_finder.rewrite_url(path)) + + def test_chrome_resource_paths(self): + paths = [ + # Path with default url prefix + ( + "resource://gre/modules/osfile/osfile_async_worker.js", + ("toolkit/components/osfile/modules/osfile_async_worker.js", None), + ), + # Path with url prefix that is in chrome map + ( + "resource://activity-stream/lib/PrefsFeed.jsm", + ("browser/components/newtab/lib/PrefsFeed.jsm", None), + ), + # Path which is in url overrides + ( + "chrome://global/content/license.html", + ("browser/base/content/license.html", None), + ), + # Path which ends with > eval + ( + "resource://gre/modules/osfile/osfile_async_worker.js line 3 > eval", + None, + ), + # Path which ends with > Function + ( + "resource://gre/modules/osfile/osfile_async_worker.js line 3 > Function", + None, + ), + # Path which contains "->" + ( + "resource://gre/modules/addons/XPIProvider.jsm -> resource://gre/modules/osfile/osfile_async_worker.js", # noqa + ("toolkit/components/osfile/modules/osfile_async_worker.js", None), + ), + # Path with pp_info + ( + "resource://gre/modules/AppConstants.sys.mjs", + ( + "toolkit/modules/AppConstants.sys.mjs", + { + "101,102": ["toolkit/modules/AppConstants.sys.mjs", 135], + }, + ), + ), + # Path with query + ( + "resource://activity-stream/lib/PrefsFeed.jsm?q=0.9098419174803978", + ("browser/components/newtab/lib/PrefsFeed.jsm", None), + ), + ] + + url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, "", "dist/bin/", []) + for path, expected in paths: + self.assertEqual(url_finder.rewrite_url(path), expected) + + +if __name__ == "__main__": + mozunit.main() |