Bug 1296842: check parameters; r=jmaher
authorDustin J. Mitchell <dustin@mozilla.com>
Mon, 07 Nov 2016 19:13:40 +0000
changeset 351519 126467fa0641535597213dd494aec402bd47c5ff
parent 351518 f29234ab0d3c57a374824bec5278fe6c37f561be
child 351520 8988e17605ed135abd91f64a0c3c30b559d453ce
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher
bugs1296842
milestone52.0a1
Bug 1296842: check parameters; r=jmaher MozReview-Commit-ID: 1JCpufowNHD
taskcluster/mach_commands.py
taskcluster/taskgraph/parameters.py
taskcluster/taskgraph/test/test_parameters.py
--- a/taskcluster/mach_commands.py
+++ b/taskcluster/mach_commands.py
@@ -212,16 +212,17 @@ class MachCommands(MachCommandBase):
     def show_taskgraph(self, graph_attr, options):
         import taskgraph.parameters
         import taskgraph.target_tasks
         import taskgraph.generator
 
         try:
             self.setup_logging(quiet=options['quiet'], verbose=options['verbose'])
             parameters = taskgraph.parameters.load_parameters_file(options)
+            parameters.check()
 
             target_tasks_method = parameters.get('target_tasks_method', 'all_tasks')
             target_tasks_method = taskgraph.target_tasks.get_method(target_tasks_method)
             tgg = taskgraph.generator.TaskGraphGenerator(
                 root_dir=options['root'],
                 parameters=parameters,
                 target_tasks_method=target_tasks_method)
 
--- a/taskcluster/taskgraph/parameters.py
+++ b/taskcluster/taskgraph/parameters.py
@@ -5,20 +5,56 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import json
 import yaml
 from mozbuild.util import ReadOnlyDict
 
+# Please keep this list sorted and in sync with taskcluster/docs/parameters.rst
+PARAMETER_NAMES = set([
+    'base_repository',
+    'build_date',
+    'head_ref',
+    'head_repository',
+    'head_rev',
+    'level',
+    'message',
+    'moz_build_date',
+    'optimize_target_tasks',
+    'owner',
+    'project',
+    'pushdate',
+    'pushlog_id',
+    'target_tasks_method',
+    'triggered_by',
+])
+
 
 class Parameters(ReadOnlyDict):
     """An immutable dictionary with nicer KeyError messages on failure"""
+    def check(self):
+        names = set(self)
+        msg = []
+
+        missing = PARAMETER_NAMES - names
+        if missing:
+            msg.append("missing parameters: " + ", ".join(missing))
+
+        extra = names - PARAMETER_NAMES
+        if extra:
+            msg.append("extra parameters: " + ", ".join(extra))
+
+        if msg:
+            raise Exception("; ".join(msg))
+
     def __getitem__(self, k):
+        if k not in PARAMETER_NAMES:
+            raise KeyError("no such parameter {!r}".format(k))
         try:
             return super(Parameters, self).__getitem__(k)
         except KeyError:
             raise KeyError("taskgraph parameter {!r} not found".format(k))
 
 
 def load_parameters_file(options):
     """
--- a/taskcluster/taskgraph/test/test_parameters.py
+++ b/taskcluster/taskgraph/test/test_parameters.py
@@ -1,36 +1,55 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import unittest
 
-from ..parameters import Parameters, load_parameters_file
+from ..parameters import Parameters, load_parameters_file, PARAMETER_NAMES
 from mozunit import main, MockedOpen
 
 
 class TestParameters(unittest.TestCase):
 
+    vals = {n: n for n in PARAMETER_NAMES}
+
     def test_Parameters_immutable(self):
-        p = Parameters(x=10, y=20)
+        p = Parameters(**self.vals)
 
         def assign():
-            p['x'] = 20
+            p['head_ref'] = 20
         self.assertRaises(Exception, assign)
 
-    def test_Parameters_KeyError(self):
-        p = Parameters(x=10, y=20)
+    def test_Parameters_missing_KeyError(self):
+        p = Parameters(**self.vals)
         self.assertRaises(KeyError, lambda: p['z'])
 
+    def test_Parameters_invalid_KeyError(self):
+        """even if the value is present, if it's not a valid property, raise KeyError"""
+        p = Parameters(xyz=10, **self.vals)
+        self.assertRaises(KeyError, lambda: p['xyz'])
+
     def test_Parameters_get(self):
-        p = Parameters(x=10, y=20)
-        self.assertEqual(p['x'], 10)
+        p = Parameters(head_ref=10, level=20)
+        self.assertEqual(p['head_ref'], 10)
+
+    def test_Parameters_check(self):
+        p = Parameters(**self.vals)
+        p.check()  # should not raise
+
+    def test_Parameters_check_missing(self):
+        p = Parameters()
+        self.assertRaises(Exception, lambda: p.check())
+
+    def test_Parameters_check_extra(self):
+        p = Parameters(xyz=10, **self.vals)
+        self.assertRaises(Exception, lambda: p.check())
 
     def test_load_parameters_file_yaml(self):
         with MockedOpen({"params.yml": "some: data\n"}):
             self.assertEqual(
                     load_parameters_file({'parameters': 'params.yml'}),
                     {'some': 'data'})
 
     def test_load_parameters_file_json(self):