Bug 1486072 - Update and augment parsers tests r=janerik
authorChris H-C <chutten@mozilla.com>
Tue, 16 Jul 2019 18:27:28 +0000
changeset 543490 16fb6c5fae52185d7decf1e7b6ee27705e26a086
parent 543489 e7998a2833e1b8db0ef949961391728bafe3b240
child 543491 4a04f6178a17ddd30c271e22edb8cfbda6d293c6
push id11848
push userffxbld-merge
push dateMon, 26 Aug 2019 19:26:25 +0000
treeherdermozilla-beta@9b31bfdfac10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanerik
bugs1486072
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1486072 - Update and augment parsers tests r=janerik Differential Revision: https://phabricator.services.mozilla.com/D38192
toolkit/components/telemetry/tests/python/test_gen_event_data_json.py
toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py
toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py
toolkit/components/telemetry/tests/python/test_histogramtools_strict.py
toolkit/components/telemetry/tests/python/test_parse_events.py
toolkit/components/telemetry/tests/python/test_parse_scalars.py
toolkit/components/telemetry/tests/python/test_usecounters.py
--- a/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py
+++ b/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py
@@ -16,61 +16,68 @@ 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 = """
 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', 'geckoview']
     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
+                    "record_on_release": True,
+                    "products": ["firefox"],
                 }
             },
             "with.optin": {
                 "testme2": {
                     "objects": ["test2"],
                     "expired": False,
                     "expires": "never",
                     "methods": ["testme2"],
                     "extra_keys": ["message"],
-                    "record_on_release": False
+                    "record_on_release": False,
+                    "products": ["firefox", "fennec", "geckoview"],
                 }
             },
         }
 
         io = StringIO()
         try:
             tmpfile = tempfile.NamedTemporaryFile(suffix=".json", delete=False)
             # Write the event definition to the temporary file
--- a/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py
+++ b/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py
@@ -15,58 +15,65 @@ TELEMETRY_ROOT_PATH = path.abspath(path.
 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 = """
 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", "geckoview"]
     keyed: false
         """
 
         EXPECTED_JSON = {
             "newscalar": {
                 "withoptout": {
                     "kind": "nsITelemetry::SCALAR_TYPE_STRING",
                     "expired": False,
                     "expires": "never",
                     "record_on_release": True,
                     "keyed": False,
                     "stores": ["main"],
+                    "products": ["firefox", "fennec", "geckoview"],
                 },
                 "withoptin": {
                     "kind": "nsITelemetry::SCALAR_TYPE_COUNT",
                     "expired": False,
                     "expires": "never",
                     "record_on_release": False,
                     "keyed": False,
                     "stores": ["main"],
+                    "products": ["firefox"],
                 }
             }
         }
 
         io = StringIO()
         try:
             tmpfile = tempfile.NamedTemporaryFile(suffix=".json", delete=False)
             # Write the scalar definition to the temporary file
--- a/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py
+++ b/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py
@@ -71,11 +71,27 @@ class TestParser(unittest.TestCase):
                                                           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()
--- a/toolkit/components/telemetry/tests/python/test_histogramtools_strict.py
+++ b/toolkit/components/telemetry/tests/python/test_histogramtools_strict.py
@@ -21,16 +21,17 @@ class TestParser(unittest.TestCase):
     def test_valid_histogram(self):
         SAMPLE_HISTOGRAM = {
             "TEST_VALID_HISTOGRAM": {
                 "record_in_processes": ["main", "content", "socket"],
                 "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_whitelist()
 
         hist = parse_histograms.Histogram('TEST_VALID_HISTOGRAM',
                                           histograms['TEST_VALID_HISTOGRAM'],
@@ -44,16 +45,17 @@ class TestParser(unittest.TestCase):
 
     def test_missing_bug_numbers(self):
         SAMPLE_HISTOGRAM = {
             "TEST_HISTOGRAM_WHITELIST_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_whitelist()
 
         parse_histograms.Histogram('TEST_HISTOGRAM_WHITELIST_BUG_NUMBERS',
                                    histograms['TEST_HISTOGRAM_WHITELIST_BUG_NUMBERS'],
@@ -86,16 +88,17 @@ class TestParser(unittest.TestCase):
 
     def test_missing_alert_emails(self):
         SAMPLE_HISTOGRAM = {
             "TEST_HISTOGRAM_WHITELIST_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_whitelist()
 
         parse_histograms.Histogram('TEST_HISTOGRAM_WHITELIST_ALERT_EMAILS',
                                    histograms['TEST_HISTOGRAM_WHITELIST_ALERT_EMAILS'],
@@ -132,16 +135,17 @@ class TestParser(unittest.TestCase):
                 "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_whitelist()
 
         parse_histograms.Histogram('TEST_HISTOGRAM_WHITELIST_N_BUCKETS',
                                    histograms['TEST_HISTOGRAM_WHITELIST_N_BUCKETS'],
@@ -155,16 +159,17 @@ class TestParser(unittest.TestCase):
                 "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_whitelist()
 
         parse_histograms.Histogram('TEST_HISTOGRAM_WHITELIST_N_BUCKETS',
                                    histograms['TEST_HISTOGRAM_WHITELIST_N_BUCKETS'],
@@ -201,16 +206,17 @@ class TestParser(unittest.TestCase):
     def test_expiry_default(self):
         SAMPLE_HISTOGRAM = {
             "TEST_HISTOGRAM_WHITELIST_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_whitelist()
 
         parse_histograms.Histogram('TEST_HISTOGRAM_WHITELIST_EXPIRY_DEFAULT',
                                    histograms['TEST_HISTOGRAM_WHITELIST_EXPIRY_DEFAULT'],
@@ -245,16 +251,17 @@ class TestParser(unittest.TestCase):
         SAMPLE_HISTOGRAM = {
             "TEST_HISTOGRAM_WHITELIST_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_whitelist()
 
         self.assertRaises(SystemExit, parse_histograms.Histogram,
                           'TEST_HISTOGRAM_WHITELIST_KIND',
@@ -287,16 +294,17 @@ class TestParser(unittest.TestCase):
     def test_unsupported_kind_flag(self):
         SAMPLE_HISTOGRAM = {
             "TEST_HISTOGRAM_WHITELIST_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_whitelist()
 
         self.assertRaises(SystemExit, parse_histograms.Histogram,
                           'TEST_HISTOGRAM_WHITELIST_KIND',
@@ -330,16 +338,17 @@ class TestParser(unittest.TestCase):
         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_whitelist()
 
         hist = parse_histograms.Histogram('TEST_VALID_HISTOGRAM',
                                           histograms['TEST_VALID_HISTOGRAM'],
@@ -354,22 +363,82 @@ class TestParser(unittest.TestCase):
         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_whitelist()
 
         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_whitelist()
+
+        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_whitelist()
+
+        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_whitelist()
+
+        parse_histograms.Histogram('TEST_HISTOGRAM_ALL_PRODUCTS',
+                                   histograms['TEST_HISTOGRAM_ALL_PRODUCTS'],
+                                   strict_type_checks=True)
+        self.assertRaises(SystemExit, ParserError.exit_func)
+
 
 if __name__ == '__main__':
     mozunit.main()
--- a/toolkit/components/telemetry/tests/python/test_parse_events.py
+++ b/toolkit/components/telemetry/tests/python/test_parse_events.py
@@ -29,41 +29,43 @@ def load_event(event):
 class TestParser(unittest.TestCase):
     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, ["all"])
+        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)
 
@@ -96,11 +98,44 @@ operating_systems:
 
         self.assertEqual(evt.methods, ["method1", "method2"])
         self.assertEqual(evt.objects, ["object1", "object2"])
         self.assertEqual(evt.record_in_processes, ["content"])
         self.assertEqual(evt.products, ["geckoview"])
         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))
+
 
 if __name__ == '__main__':
     mozunit.main()
--- a/toolkit/components/telemetry/tests/python/test_parse_scalars.py
+++ b/toolkit/components/telemetry/tests/python/test_parse_scalars.py
@@ -32,16 +32,17 @@ class TestParser(unittest.TestCase):
 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)
@@ -53,16 +54,17 @@ bug_numbers:
         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)
@@ -73,16 +75,17 @@ bug_numbers:
         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)
@@ -96,16 +99,17 @@ 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,
@@ -120,16 +124,17 @@ 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)
@@ -138,16 +143,17 @@ record_into_store: []
         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)
@@ -161,16 +167,17 @@ 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)
@@ -184,20 +191,62 @@ 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)
+
 
 if __name__ == '__main__':
     mozunit.main()
--- a/toolkit/components/telemetry/tests/python/test_usecounters.py
+++ b/toolkit/components/telemetry/tests/python/test_usecounters.py
@@ -30,16 +30,17 @@ class TestParser(unittest.TestCase):
         parse_histograms.load_whitelist()
 
         hist = parse_histograms.Histogram('USE_COUNTER2_TEST_HISTOGRAM',
                                           histograms['USE_COUNTER2_TEST_HISTOGRAM'],
                                           strict_type_checks=True)
 
         ParserError.exit_func()
         self.assertEquals(hist.dataset(), "nsITelemetry::DATASET_ALL_CHANNELS")
+        self.assertEquals(hist.products(), ["firefox", "fennec", "geckoview"])
 
     def test_usecounter_histogram(self):
         SAMPLE_HISTOGRAM = {
             "USE_COUNTER2_TEST_HISTOGRAM": {
                 "expires_in_version": "never",
                 "kind": "boolean",
                 "description": "Whether a foo used bar"
             }
@@ -50,12 +51,13 @@ class TestParser(unittest.TestCase):
         hist = parse_histograms.Histogram('USE_COUNTER2_TEST_HISTOGRAM',
                                           histograms['USE_COUNTER2_TEST_HISTOGRAM'],
                                           strict_type_checks=True)
 
         ParserError.exit_func()
         self.assertEquals(hist.expiration(), "never")
         self.assertEquals(hist.kind(), "boolean")
         self.assertEquals(hist.description(), "Whether a foo used bar")
+        self.assertEquals(hist.products(), ["firefox", "fennec", "geckoview"])
 
 
 if __name__ == '__main__':
     mozunit.main()