pre-upload: allow projects to turn on specific checks

The set of extended hook checks can only be turned on by hardcoding the
project in this pre upload script.  The PRESUBMIT.cfg file lets you turn
off specific checks, but only ones that were already explicitly enabled.
If you have a repo that wants to use an extended hook but isn't listed
here, then you can't.

Lets extend the PRESUBMIT.cfg so that it works as one might expect: you
can list any of the extended hook keys and, based on its value, explicitly
enable or disable the test.

BUG=chromium:466264
TEST=`./pre-upload_unittest.py` passes
TEST=made local changes to a PRESUBMIT.cfg and observed behavior

Change-Id: I0d7c4f9328484b62fa4f85c3746cc30344ed72ba
Reviewed-on: https://chromium-review.googlesource.com/258691
Tested-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index 5ba3695..f11d0d8 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -1155,8 +1155,8 @@
 
 
 # A dictionary of flags (keys) that can appear in the config file, and the hook
-# that the flag disables (value)
-_DISABLE_FLAGS = {
+# that the flag controls (value).
+_HOOK_FLAGS = {
     'stray_whitespace_check': _check_no_stray_whitespace,
     'long_line_check': _check_no_long_lines,
     'cros_license_check': _check_license,
@@ -1168,8 +1168,8 @@
 }
 
 
-def _get_disabled_hooks(config):
-  """Returns a set of hooks disabled by the current project's config file.
+def _get_override_hooks(config):
+  """Returns a set of hooks controlled by the current project's config file.
 
   Expects to be called within the project root.
 
@@ -1178,19 +1178,29 @@
   """
   SECTION = 'Hook Overrides'
   if not config.has_section(SECTION):
-    return set()
+    return set(), set()
 
+  valid_keys = set(_HOOK_FLAGS.iterkeys())
+
+  enable_flags = []
   disable_flags = []
   for flag in config.options(SECTION):
+    if flag not in valid_keys:
+      raise ValueError('Error: unknown key "%s" in hook section of "%s"' %
+                       (flag, _CONFIG_FILE))
+
     try:
       if not config.getboolean(SECTION, flag):
         disable_flags.append(flag)
+      else:
+        enable_flags.append(flag)
     except ValueError as e:
-      msg = "Error parsing flag \'%s\' in %s file - " % (flag, _CONFIG_FILE)
-      print(msg + str(e))
+      raise ValueError('Error: parsing flag "%s" in "%s" failed: %s' %
+                       (flag, _CONFIG_FILE, e))
 
-  disabled_keys = set(_DISABLE_FLAGS.iterkeys()).intersection(disable_flags)
-  return set([_DISABLE_FLAGS[key] for key in disabled_keys])
+  enabled_hooks = set(_HOOK_FLAGS[key] for key in enable_flags)
+  disabled_hooks = set(_HOOK_FLAGS[key] for key in disable_flags)
+  return enabled_hooks, disabled_hooks
 
 
 def _get_project_hook_scripts(config):
@@ -1229,8 +1239,9 @@
   else:
     hook_list = _PATCH_DESCRIPTION_HOOKS + _COMMON_HOOKS
 
-  disabled_hooks = _get_disabled_hooks(config)
-  hooks = [hook for hook in hook_list if hook not in disabled_hooks]
+  enabled_hooks, disabled_hooks = _get_override_hooks(config)
+  hooks = (list(enabled_hooks) +
+           [hook for hook in hook_list if hook not in disabled_hooks])
 
   if project in _PROJECT_SPECIFIC_HOOKS:
     hooks.extend(hook for hook in _PROJECT_SPECIFIC_HOOKS[project]