measure_patch_size.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright 2018 The Chromium OS Authors. All rights reserved.
  4. # Use of this source code is governed by a BSD-style license that can be
  5. # found in the LICENSE file.
  6. #
  7. """A tool for running diffing tools and measuring patch sizes."""
  8. import argparse
  9. import logging
  10. import os
  11. import subprocess
  12. import sys
  13. import tempfile
  14. class Error(Exception):
  15. """Puffin general processing error."""
  16. def ParseArguments(argv):
  17. """Parses and Validates command line arguments.
  18. Args:
  19. argv: command line arguments to parse.
  20. Returns:
  21. The arguments list.
  22. """
  23. parser = argparse.ArgumentParser()
  24. parser.add_argument('--src-corpus', metavar='DIR',
  25. help='The source corpus directory with compressed files.')
  26. parser.add_argument('--tgt-corpus', metavar='DIR',
  27. help='The target corpus directory with compressed files.')
  28. parser.add_argument('--debug', action='store_true',
  29. help='Turns on verbosity.')
  30. # Parse command-line arguments.
  31. args = parser.parse_args(argv)
  32. for corpus in (args.src_corpus, args.tgt_corpus):
  33. if not corpus or not os.path.isdir(corpus):
  34. raise Error('Corpus directory {} is non-existent or inaccesible'
  35. .format(corpus))
  36. return args
  37. def main(argv):
  38. """The main function."""
  39. args = ParseArguments(argv[1:])
  40. if args.debug:
  41. logging.getLogger().setLevel(logging.DEBUG)
  42. # Construct list of appropriate files.
  43. src_files = list(filter(os.path.isfile,
  44. [os.path.join(args.src_corpus, f)
  45. for f in os.listdir(args.src_corpus)]))
  46. tgt_files = list(filter(os.path.isfile,
  47. [os.path.join(args.tgt_corpus, f)
  48. for f in os.listdir(args.tgt_corpus)]))
  49. # Check if all files in src_files have a target file in tgt_files.
  50. files_mismatch = (set(map(os.path.basename, src_files)) -
  51. set(map(os.path.basename, tgt_files)))
  52. if files_mismatch:
  53. raise Error('Target files {} do not exist in corpus: {}'
  54. .format(files_mismatch, args.tgt_corpus))
  55. for src in src_files:
  56. with tempfile.NamedTemporaryFile() as puffdiff_patch, \
  57. tempfile.NamedTemporaryFile() as bsdiff_patch:
  58. tgt = os.path.join(args.tgt_corpus, os.path.basename(src))
  59. operation = 'puffdiff'
  60. cmd = ['puffin',
  61. '--operation={}'.format(operation),
  62. '--src_file={}'.format(src),
  63. '--dst_file={}'.format(tgt),
  64. '--patch_file={}'.format(puffdiff_patch.name)]
  65. # Running the puffdiff operation
  66. if subprocess.call(cmd) != 0:
  67. raise Error('Puffin failed to do {} command: {}'
  68. .format(operation, cmd))
  69. operation = 'bsdiff'
  70. cmd = ['bsdiff', '--type', 'bz2', src, tgt, bsdiff_patch.name]
  71. # Running the bsdiff operation
  72. if subprocess.call(cmd) != 0:
  73. raise Error('Failed to do {} command: {}'
  74. .format(operation, cmd))
  75. logging.debug('%s(%d -> %d) : bsdiff(%d), puffdiff(%d)',
  76. os.path.basename(src),
  77. os.stat(src).st_size, os.stat(tgt).st_size,
  78. os.stat(bsdiff_patch.name).st_size,
  79. os.stat(puffdiff_patch.name).st_size)
  80. return 0
  81. if __name__ == '__main__':
  82. sys.exit(main(sys.argv))