Home | History | Annotate | Download | only in rebaseline_server
      1 #!/usr/bin/python
      2 
      3 """
      4 Copyright 2014 Google Inc.
      5 
      6 Use of this source code is governed by a BSD-style license that can be
      7 found in the LICENSE file.
      8 
      9 A wrapper around the standard Python unittest library, adding features we need
     10 for various unittests within this directory.
     11 """
     12 
     13 import filecmp
     14 import os
     15 import shutil
     16 import tempfile
     17 import unittest
     18 
     19 PARENT_DIR = os.path.dirname(os.path.realpath(__file__))
     20 TRUNK_DIR = os.path.dirname(os.path.dirname(PARENT_DIR))
     21 TESTDATA_DIR = os.path.join(PARENT_DIR, 'testdata')
     22 OUTPUT_DIR_ACTUAL = os.path.join(TESTDATA_DIR, 'outputs', 'actual')
     23 OUTPUT_DIR_EXPECTED = os.path.join(TESTDATA_DIR, 'outputs', 'expected')
     24 
     25 
     26 class TestCase(unittest.TestCase):
     27 
     28   def setUp(self):
     29     self._input_dir = os.path.join(TESTDATA_DIR, 'inputs')
     30     self._output_dir_actual   = os.path.join(OUTPUT_DIR_ACTUAL, self.id())
     31     self._output_dir_expected = os.path.join(OUTPUT_DIR_EXPECTED, self.id())
     32     create_empty_dir(self._output_dir_actual)
     33     self._temp_dir = tempfile.mkdtemp()
     34 
     35   def tearDown(self):
     36     shutil.rmtree(self._temp_dir)
     37     if os.path.exists(self._output_dir_expected):
     38       different_files = find_different_files(self._output_dir_actual,
     39                                              self._output_dir_expected)
     40       # Maybe we should move this assert elsewhere?  It's unusual to see an
     41       # assert within tearDown(), but my thinking was:
     42       # 1. Every test case will have some collection of output files that need
     43       #    to be validated.
     44       # 2. So put that validation within tearDown(), which will be called after
     45       #    every test case!
     46       #
     47       # I have confirmed that the test really does fail if this assert is
     48       # triggered.
     49       #
     50       # Ravi notes: if somebody later comes along and adds cleanup code below
     51       # this assert, then if tests fail, the artifacts will not be cleaned up.
     52       assert (not different_files), \
     53         ('found differing files:\n' +
     54          '\n'.join(['tkdiff %s %s &' % (
     55              os.path.join(self._output_dir_actual, basename),
     56              os.path.join(self._output_dir_expected, basename))
     57                     for basename in different_files]))
     58 
     59   def shortDescription(self):
     60     """Tell unittest framework to not print docstrings for test cases."""
     61     return None
     62 
     63   def find_path_to_program(self, program):
     64     """Returns path to an existing program binary.
     65 
     66     Args:
     67       program: Basename of the program to find (e.g., 'render_pictures').
     68 
     69     Returns:
     70       Absolute path to the program binary, as a string.
     71 
     72     Raises:
     73       Exception: unable to find the program binary.
     74     """
     75     possible_paths = [os.path.join(TRUNK_DIR, 'out', 'Release', program),
     76                       os.path.join(TRUNK_DIR, 'out', 'Debug', program),
     77                       os.path.join(TRUNK_DIR, 'out', 'Release',
     78                                    program + '.exe'),
     79                       os.path.join(TRUNK_DIR, 'out', 'Debug',
     80                                    program + '.exe')]
     81     for try_path in possible_paths:
     82       if os.path.isfile(try_path):
     83         return try_path
     84     raise Exception('cannot find %s in paths %s; maybe you need to '
     85                     'build %s?' % (program, possible_paths, program))
     86 
     87 
     88 def create_empty_dir(path):
     89   """Create an empty directory at the given path."""
     90   if os.path.isdir(path):
     91     shutil.rmtree(path)
     92   elif os.path.lexists(path):
     93     os.remove(path)
     94   os.makedirs(path)
     95 
     96 
     97 def find_different_files(dir1, dir2, ignore_subtree_names=None):
     98   """Returns a list of any files that differ between the directory trees rooted
     99   at dir1 and dir2.
    100 
    101   Args:
    102     dir1: root of a directory tree; if nonexistent, will raise OSError
    103     dir2: root of another directory tree; if nonexistent, will raise OSError
    104     ignore_subtree_names: list of subtree directory names to ignore;
    105           defaults to ['.svn'], so all SVN files are ignores
    106 
    107   TODO(epoger): include the dirname within each filename (not just the
    108   basename), to make it easier to locate any differences
    109   """
    110   differing_files = []
    111   if ignore_subtree_names is None:
    112     ignore_subtree_names = ['.svn']
    113   dircmp = filecmp.dircmp(dir1, dir2, ignore=ignore_subtree_names)
    114   differing_files.extend(dircmp.left_only)
    115   differing_files.extend(dircmp.right_only)
    116   differing_files.extend(dircmp.common_funny)
    117   differing_files.extend(dircmp.diff_files)
    118   differing_files.extend(dircmp.funny_files)
    119   for common_dir in dircmp.common_dirs:
    120     differing_files.extend(find_different_files(
    121         os.path.join(dir1, common_dir), os.path.join(dir2, common_dir)))
    122   return differing_files
    123 
    124 
    125 def main(test_case_class):
    126   """Run the unit tests within the given class."""
    127   suite = unittest.TestLoader().loadTestsFromTestCase(test_case_class)
    128   results = unittest.TextTestRunner(verbosity=2).run(suite)
    129