summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/python
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/tests/python')
-rw-r--r--toolkit/components/telemetry/tests/python/python.toml14
-rw-r--r--toolkit/components/telemetry/tests/python/test_gen_event_data_json.py102
-rw-r--r--toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py100
-rw-r--r--toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py114
-rw-r--r--toolkit/components/telemetry/tests/python/test_histogramtools_strict.py566
-rw-r--r--toolkit/components/telemetry/tests/python/test_parse_events.py166
-rw-r--r--toolkit/components/telemetry/tests/python/test_parse_scalars.py267
7 files changed, 1329 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/python/python.toml b/toolkit/components/telemetry/tests/python/python.toml
new file mode 100644
index 0000000000..d29eeb0a20
--- /dev/null
+++ b/toolkit/components/telemetry/tests/python/python.toml
@@ -0,0 +1,14 @@
+[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"]
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..5caa4cebbc
--- /dev/null
+++ b/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py
@@ -0,0 +1,102 @@
+# 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..ac60e2ae10
--- /dev/null
+++ b/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py
@@ -0,0 +1,100 @@
+# 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..6c8a3ed62d
--- /dev/null
+++ b/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py
@@ -0,0 +1,114 @@
+# 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_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..2109cd7d35
--- /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()