diff options
Diffstat (limited to 'toolkit/components/telemetry/tests/python')
8 files changed, 1429 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/python/python.ini b/toolkit/components/telemetry/tests/python/python.ini new file mode 100644 index 0000000000..f2301537f9 --- /dev/null +++ b/toolkit/components/telemetry/tests/python/python.ini @@ -0,0 +1,10 @@ +[DEFAULT] +subsuite = telemetry-python + +[test_gen_event_data_json.py] +[test_gen_scalar_data_json.py] +[test_histogramtools_non_strict.py] +[test_histogramtools_strict.py] +[test_parse_events.py] +[test_parse_scalars.py] +[test_usecounters.py] diff --git a/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py b/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py new file mode 100644 index 0000000000..3a2b374bf3 --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py @@ -0,0 +1,103 @@ +# 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 sys +import tempfile +import unittest +from io import StringIO +from os import path + +import mozunit + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The generators live in "build_scripts", account for that. +# NOTE: if the generators are moved, this logic will need to be updated. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +import gen_event_data # noqa: E402 + + +class TestEventDataJson(unittest.TestCase): + + maxDiff = None + + def test_JSON_definitions_generation(self): + EVENTS_YAML = b""" +with.optout: + testme1: + objects: ["test1"] + bug_numbers: [1456415] + notification_emails: ["telemetry-client-dev@mozilla.org"] + record_in_processes: ["main"] + description: opt-out event + release_channel_collection: opt-out + expiry_version: never + products: + - firefox + extra_keys: + message: a message 1 +with.optin: + testme2: + objects: ["test2"] + bug_numbers: [1456415] + notification_emails: ["telemetry-client-dev@mozilla.org"] + record_in_processes: ["main"] + description: opt-in event + release_channel_collection: opt-in + expiry_version: never + products: ['firefox', 'fennec'] + extra_keys: + message: a message 2 + """ + + EXPECTED_JSON = { + "with.optout": { + "testme1": { + "objects": ["test1"], + "expired": False, + "expires": "never", + "methods": ["testme1"], + "extra_keys": ["message"], + "record_on_release": True, + "products": ["firefox"], + } + }, + "with.optin": { + "testme2": { + "objects": ["test2"], + "expired": False, + "expires": "never", + "methods": ["testme2"], + "extra_keys": ["message"], + "record_on_release": False, + "products": ["firefox", "fennec"], + } + }, + } + + io = StringIO() + try: + tmpfile = tempfile.NamedTemporaryFile(suffix=".json", delete=False) + # Write the event definition to the temporary file + tmpfile.write(EVENTS_YAML) + tmpfile.close() + + # Let the parser generate the artifact definitions + gen_event_data.generate_JSON_definitions(io, tmpfile.name) + finally: + if tmpfile: + os.unlink(tmpfile.name) + + event_definitions = json.loads(io.getvalue()) + + # Check that it generated the correct data + self.assertEqual(EXPECTED_JSON, event_definitions) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py b/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py new file mode 100644 index 0000000000..1db5558a2e --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py @@ -0,0 +1,101 @@ +# 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 sys +import tempfile +import unittest +from io import StringIO +from os import path + +import mozunit + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The generators live in "build_scripts", account for that. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +import gen_scalar_data # noqa: E402 + + +class TestScalarDataJson(unittest.TestCase): + + maxDiff = None + + def test_JSON_definitions_generation(self): + SCALARS_YAML = b""" +newscalar: + withoptin: + bug_numbers: + - 1456415 + description: opt-in scalar + expires: never + kind: uint + notification_emails: ["telemetry-client-dev@mozilla.org"] + record_in_processes: ["main"] + release_channel_collection: opt-in + products: + - firefox + keyed: false + withoptout: + bug_numbers: + - 1456415 + description: opt-out scalar + expires: never + kind: string + notification_emails: ["telemetry-client-dev@mozilla.org"] + record_in_processes: ["main"] + release_channel_collection: opt-out + products: ["firefox", "fennec"] + keyed: false + """ + + EXPECTED_JSON = { + "newscalar": { + "withoptout": { + "kind": "nsITelemetry::SCALAR_TYPE_STRING", + "expired": False, + "expires": "never", + "record_on_release": True, + "keyed": False, + "keys": [], + "stores": ["main"], + "products": ["firefox", "fennec"], + }, + "withoptin": { + "kind": "nsITelemetry::SCALAR_TYPE_COUNT", + "expired": False, + "expires": "never", + "record_on_release": False, + "keyed": False, + "keys": [], + "stores": ["main"], + "products": ["firefox"], + }, + } + } + + io = StringIO() + try: + tmpfile = tempfile.NamedTemporaryFile(suffix=".json", delete=False) + # Write the scalar definition to the temporary file + tmpfile.write(SCALARS_YAML) + tmpfile.close() + + # Let the parser generate the artifact definitions + gen_scalar_data.generate_JSON_definitions(io, tmpfile.name) + finally: + if tmpfile: + os.unlink(tmpfile.name) + + scalar_definitions = json.loads(io.getvalue()) + + # Check that it generated the correct data + self.assertEqual(EXPECTED_JSON, scalar_definitions) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py b/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py new file mode 100644 index 0000000000..275690735c --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py @@ -0,0 +1,146 @@ +# 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 sys +import unittest +from os import path + +import mozunit + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The parsers live in a subdirectory of "build_scripts", account for that. +# NOTE: if the parsers are moved, this logic will need to be updated. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +from mozparsers import parse_histograms # noqa: E402 + + +def load_histogram(histograms): + """Parse the passed Histogram and return a dictionary mapping histogram + names to histogram parameters. + + :param histogram: Histogram as a python dictionary + :returns: Parsed Histogram dictionary mapping histogram names to histogram parameters + """ + + def hook(ps): + return parse_histograms.load_histograms_into_dict(ps, strict_type_checks=False) + + return json.loads(json.dumps(histograms), object_pairs_hook=hook) + + +class TestParser(unittest.TestCase): + def test_unknown_field(self): + SAMPLE_HISTOGRAM = { + "A11Y_INSTANTIATED_FLAG": { + "record_in_processes": ["main", "content"], + "expires_in_version": "never", + "kind": "flag", + "description": "has accessibility support been instantiated", + "new_field": "Its a new field", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + + hist = parse_histograms.Histogram( + "A11Y_INSTANTIATED_FLAG", + histograms["A11Y_INSTANTIATED_FLAG"], + strict_type_checks=False, + ) + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "flag") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + + def test_non_numeric_expressions(self): + SAMPLE_HISTOGRAM = { + "TEST_NON_NUMERIC_HISTOGRAM": { + "kind": "linear", + "description": "sample", + "n_buckets": "JS::GCReason::NUM_TELEMETRY_REASONS", + "high": "mozilla::StartupTimeline::MAX_EVENT_ID", + } + } + + histograms = load_histogram(SAMPLE_HISTOGRAM) + hist = parse_histograms.Histogram( + "TEST_NON_NUMERIC_HISTOGRAM", + histograms["TEST_NON_NUMERIC_HISTOGRAM"], + strict_type_checks=False, + ) + + # expected values come off parse_histograms.py + self.assertEqual(hist.n_buckets(), 101) + self.assertEqual(hist.high(), 12) + + def test_devtools_database_parsing(self): + db = path.join( + TELEMETRY_ROOT_PATH, + path.pardir, + path.pardir, + path.pardir, + "devtools", + "shared", + "css", + "generated", + "properties-db.js", + ) + + histograms = list(parse_histograms.from_files([db], strict_type_checks=False)) + histograms = [h.name() for h in histograms] + + # Test a shorthand (animation) + self.assertTrue("USE_COUNTER2_CSS_PROPERTY_Animation_DOCUMENT" in histograms) + + # Test a shorthand alias (-moz-animation). + self.assertTrue("USE_COUNTER2_CSS_PROPERTY_MozAnimation_DOCUMENT" in histograms) + + # Test a longhand (animation-name) + self.assertTrue( + "USE_COUNTER2_CSS_PROPERTY_AnimationName_DOCUMENT" in histograms + ) + + # Test a longhand alias (-moz-animation-name) + self.assertTrue( + "USE_COUNTER2_CSS_PROPERTY_MozAnimationName_DOCUMENT" in histograms + ) + + def test_current_histogram(self): + HISTOGRAMS_PATH = path.join(TELEMETRY_ROOT_PATH, "Histograms.json") + all_histograms = list( + parse_histograms.from_files([HISTOGRAMS_PATH], strict_type_checks=False) + ) + test_histogram = [ + i for i in all_histograms if i.name() == "TELEMETRY_TEST_FLAG" + ][0] + + self.assertEqual(test_histogram.expiration(), "never") + self.assertEqual(test_histogram.kind(), "flag") + self.assertEqual(test_histogram.record_in_processes(), ["main", "content"]) + self.assertEqual(test_histogram.keyed(), False) + + def test_no_products(self): + SAMPLE_HISTOGRAM = { + "TEST_EMPTY_PRODUCTS": { + "kind": "flag", + "description": "sample", + } + } + + histograms = load_histogram(SAMPLE_HISTOGRAM) + hist = parse_histograms.Histogram( + "TEST_EMPTY_PRODUCTS", + histograms["TEST_EMPTY_PRODUCTS"], + strict_type_checks=False, + ) + + self.assertEqual(hist.kind(), "flag") + # bug 1486072: absent `product` key becomes None instead of ["all"] + self.assertEqual(hist.products(), None) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/python/test_histogramtools_strict.py b/toolkit/components/telemetry/tests/python/test_histogramtools_strict.py new file mode 100644 index 0000000000..accafe90ad --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_histogramtools_strict.py @@ -0,0 +1,566 @@ +# 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 os +import sys +import unittest +from os import path + +import mozunit +from test_histogramtools_non_strict import load_histogram + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The parsers live in a subdirectory of "build_scripts", account for that. +# NOTE: if the parsers are moved, this logic will need to be updated. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +from mozparsers import parse_histograms +from mozparsers.shared_telemetry_utils import ParserError + + +class TestParser(unittest.TestCase): + def setUp(self): + def mockexit(x): + raise SystemExit(x) + + self.oldexit = os._exit + os._exit = mockexit + + def tearDown(self): + os._exit = self.oldexit + + def test_valid_histogram(self): + SAMPLE_HISTOGRAM = { + "TEST_VALID_HISTOGRAM": { + "record_in_processes": ["main", "content", "socket", "utility"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + hist = parse_histograms.Histogram( + "TEST_VALID_HISTOGRAM", + histograms["TEST_VALID_HISTOGRAM"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertTrue(hist.expiration(), "never") + self.assertTrue(hist.kind(), "boolean") + self.assertTrue(hist.record_in_processes, ["main", "content"]) + self.assertTrue(hist.record_into_store, ["main"]) + + def test_missing_bug_numbers(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_BUG_NUMBERS": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "expires_in_version": "never", + "kind": "boolean", + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_BUG_NUMBERS", + histograms["TEST_HISTOGRAM_ALLOWLIST_BUG_NUMBERS"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, ParserError.exit_func) + + # Set global allowlists for parse_histograms. + parse_histograms.allowlists = { + "alert_emails": [], + "bug_numbers": ["TEST_HISTOGRAM_ALLOWLIST_BUG_NUMBERS"], + "n_buckets": [], + "expiry_default": [], + "kind": [], + } + + hist = parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_BUG_NUMBERS", + histograms["TEST_HISTOGRAM_ALLOWLIST_BUG_NUMBERS"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "boolean") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + self.assertEqual(hist.keyed(), False) + + parse_histograms.allowlists = None + + def test_missing_alert_emails(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_ALERT_EMAILS": { + "record_in_processes": ["main", "content"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_ALERT_EMAILS", + histograms["TEST_HISTOGRAM_ALLOWLIST_ALERT_EMAILS"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, ParserError.exit_func) + + # Set global allowlists for parse_histograms. + parse_histograms.allowlists = { + "alert_emails": ["TEST_HISTOGRAM_ALLOWLIST_ALERT_EMAILS"], + "bug_numbers": [], + "n_buckets": [], + "expiry_default": [], + "kind": [], + } + + hist = parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_ALERT_EMAILS", + histograms["TEST_HISTOGRAM_ALLOWLIST_ALERT_EMAILS"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "boolean") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + self.assertEqual(hist.keyed(), False) + + parse_histograms.allowlists = None + + def test_high_value(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "exponential", + "low": 1024, + "high": 2 ** 64, + "n_buckets": 100, + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS", + histograms["TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_high_n_buckets(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "exponential", + "low": 1024, + "high": 16777216, + "n_buckets": 200, + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS", + histograms["TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, ParserError.exit_func) + + # Set global allowlists for parse_histograms. + parse_histograms.allowlists = { + "alert_emails": [], + "bug_numbers": [], + "n_buckets": ["TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS"], + "expiry_default": [], + "kind": [], + } + + hist = parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS", + histograms["TEST_HISTOGRAM_ALLOWLIST_N_BUCKETS"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "exponential") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + self.assertEqual(hist.keyed(), False) + self.assertEqual(hist.low(), 1024) + self.assertEqual(hist.high(), 16777216) + self.assertEqual(hist.n_buckets(), 200) + + parse_histograms.allowlists = None + + def test_expiry_default(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_EXPIRY_DEFAULT": { + "record_in_processes": ["main", "content"], + "expires_in_version": "default", + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "kind": "boolean", + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_EXPIRY_DEFAULT", + histograms["TEST_HISTOGRAM_ALLOWLIST_EXPIRY_DEFAULT"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, ParserError.exit_func) + + # Set global allowlists for parse_histograms. + parse_histograms.allowlists = { + "alert_emails": [], + "bug_numbers": [], + "n_buckets": [], + "expiry_default": ["TEST_HISTOGRAM_ALLOWLIST_EXPIRY_DEFAULT"], + "kind": [], + } + + hist = parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_EXPIRY_DEFAULT", + histograms["TEST_HISTOGRAM_ALLOWLIST_EXPIRY_DEFAULT"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "default") + self.assertEqual(hist.kind(), "boolean") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + self.assertEqual(hist.keyed(), False) + + parse_histograms.allowlists = None + + def test_unsupported_kind_count(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_KIND": { + "record_in_processes": ["main", "content"], + "expires_in_version": "never", + "kind": "count", + "releaseChannelCollection": "opt-out", + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + self.assertRaises( + SystemExit, + parse_histograms.Histogram, + "TEST_HISTOGRAM_ALLOWLIST_KIND", + histograms["TEST_HISTOGRAM_ALLOWLIST_KIND"], + strict_type_checks=True, + ) + + # Set global allowlists for parse_histograms. + parse_histograms.allowlists = { + "alert_emails": [], + "bug_numbers": [], + "n_buckets": [], + "expiry_default": [], + "kind": ["TEST_HISTOGRAM_ALLOWLIST_KIND"], + } + + hist = parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_KIND", + histograms["TEST_HISTOGRAM_ALLOWLIST_KIND"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "count") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + self.assertEqual(hist.keyed(), False) + + parse_histograms.allowlists = None + + def test_unsupported_kind_flag(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALLOWLIST_KIND": { + "record_in_processes": ["main", "content"], + "expires_in_version": "never", + "kind": "flag", + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + self.assertRaises( + SystemExit, + parse_histograms.Histogram, + "TEST_HISTOGRAM_ALLOWLIST_KIND", + histograms["TEST_HISTOGRAM_ALLOWLIST_KIND"], + strict_type_checks=True, + ) + + # Set global allowlists for parse_histograms. + parse_histograms.allowlists = { + "alert_emails": [], + "bug_numbers": [], + "n_buckets": [], + "expiry_default": [], + "kind": ["TEST_HISTOGRAM_ALLOWLIST_KIND"], + } + + hist = parse_histograms.Histogram( + "TEST_HISTOGRAM_ALLOWLIST_KIND", + histograms["TEST_HISTOGRAM_ALLOWLIST_KIND"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "flag") + self.assertEqual(hist.record_in_processes(), ["main", "content"]) + self.assertEqual(hist.keyed(), False) + + parse_histograms.allowlists = None + + def test_multistore(self): + SAMPLE_HISTOGRAM = { + "TEST_VALID_HISTOGRAM": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "description": "Test histogram", + "products": ["firefox"], + "record_into_store": ["main", "sync"], + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + hist = parse_histograms.Histogram( + "TEST_VALID_HISTOGRAM", + histograms["TEST_VALID_HISTOGRAM"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertTrue(hist.expiration(), "never") + self.assertTrue(hist.kind(), "boolean") + self.assertTrue(hist.record_into_store, ["main", "sync"]) + + def test_multistore_empty(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_EMPTY_MULTISTORE": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "description": "Test histogram", + "products": ["firefox"], + "record_into_store": [], + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_EMPTY_MULTISTORE", + histograms["TEST_HISTOGRAM_EMPTY_MULTISTORE"], + strict_type_checks=True, + ) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_products_absent(self): + SAMPLE_HISTOGRAM = { + "TEST_NO_PRODUCTS": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + def test_parse(): + return parse_histograms.Histogram( + "TEST_NO_PRODUCTS", + histograms["TEST_NO_PRODUCTS"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, test_parse) + + def test_products_empty(self): + SAMPLE_HISTOGRAM = { + "TEST_EMPTY_PRODUCTS": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "description": "Test histogram", + "products": [], + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + def test_parse(): + return parse_histograms.Histogram( + "TEST_EMPTY_PRODUCTS", + histograms["TEST_EMPTY_PRODUCTS"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, test_parse) + + def test_products_all(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_ALL_PRODUCTS": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "description": "Test histogram", + "products": ["all"], + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + parse_histograms.Histogram( + "TEST_HISTOGRAM_ALL_PRODUCTS", + histograms["TEST_HISTOGRAM_ALL_PRODUCTS"], + strict_type_checks=True, + ) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_gv_streaming_unsupported_kind(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_GV_STREAMING": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "boolean", + "description": "Test histogram", + "products": ["geckoview_streaming"], + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + parse_histograms.Histogram( + "TEST_HISTOGRAM_GV_STREAMING", + histograms["TEST_HISTOGRAM_GV_STREAMING"], + strict_type_checks=True, + ) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_gv_streaming_keyed(self): + SAMPLE_HISTOGRAM = { + "TEST_HISTOGRAM_GV_STREAMING": { + "record_in_processes": ["main", "content"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "exponential", + "low": 1024, + "high": 2 ** 64, + "n_buckets": 100, + "keyed": "true", + "description": "Test histogram", + "products": ["geckoview_streaming"], + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + parse_histograms.Histogram( + "TEST_HISTOGRAM_GV_STREAMING", + histograms["TEST_HISTOGRAM_GV_STREAMING"], + strict_type_checks=True, + ) + + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_enumerated_histogram_with_100_buckets(self): + SAMPLE_HISTOGRAM = { + "TEST_100_BUCKETS_HISTOGRAM": { + "record_in_processes": ["main", "content", "socket", "utility"], + "alert_emails": ["team@mozilla.xyz"], + "bug_numbers": [1383793], + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 100, + "products": ["firefox"], + "description": "Test histogram", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + hist = parse_histograms.Histogram( + "TEST_100_BUCKETS_HISTOGRAM", + histograms["TEST_100_BUCKETS_HISTOGRAM"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertTrue(hist.expiration(), "never") + self.assertTrue(hist.kind(), "enumerated") + self.assertTrue(hist.n_buckets(), 101) + self.assertTrue(hist.record_in_processes, ["main", "content"]) + self.assertTrue(hist.record_into_store, ["main"]) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/python/test_parse_events.py b/toolkit/components/telemetry/tests/python/test_parse_events.py new file mode 100644 index 0000000000..0b7b91efcc --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_parse_events.py @@ -0,0 +1,166 @@ +# This Source Code Form is subject to the terms of 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 os +import sys +import unittest +from os import path + +import mozunit +import yaml + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The parsers live in a subdirectory of "build_scripts", account for that. +# NOTE: if the parsers are moved, this logic will need to be updated. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +from mozparsers import parse_events +from mozparsers.shared_telemetry_utils import ParserError + + +def load_event(event): + """Parse the passed event and return a dictionary + + :param event: Event as YAML string + :returns: Parsed Event dictionary + """ + return yaml.safe_load(event) + + +class TestParser(unittest.TestCase): + def setUp(self): + def mockexit(x): + raise SystemExit(x) + + self.oldexit = os._exit + os._exit = mockexit + + def tearDown(self): + os._exit = self.oldexit + + def test_valid_event_defaults(self): + SAMPLE_EVENT = """ +objects: ["object1", "object2"] +bug_numbers: [12345] +notification_emails: ["test01@mozilla.com", "test02@mozilla.com"] +record_in_processes: ["main"] +description: This is a test entry for Telemetry. +products: ["firefox"] +expiry_version: never +""" + name = "test_event" + event = load_event(SAMPLE_EVENT) + evt = parse_events.EventData("CATEGORY", name, event, strict_type_checks=True) + ParserError.exit_func() + + self.assertEqual(evt.methods, [name]) + self.assertEqual(evt.record_in_processes, ["main"]) + self.assertEqual(evt.objects, ["object1", "object2"]) + self.assertEqual(evt.products, ["firefox"]) + self.assertEqual(evt.operating_systems, ["all"]) + self.assertEqual(evt.extra_keys, []) + + def test_wrong_collection(self): + SAMPLE_EVENT = """ +objects: ["object1", "object2"] +bug_numbers: [12345] +notification_emails: ["test01@mozilla.com", "test02@mozilla.com"] +record_in_processes: ["main"] +description: This is a test entry for Telemetry. +expiry_version: never +products: ["firefox"] +release_channel_collection: none +""" + event = load_event(SAMPLE_EVENT) + parse_events.EventData("CATEGORY", "test_event", event, strict_type_checks=True) + + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_valid_event_custom(self): + SAMPLE_EVENT = """ +methods: ["method1", "method2"] +objects: ["object1", "object2"] +bug_numbers: [12345] +notification_emails: ["test01@mozilla.com", "test02@mozilla.com"] +record_in_processes: ["content"] +description: This is a test entry for Telemetry. +expiry_version: never +extra_keys: + key1: test1 + key2: test2 +products: + - fennec +operating_systems: + - windows +""" + name = "test_event" + event = load_event(SAMPLE_EVENT) + evt = parse_events.EventData("CATEGORY", name, event, strict_type_checks=True) + ParserError.exit_func() + + self.assertEqual(evt.methods, ["method1", "method2"]) + self.assertEqual(evt.objects, ["object1", "object2"]) + self.assertEqual(evt.record_in_processes, ["content"]) + self.assertEqual(evt.products, ["fennec"]) + self.assertEqual(evt.operating_systems, ["windows"]) + self.assertEqual(sorted(evt.extra_keys), ["key1", "key2"]) + + def test_absent_products(self): + SAMPLE_EVENT = """ +methods: ["method1", "method2"] +objects: ["object1", "object2"] +bug_numbers: [12345] +notification_emails: ["test01@mozilla.com", "test02@mozilla.com"] +record_in_processes: ["content"] +description: This is a test entry for Telemetry. +expiry_version: never +""" + event = load_event(SAMPLE_EVENT) + self.assertRaises( + SystemExit, + lambda: parse_events.EventData( + "CATEGORY", "test_event", event, strict_type_checks=True + ), + ) + + def test_empty_products(self): + SAMPLE_EVENT = """ +methods: ["method1", "method2"] +objects: ["object1", "object2"] +bug_numbers: [12345] +notification_emails: ["test01@mozilla.com", "test02@mozilla.com"] +record_in_processes: ["content"] +description: This is a test entry for Telemetry. +products: [] +expiry_version: never +""" + event = load_event(SAMPLE_EVENT) + self.assertRaises( + SystemExit, + lambda: parse_events.EventData( + "CATEGORY", "test_event", event, strict_type_checks=True + ), + ) + + def test_geckoview_streaming_product(self): + SAMPLE_EVENT = """ +methods: ["method1", "method2"] +objects: ["object1", "object2"] +bug_numbers: [12345] +notification_emails: ["test01@mozilla.com", "test02@mozilla.com"] +record_in_processes: ["content"] +description: This is a test entry for Telemetry. +products: ["geckoview_streaming"] +expiry_version: never +""" + event = load_event(SAMPLE_EVENT) + parse_events.EventData("CATEGORY", "test_event", event, strict_type_checks=True) + + self.assertRaises(SystemExit, ParserError.exit_func) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/python/test_parse_scalars.py b/toolkit/components/telemetry/tests/python/test_parse_scalars.py new file mode 100644 index 0000000000..c699cdb4d8 --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_parse_scalars.py @@ -0,0 +1,267 @@ +# This Source Code Form is subject to the terms of 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 os +import sys +import unittest +from os import path + +import mozunit +import yaml + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The parsers live in a subdirectory of "build_scripts", account for that. +# NOTE: if the parsers are moved, this logic will need to be updated. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +from mozparsers import parse_scalars +from mozparsers.shared_telemetry_utils import ParserError + + +def load_scalar(scalar): + """Parse the passed Scalar and return a dictionary + + :param scalar: Scalar as YAML string + :returns: Parsed Scalar dictionary + """ + return yaml.safe_load(scalar) + + +class TestParser(unittest.TestCase): + def setUp(self): + def mockexit(x): + raise SystemExit(x) + + self.oldexit = os._exit + os._exit = mockexit + + def tearDown(self): + os._exit = self.oldexit + + def test_valid_email_address(self): + SAMPLE_SCALAR_VALID_ADDRESSES = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com + - test02@mozilla.com +products: ["firefox"] +bug_numbers: + - 12345 +""" + scalar = load_scalar(SAMPLE_SCALAR_VALID_ADDRESSES) + sclr = parse_scalars.ScalarType( + "CATEGORY", "PROVE", scalar, strict_type_checks=True + ) + ParserError.exit_func() + + self.assertEqual( + sclr.notification_emails, ["test01@mozilla.com", "test02@mozilla.com"] + ) + + def test_invalid_email_address(self): + SAMPLE_SCALAR_INVALID_ADDRESSES = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com, test02@mozilla.com +products: ["firefox"] +bug_numbers: + - 12345 +""" + scalar = load_scalar(SAMPLE_SCALAR_INVALID_ADDRESSES) + parse_scalars.ScalarType("CATEGORY", "PROVE", scalar, strict_type_checks=True) + + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_multistore_default(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +products: ["firefox"] +bug_numbers: + - 12345 +""" + scalar = load_scalar(SAMPLE_SCALAR) + sclr = parse_scalars.ScalarType( + "CATEGORY", "PROVE", scalar, strict_type_checks=True + ) + ParserError.exit_func() + + self.assertEqual(sclr.record_into_store, ["main"]) + + def test_multistore_extended(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +bug_numbers: + - 12345 +products: ["firefox"] +record_into_store: + - main + - sync +""" + scalar = load_scalar(SAMPLE_SCALAR) + sclr = parse_scalars.ScalarType( + "CATEGORY", "PROVE", scalar, strict_type_checks=True + ) + ParserError.exit_func() + + self.assertEqual(sclr.record_into_store, ["main", "sync"]) + + def test_multistore_empty(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +bug_numbers: + - 12345 +products: ["firefox"] +record_into_store: [] +""" + scalar = load_scalar(SAMPLE_SCALAR) + parse_scalars.ScalarType("CATEGORY", "PROVE", scalar, strict_type_checks=True) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_operating_systems_default(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +products: ["firefox"] +bug_numbers: + - 12345 +""" + scalar = load_scalar(SAMPLE_SCALAR) + sclr = parse_scalars.ScalarType( + "CATEGORY", "PROVE", scalar, strict_type_checks=True + ) + ParserError.exit_func() + + self.assertEqual(sclr.operating_systems, ["all"]) + + def test_operating_systems_custom(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +bug_numbers: + - 12345 +products: ["firefox"] +operating_systems: + - windows +""" + scalar = load_scalar(SAMPLE_SCALAR) + sclr = parse_scalars.ScalarType( + "CATEGORY", "PROVE", scalar, strict_type_checks=True + ) + ParserError.exit_func() + + self.assertEqual(sclr.operating_systems, ["windows"]) + + def test_operating_systems_empty(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +bug_numbers: + - 12345 +products: ["firefox"] +operating_systems: [] +""" + scalar = load_scalar(SAMPLE_SCALAR) + parse_scalars.ScalarType("CATEGORY", "PROVE", scalar, strict_type_checks=True) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_products_absent(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +bug_numbers: + - 12345 +""" + + scalar = load_scalar(SAMPLE_SCALAR) + parse_scalars.ScalarType("CATEGORY", "PROVE", scalar, strict_type_checks=True) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_products_empty(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +products: [] +bug_numbers: + - 12345 +""" + + scalar = load_scalar(SAMPLE_SCALAR) + parse_scalars.ScalarType("CATEGORY", "PROVE", scalar, strict_type_checks=True) + self.assertRaises(SystemExit, ParserError.exit_func) + + def test_gv_streaming_keyed(self): + SAMPLE_SCALAR = """ +description: A nice one-line description. +expires: never +record_in_processes: + - 'main' +kind: uint +notification_emails: + - test01@mozilla.com +products: ['geckoview_streaming'] +keyed: true +bug_numbers: + - 12345 +""" + + scalar = load_scalar(SAMPLE_SCALAR) + parse_scalars.ScalarType("CATEGORY", "PROVE", scalar, strict_type_checks=True) + self.assertRaises(SystemExit, ParserError.exit_func) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/python/test_usecounters.py b/toolkit/components/telemetry/tests/python/test_usecounters.py new file mode 100644 index 0000000000..643bba27d7 --- /dev/null +++ b/toolkit/components/telemetry/tests/python/test_usecounters.py @@ -0,0 +1,70 @@ +# 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 sys +import unittest +from os import path + +import mozunit +from test_histogramtools_non_strict import load_histogram + +TELEMETRY_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(TELEMETRY_ROOT_PATH) +# The parsers live in a subdirectory of "build_scripts", account for that. +# NOTE: if the parsers are moved, this logic will need to be updated. +sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts")) +from mozparsers import parse_histograms +from mozparsers.shared_telemetry_utils import ParserError + + +class TestParser(unittest.TestCase): + def test_usecounter_collection_enabled(self): + SAMPLE_HISTOGRAM = { + "USE_COUNTER2_TEST_HISTOGRAM": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Whether a foo used bar", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + hist = parse_histograms.Histogram( + "USE_COUNTER2_TEST_HISTOGRAM", + histograms["USE_COUNTER2_TEST_HISTOGRAM"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.dataset(), "nsITelemetry::DATASET_ALL_CHANNELS") + self.assertEqual(hist.products(), ["firefox", "fennec"]) + + def test_usecounter_histogram(self): + SAMPLE_HISTOGRAM = { + "USE_COUNTER2_TEST_HISTOGRAM": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Whether a foo used bar", + } + } + histograms = load_histogram(SAMPLE_HISTOGRAM) + parse_histograms.load_allowlist() + + hist = parse_histograms.Histogram( + "USE_COUNTER2_TEST_HISTOGRAM", + histograms["USE_COUNTER2_TEST_HISTOGRAM"], + strict_type_checks=True, + ) + + ParserError.exit_func() + self.assertEqual(hist.expiration(), "never") + self.assertEqual(hist.kind(), "boolean") + self.assertEqual(hist.description(), "Whether a foo used bar") + self.assertEqual(hist.products(), ["firefox", "fennec"]) + + +if __name__ == "__main__": + mozunit.main() |