pre-upload: improve KEYWORDS detection

The current code uses git diff which doesn't work with new files.
Since the majority of the tree is converted now, let's start forcing
people to update things even when they don't really touch KEYWORDS.

BUG=chromium:307180
TEST=unittest passes

Change-Id: I626a7493ee067997ef66311064e2b09236f539ed
Reviewed-on: https://chromium-review.googlesource.com/217025
Reviewed-by: Steve Fung <stevefung@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index f6ba150..8d5084b 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -592,7 +592,10 @@
                           ebuilds_re)
 
   for ebuild in ebuilds:
-    for _, line in _get_file_diff(ebuild, commit):
+    # We get the full content rather than a diff as the latter does not work
+    # on new files (like when adding new ebuilds).
+    lines = _get_file_content(ebuild, commit).splitlines()
+    for line in lines:
       m = get_keywords.match(line)
       if m:
         keywords = set(m.group(1).split())
diff --git a/pre-upload_unittest.py b/pre-upload_unittest.py
index ffc4777..6a5d968 100755
--- a/pre-upload_unittest.py
+++ b/pre-upload_unittest.py
@@ -207,6 +207,75 @@
     self.assertEqual(ret, None)
 
 
+class CheckEbuildKeywords(cros_test_lib.MockTestCase):
+  """Tests for _check_ebuild_keywords."""
+
+  def setUp(self):
+    self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
+    self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
+
+  def testNoEbuilds(self):
+    """If no ebuilds are found, do not scan."""
+    self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
+
+    ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
+    self.assertEqual(ret, None)
+
+    self.assertEqual(self.content_mock.call_count, 0)
+
+  def testSomeEbuilds(self):
+    """If ebuilds are found, only scan them."""
+    self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
+    self.content_mock.return_value = ''
+
+    ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
+    self.assertEqual(ret, None)
+
+    self.assertEqual(self.content_mock.call_count, 1)
+
+  def _CheckContent(self, content, fails):
+    """Test helper for inputs/outputs.
+
+    Args:
+      content: The ebuild content to test.
+      fails: Whether |content| should trigger a hook failure.
+    """
+    self.file_mock.return_value = ['a.ebuild']
+    self.content_mock.return_value = content
+
+    ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
+    if fails:
+      self.assertTrue(isinstance (ret, errors.HookFailure))
+    else:
+      self.assertEqual(ret, None)
+
+    self.assertEqual(self.content_mock.call_count, 1)
+
+  def testEmpty(self):
+    """Check KEYWORDS= is accepted."""
+    self._CheckContent('# HEADER\nKEYWORDS=\nblah\n', False)
+
+  def testEmptyQuotes(self):
+    """Check KEYWORDS="" is accepted."""
+    self._CheckContent('# HEADER\nKEYWORDS="    "\nblah\n', False)
+
+  def testStableGlob(self):
+    """Check KEYWORDS=* is accepted."""
+    self._CheckContent('# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
+
+  def testUnstableGlob(self):
+    """Check KEYWORDS=~* is accepted."""
+    self._CheckContent('# HEADER\nKEYWORDS="~* "\nblah\n', False)
+
+  def testRestrictedGlob(self):
+    """Check KEYWORDS=-* is accepted."""
+    self._CheckContent('# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
+
+  def testMissingGlobs(self):
+    """Reject KEYWORDS missing any globs."""
+    self._CheckContent('# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
+
+
 class CheckEbuildVirtualPv(cros_test_lib.MockTestCase):
   """Tests for _check_ebuild_virtual_pv."""