summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py')
-rw-r--r--python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py444
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()