Add test for au_generator.zip contents.

Add a new script that can be run (usually outside the chroot) to validate the
contents of au_generator.zip. This is mostly done to validate our handling of
dynamic library exports.

BUG=chromium:214885
TEST=Manual run + added to cbuildbot BuildImage stage.

Change-Id: I3051d622eebb67d27b0871379dfe8eb68c8da6ea
Reviewed-on: https://gerrit.chromium.org/gerrit/60172
Reviewed-by: Ben Chan <benchan@chromium.org>
Commit-Queue: Don Garrett <dgarrett@chromium.org>
Tested-by: Don Garrett <dgarrett@chromium.org>
diff --git a/build_library/generate_au_zip.py b/build_library/generate_au_zip.py
index dc4023f..6dba5d3 100755
--- a/build_library/generate_au_zip.py
+++ b/build_library/generate_au_zip.py
@@ -15,16 +15,18 @@
 
 # GLOBALS
 image_sign_dir = '~/trunk/src/platform/vboot_reference/scripts/image_signing'
-EXECUTABLE_FILES = [
+BINARY_EXECUTABLES = [
     '/usr/bin/old_bins/cgpt',
+    '/usr/bin/delta_generator',
+    '/usr/bin/bsdiff',
+    '/usr/bin/bspatch',
+    ]
+EXECUTABLE_FILES = BINARY_EXECUTABLES + [
     '~/trunk/src/scripts/common.sh',
     '/usr/bin/cros_generate_update_payload',
     '~/trunk/src/scripts/chromeos-common.sh',
     os.path.join(image_sign_dir, 'convert_recovery_to_ssd.sh'),
     os.path.join(image_sign_dir, 'common_minimal.sh'),
-    '/usr/bin/delta_generator',
-    '/usr/bin/bsdiff',
-    '/usr/bin/bspatch',
     ]
 # We need directories to be copied recursively to a dest within tempdir
 SHELL_LIBRARIES = {'~/trunk/src/scripts/lib/shflags': 'lib/shflags'}
diff --git a/build_library/test_au_zip.py b/build_library/test_au_zip.py
new file mode 100755
index 0000000..1e7f23d
--- /dev/null
+++ b/build_library/test_au_zip.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python
+
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Script to validate the output of generate_au_zip.py.
+
+This does NOT validate older versions of au_generator.zip, only the zip files
+generated by the matching version of generate_au_zip.py.
+"""
+
+import logging
+import optparse
+import os
+import shutil
+import subprocess
+import tempfile
+
+import generate_au_zip
+
+
+class TestFailure(Exception):
+  """An exception showing we failed to verify the current au-generator.zip."""
+  pass
+
+
+def FailWithError(msg):
+  """Fail the current test.
+
+  Args:
+    msg: User readable reason for failing the test.
+
+  Raises:
+    TestFailure always raised.
+  """
+  logging.error(msg)
+  raise TestFailure(msg)
+
+
+def ExpandAuGeneratorZip(zip_file, working_dir):
+  """Expand the au-generator.zip file out into a working directory.
+
+  Args:
+    zip_file: The file name of the zip file to expand.
+    working_dir: The directory into which to expand the zip file.
+
+  Raises:
+    TestFailure: Raised if the zip fails to expand.
+  """
+  cmd = ['unzip', '-o', '-d', working_dir, zip_file]
+  logging.debug('Extracting with: %s', ' '.join(cmd))
+  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+  p.communicate()
+  if p.returncode != 0:
+    FailWithError('Failed: %s' % ' '.join(cmd))
+
+
+def VerifyBinariesPresent(working_dir):
+  """Verify that all expected executables from the zip are present.
+
+  Check the expanded zip contents to see if all expected executable files
+  are present.
+
+  Args:
+    working_dir: Directory in which expected binaries should be findable.
+
+  Raises:
+    TestFailure if an expected executable is missing.
+  """
+  for src_filename in generate_au_zip.EXECUTABLE_FILES:
+    basename = os.path.basename(src_filename)
+    expected_name = os.path.join(working_dir, basename)
+    logging.debug('Expecting executable: %s', expected_name)
+
+    if not os.path.isfile(expected_name):
+      FailWithError('Expected file not found: %s' % expected_name)
+
+
+def VerifyLinking(working_dir):
+  """Verify that binary executables are executable outside of the chroot.
+
+  Run each of the binary executables outside of the chroot with --help and
+  see if they can startup and shutdown correctly. This mostly validates
+  that problems with dynamic linking are properly handled.
+
+  Args:
+    working_dir: Directory in which expected binaries should be present.
+
+  Raises:
+    TestFailure if an expected executable is missing.
+  """
+  for src_filename in generate_au_zip.BINARY_EXECUTABLES:
+    basename = os.path.basename(src_filename)
+    expected_name = os.path.join(working_dir, basename)
+
+    cmd = [expected_name, '--help']
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    output, _ = p.communicate()
+
+    # We expect --help to either succeed, or fail with 1.
+    if p.returncode not in (0, 1):
+      FailWithError('%s failed outside chroot with:\n%s' %
+                  (expected_name, output))
+
+
+def main():
+  """Main function to start the script"""
+  parser = optparse.OptionParser()
+
+  parser.add_option(
+      '-d', '--debug', dest='debug', action='store_true',
+      default=False, help='Verbose [%default]',)
+  parser.add_option(
+      '-o', '--output-dir', dest='output_dir',
+      default='/tmp/au-generator',
+      help='The output location for copying the zipfile [%default]')
+  parser.add_option(
+      '-z', '--zip-name', dest='zip_name',
+      default='au-generator.zip', help='Name of the zip file. [%default]')
+
+  logging_format = '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s'
+  date_format = '%Y/%m/%d %H:%M:%S'
+  logging.basicConfig(level=logging.INFO, format=logging_format,
+                      datefmt=date_format)
+
+  (options, _) = parser.parse_args()
+  if options.debug:
+    logging.getLogger().setLevel(logging.DEBUG)
+
+  logging.debug('Options are %s ', options)
+
+  working_dir = None
+  try:
+    working_dir = tempfile.mkdtemp(suffix='au', prefix='tmp')
+    logging.debug('Using tempdir = %s', working_dir)
+
+    zip_file = os.path.join(options.output_dir, options.zip_name)
+
+    ExpandAuGeneratorZip(zip_file, working_dir)
+    VerifyBinariesPresent(working_dir)
+    VerifyLinking(working_dir)
+
+    logging.info('SUCCESS for: %s', zip_file)
+  finally:
+    if working_dir:
+      shutil.rmtree(working_dir, ignore_errors=True)
+      logging.debug('Removed tempdir = %s', working_dir)
+
+if __name__ == '__main__':
+  main()