1#!/usr/bin/python 2 3""" 4Copyright 2014 Google Inc. 5 6Use of this source code is governed by a BSD-style license that can be 7found in the LICENSE file. 8 9A wrapper around the standard Python unittest library, adding features we need 10for various unittests within this directory. 11""" 12 13import filecmp 14import os 15import shutil 16import tempfile 17import unittest 18 19PARENT_DIR = os.path.dirname(os.path.realpath(__file__)) 20TRUNK_DIR = os.path.dirname(os.path.dirname(PARENT_DIR)) 21TESTDATA_DIR = os.path.join(PARENT_DIR, 'testdata') 22OUTPUT_DIR_ACTUAL = os.path.join(TESTDATA_DIR, 'outputs', 'actual') 23OUTPUT_DIR_EXPECTED = os.path.join(TESTDATA_DIR, 'outputs', 'expected') 24 25 26class 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 88def 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 97def 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 125def 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