make autotest_quickmerge remove stale test packages

This CL makes autotest_quickmerge search through its itemized changes
for changes to files within subdirectories of client/site_tests. From
the subdirectories of any new or modified files, it pulls the name of
the corresponding test package, and then deletes the premade pacakge
from /[sysroot]/usr/local/autotest/packages if that file exists.

BUG=chromium:233306
TEST=unit test for pacakge name parsing. Also, manual test as follows:
`build_packages`
edit client/site_tests/dummy_Pass/dummy_Pass.py to throw an error in
run_once (ie fail immediately on client side)
`autotest_quickmerge --board=[board]`
`run_remote_tests.sh --remote=[remote ip] --use_emerged
^client/site_tests/dummy_Pass/control$`
Test fails. Prior to this CL, test would still pass since the
pre-existing version of dummy_Pass.py was stored in bzipped package, and
was being used by autoserv instead of the version in the sysroot client/
tree.

Change-Id: Ib0e5a20dc9d3eba31e1160d75274b6bf1027601b
Reviewed-on: https://gerrit.chromium.org/gerrit/48550
Reviewed-by: Aviv Keshet <akeshet@chromium.org>
Tested-by: Aviv Keshet <akeshet@chromium.org>
Commit-Queue: Aviv Keshet <akeshet@chromium.org>
diff --git a/scripts/autotest_quickmerge.py b/scripts/autotest_quickmerge.py
index db6d5d7..a9fc19a 100755
--- a/scripts/autotest_quickmerge.py
+++ b/scripts/autotest_quickmerge.py
@@ -51,6 +51,30 @@
                                    'new_directories'])
 
 
+def GetStalePackageNames(change_list, autotest_sysroot):
+  """
+  Given a rsync change report, returns the names of stale test packages.
+
+  This function pulls out test package names for client-side tests, stored
+  within the client/site_tests directory tree, that had any files added or
+  modified and for whom any existing bzipped test packages may now be stale.
+
+  Arguments:
+    change_list: A list of ItemizedChange objects corresponding to changed
+                 or modified files.
+    autotest_sysroot: Absolute path of autotest in the sysroot,
+                      e.g. '/build/lumpy/usr/local/autotest'
+
+  Returns:
+    A list of test package names, eg ['factory_Leds', 'login_UserPolicyKeys'].
+    May contain duplicate entries if multiple files within a test directory
+    were modified.
+  """
+  exp = os.path.abspath(autotest_sysroot) + r'/client/site_tests/(.*?)/.*'
+  matches = [re.match(exp, change.absolute_path) for change in change_list]
+  return [match.group(1) for match in matches if match]
+
+
 def ItemizeChangesFromRsyncOutput(rsync_output, destination_path):
   """Convert the output of an rsync with `-i` to a ItemizedChangeReport object.
 
@@ -184,6 +208,29 @@
   vartree.dbapi.writeContentsToContentsFile(package, contents)
 
 
+def RemoveTestPackages(stale_packages, autotest_sysroot):
+  """
+  Remove bzipped test packages from sysroot.
+
+  Arguments:
+    stale_packages: List of test packages names to be removed.
+                    e.g. ['factory_Leds', 'login_UserPolicyKeys']
+    autotest_sysroot: Absolute path of autotest in the sysroot,
+                      e.g. '/build/lumpy/usr/local/autotest'
+  """
+  for package in set(stale_packages):
+    package_filename = 'test-' + package + '.tar.bz2'
+    package_file_fullpath = os.path.join(autotest_sysroot, 'packages',
+        package_filename)
+    try:
+      os.remove(package_file_fullpath)
+      logging.info('Removed stale %s', package_file_fullpath)
+    except OSError as err:
+      # Suppress no-such-file exceptions. Raise all others.
+      if err.errno != 2:
+        raise
+
+
 def RsyncQuickmerge(source_path, sysroot_autotest_path,
                     include_pattern_file=None, pretend=False,
                     overwrite=False):
@@ -288,6 +335,10 @@
     if DowngradePackageVersion(sysroot_path, AUTOTEST_TESTS_EBUILD) != 0:
       logging.warning('Unable to downgrade package %s version number.',
           AUTOTEST_TESTS_EBUILD)
+    stale_packages = GetStalePackageNames(
+        change_report.new_files + change_report.modified_files,
+        sysroot_autotest_path)
+    RemoveTestPackages(stale_packages, sysroot_autotest_path)
 
   if args.pretend:
     logging.info('The following message is pretend only. No filesystem '