| #!/usr/bin/python |
| # |
| # Copyright (c) 2012 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. |
| |
| import argparse |
| import logging |
| import os |
| import re |
| import subprocess |
| import sys |
| from contextlib import contextmanager |
| |
| import factory_common # pylint: disable=W0611 |
| from cros.factory.test import utils |
| from cros.factory.tools.mount_partition import MountPartition |
| from cros.factory.utils.process_utils import Spawn |
| |
| |
| STATEFUL_PARTITION_INDEX = 1 |
| |
| BLACKLIST = map(re.compile, [ |
| 'autotest/deps/', |
| 'autotest/site_tests/.+/src/', |
| 'autotest/site_tests/audiovideo_V4L2/media_v4l2_.*test$', |
| ]) |
| |
| |
| def DiffImages(mount_point_1, mount_point_2, out=sys.stdout): |
| '''Compares two images. |
| |
| Args: |
| mount_point_1, mount_point_2: Mount points of stateful partitions of images |
| to compare. |
| out: Output to which differences should be printed. |
| ''' |
| mount_points = [mount_point_1, mount_point_2] |
| |
| # Hold in a variable since we want to change in an inner function. |
| differences = [0] |
| |
| for d in ['autotest', 'factory']: |
| process = Spawn( |
| ['diff', '-qr'] + |
| # Skip client directory in autotest, since it causes a recursive |
| # directory loop in diff. This isn't perfect since it skips |
| # *everything* called client, but it'll do. |
| (['-x', 'client'] if d == 'autotest' else []) + |
| [os.path.join(x, 'dev_image', d) for x in mount_points], |
| read_stdout=True, log=True, |
| check_call=lambda returncode: returncode in [0,1]) |
| |
| for line in process.stdout_lines(): |
| match = re.match('^Files (.+) and (.+) differ$|' |
| '^Only in (.+): (.+)$', |
| line) |
| assert match, 'Weird line in diff output: %r' % line |
| |
| if match.group(1): |
| # Files exist in both trees, but differ |
| paths = [match.group(1), match.group(2)] |
| else: |
| assert match.group(3) |
| path = os.path.join(match.group(3), match.group(4)) |
| if path.startswith(mount_points[0]): |
| paths = [path, None] |
| elif path.startswith(mount_points[1]): |
| paths = [None, path] |
| else: |
| assert False, ( |
| "path doesn't start with either of %s" % mount_points) |
| |
| stripped_paths = [] |
| for i in (0, 1): |
| if paths[i]: |
| prefix = os.path.join(mount_points[i], 'dev_image', '') |
| assert paths[i].startswith(prefix) |
| # Strip the prefix |
| stripped_paths.append(paths[i][len(prefix):]) |
| |
| assert all(x == stripped_paths[0] for x in stripped_paths), ( |
| stripped_paths) |
| |
| stripped_path = stripped_paths[0] |
| |
| blacklist_matches = [x for x in BLACKLIST |
| if x.match(stripped_path)] |
| if blacklist_matches: |
| logging.debug('Skipping %s since it matches %r', |
| stripped_path, |
| [x.pattern for x in blacklist_matches]) |
| continue |
| |
| def PrintHeader(message): |
| print >> out |
| print >> out, '*** %s' % stripped_path |
| print >> out, '*** %s' % message |
| differences[0] += 1 |
| |
| if any(x is None for x in paths): |
| # We only have one or the other |
| PrintHeader('Only in image%d' % (1 if paths[0] else 2)) |
| continue |
| |
| # It's a real difference. Are either or both symlinks? |
| is_symlink = map(os.path.islink, paths) |
| if is_symlink[0] != is_symlink[1]: |
| # That's a difference. |
| # pylint: disable=E9906 |
| PrintHeader('%s symlink in image1 but %s in image2' % |
| ('is' if x else 'is not' for x in is_symlink)) |
| elif is_symlink[0]: |
| link_paths = map(os.readlink, paths) |
| if link_paths[0] != link_paths[1]: |
| PrintHeader('symlink to %r in image1 but %r in image2' % |
| tuple(link_paths)) |
| else: |
| # They're both regular files; print a unified diff of the |
| # contents. |
| process = Spawn( |
| ['diff', '-u'] + paths, |
| check_call=lambda returncode: returncode in [0,1,2], |
| read_stdout=True) |
| if process.returncode == 2: |
| if re.match('Binary files .+ differ\n$', process.stdout_data): |
| PrintHeader('Binary files differ') |
| else: |
| raise subprocess.CalledProcessError(process.returncode, |
| process.args) |
| else: |
| PrintHeader('Files differ; unified diff follows') |
| out.write(process.stdout_data) |
| |
| return differences[0] |
| |
| |
| def main(argv=None, out=sys.stdout): |
| parser = argparse.ArgumentParser( |
| description=("Compares the autotest and factory directories in " |
| "two partitions.")) |
| parser.add_argument('--verbose', '-v', action='count') |
| parser.add_argument('images', metavar='IMAGE', nargs=2) |
| args = parser.parse_args(argv or sys.argv) |
| logging.basicConfig(level=logging.WARNING - 10 * (args.verbose or 0)) |
| |
| mount_points = ['/tmp/diff_image_1', '/tmp/diff_image_2'] |
| for f in mount_points: |
| utils.TryMakeDirs(f) |
| |
| def MountOrReuse(index): |
| if os.path.isdir(args.images[index]): |
| @contextmanager |
| def Image(): |
| yield args.images[index] |
| return Image() |
| else: |
| return MountPartition(args.images[index], STATEFUL_PARTITION_INDEX, |
| mount_points[index]) |
| |
| differences = 0 |
| with MountOrReuse(0) as mount_point_0: |
| with MountOrReuse(1) as mount_point_1: |
| differences += DiffImages(mount_point_0, mount_point_1, out) |
| |
| print >> out |
| print >> out, 'Found %d differences' % differences |
| sys.exit(0 if differences == 0 else 1) |
| |
| if __name__ == '__main__': |
| main() |