|
1 # |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # This component and the accompanying materials are made available |
|
5 # under the terms of "Eclipse Public License v1.0" |
|
6 # which accompanies this distribution, and is available |
|
7 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 # |
|
9 # Initial Contributors: |
|
10 # Nokia Corporation - initial contribution. |
|
11 # |
|
12 # Contributors: |
|
13 # |
|
14 # Description: |
|
15 # |
|
16 |
|
17 import sys, os, re, unittest, shutil, zipfile, filecmp, subprocess |
|
18 |
|
19 class BaseTestCase(unittest.TestCase): |
|
20 def set_modification_reference_time(self, path): |
|
21 """ |
|
22 Set modification reference time for a subsequent call to assert_modified() |
|
23 or assert_not_modified(). |
|
24 @param path: The path to use, can be a file or a directory. |
|
25 """ |
|
26 if not hasattr(self, '_mod_refs'): |
|
27 self._mod_refs = {} |
|
28 |
|
29 if os.path.isdir(path): |
|
30 self._mod_refs[path] = self._get_dir_modtime_dict(path) |
|
31 elif os.path.isfile(path): |
|
32 self._mod_refs[path] = os.stat(path).st_mtime |
|
33 else: |
|
34 self.fail("'%s' does not exist" % path) |
|
35 |
|
36 def assert_modified(self, path): |
|
37 """ |
|
38 Assert that a given file or directory has been modified since the last |
|
39 call to set_modification_reference_time() with the same path. |
|
40 """ |
|
41 self._assert_modification(path, assert_not_modified=False) |
|
42 |
|
43 def assert_not_modified(self, path): |
|
44 """ |
|
45 Assert that a given file or directory has NOT been modified since the last |
|
46 call to set_modification_reference_time() with the same path. |
|
47 """ |
|
48 self._assert_modification(path, assert_not_modified=True) |
|
49 |
|
50 def remove_if_exists(self, path_or_paths): |
|
51 """Remove files or directories if they exist. |
|
52 @param path_or_paths: The path to remove. Can also be a list of paths.""" |
|
53 if isinstance(path_or_paths, list): |
|
54 paths = path_or_paths |
|
55 else: |
|
56 paths = [path_or_paths] |
|
57 |
|
58 for path in paths: |
|
59 if os.path.isdir(path): |
|
60 shutil.rmtree(path) |
|
61 elif os.path.isfile(path): |
|
62 os.remove(path) |
|
63 |
|
64 def create_dir(self, path): |
|
65 """Create the given directory if it doesn't exist.""" |
|
66 if not os.path.exists(path): |
|
67 os.makedirs(path) |
|
68 |
|
69 def recreate_dir(self, path): |
|
70 """Remove the given directory if it exists, and recreate it.""" |
|
71 if os.path.exists(path): |
|
72 shutil.rmtree(path) |
|
73 os.makedirs(path) |
|
74 |
|
75 def create_dir_for_file_path(self, path): |
|
76 """Create the directories for the given file""" |
|
77 dir = os.path.dirname(path) |
|
78 if dir != '' and not os.path.exists(dir): |
|
79 os.makedirs(dir) |
|
80 |
|
81 def assert_exists_and_contains_something(self, path): |
|
82 """ |
|
83 Assert that the given path is a file or a directory and contains some data. |
|
84 """ |
|
85 if os.path.isdir(path): |
|
86 if len(os.listdir(path)) == 0: |
|
87 self.fail("Path '%s' exists (is a directory) but does not contain anything)" % path) |
|
88 elif os.path.isfile(path): |
|
89 if os.stat(path).st_size == 0: |
|
90 self.fail("Path '%s' exists (is a file) but does not contain anything)" % path) |
|
91 else: |
|
92 self.fail("Path '%s' does not exist" % path) |
|
93 |
|
94 def assert_dir_contents_equal(self, dir1, dir2, ignore=[], custom_comparison_functions={}, current_root_dir=''): |
|
95 """ |
|
96 Assert recursively that the contents of two directories are equal. |
|
97 @param ignore: List containing names that should be ignored in the comparison (e.g. '.svn'). |
|
98 The entries can either be relative, e.g. 'file.txt', which would ignore 'file.txt' |
|
99 in any directory, or they can be absolute, e.g. '/some/dir/file.txt', which would |
|
100 ignore 'file.txt' only under 'some/dir/', relative to the comparison root. |
|
101 @param custom_comparison_functions: Dictionary containing custom comparison functions |
|
102 for files. Each entry in the dict should contain the following contents: |
|
103 Key: The relative path of the file under the directories, e.g. |
|
104 'some/path/file.txt' |
|
105 Value: The function used to compare the file contents. The function should |
|
106 take as parameters the raw binary data of the files, and should return |
|
107 True if the contents are equal. |
|
108 @param current_root_dir: For internal use. |
|
109 """ |
|
110 msg = "Directory contents are not equal ('%s' vs. '%s')\n" % (dir1, dir2) |
|
111 |
|
112 ignore_list = [] |
|
113 for entry in ignore: |
|
114 if entry.startswith('/'): |
|
115 dirname, entryname = entry.rsplit('/', 1) |
|
116 dirname = dirname.lstrip('/') |
|
117 #print "dirname = %r" % dirname |
|
118 #print "entryname = %r" % entryname |
|
119 #print "current_root_dir = %r" % current_root_dir |
|
120 if dirname == current_root_dir.rstrip('/'): |
|
121 ignore_list.append(entryname) |
|
122 else: |
|
123 ignore_list.append(entry) |
|
124 |
|
125 # Compare files with the custom comparison functions if necessary |
|
126 for path, func in custom_comparison_functions.iteritems(): |
|
127 dirname = os.path.dirname(path).replace('\\', '/') |
|
128 filename = os.path.basename(path) |
|
129 |
|
130 filepath1 = os.path.join(dir1, filename) |
|
131 filepath2 = os.path.join(dir2, filename) |
|
132 |
|
133 # Compare if the file is in the current path and they both exist |
|
134 if dirname == current_root_dir and \ |
|
135 os.path.isfile(filepath1) and \ |
|
136 os.path.isfile(filepath2): |
|
137 comp_result = func( |
|
138 self.read_data_from_file(filepath1), |
|
139 self.read_data_from_file(filepath2)) |
|
140 if not comp_result: |
|
141 # The files are not equal -> fail |
|
142 self.fail(msg + "File '%s' differs" % filename) |
|
143 else: |
|
144 # The files are equal -> ignore from dircmp comparison |
|
145 ignore_list.append(filename) |
|
146 |
|
147 dcmp = filecmp.dircmp(dir1, dir2, ignore=ignore_list) |
|
148 self.assertEquals(0, len(dcmp.left_only), msg + "Files only on left: %s" % dcmp.left_only) |
|
149 self.assertEquals(0, len(dcmp.right_only), msg + "Files only on right: %s" % dcmp.right_only) |
|
150 self.assertEquals(0, len(dcmp.diff_files), msg + "Differing files: %s" % dcmp.diff_files) |
|
151 self.assertEquals(0, len(dcmp.funny_files), msg + "Funny files: %s" % dcmp.funny_files) |
|
152 # Recurse into sub-directories |
|
153 for d in dcmp.common_dirs: |
|
154 if current_root_dir: cr = current_root_dir + '/' + d |
|
155 else: cr = d |
|
156 self.assert_dir_contents_equal( |
|
157 os.path.join(dir1, d), os.path.join(dir2, d), |
|
158 ignore, custom_comparison_functions, cr) |
|
159 |
|
160 def assert_file_contents_equal(self, file1, file2, ignore_patterns=[]): |
|
161 """ |
|
162 Assert the the given two files exist and their contents are equal. |
|
163 @param ignore_patterns: List of regular expressions for portions of the |
|
164 file content to ignore in the comparison. The ignored parts are |
|
165 deleted from the files before actual comparison. |
|
166 """ |
|
167 self.assertTrue(os.path.exists(file1), "File '%s' does not exist!" % file1) |
|
168 self.assertTrue(os.path.exists(file2), "File '%s' does not exist!" % file2) |
|
169 |
|
170 data1 = self.read_data_from_file(file1) |
|
171 data2 = self.read_data_from_file(file2) |
|
172 |
|
173 def remove_ignored(data, pattern_list): |
|
174 for i, pattern in enumerate(pattern_list): |
|
175 data = re.sub(pattern, '{{{ignore_%d}}}' % i, data) |
|
176 return data |
|
177 data1 = remove_ignored(data1, ignore_patterns) |
|
178 data2 = remove_ignored(data2, ignore_patterns) |
|
179 |
|
180 if data1 != data2: |
|
181 if len(ignore_patterns) > 0: |
|
182 self.write_data_to_file(file1 + '.comparetemp', data1) |
|
183 self.write_data_to_file(file2 + '.comparetemp', data2) |
|
184 self.fail("Data of the files '%s' and '%s' are not equal\nSee *.comparetemp files for the actual data that was compared." % (file1, file2)) |
|
185 else: |
|
186 self.fail("Data of the files '%s' and '%s' are not equal" % (file1, file2)) |
|
187 |
|
188 def assert_file_content_equals(self, filepath, expected_data): |
|
189 """ |
|
190 Assert that the content of the given file is equals to the given expected data. |
|
191 """ |
|
192 self.assertTrue(os.path.exists(filepath), "'%s' does not exist!" % filepath) |
|
193 self.assertTrue(os.path.isfile(filepath), "'%s' is not a file!" % filepath) |
|
194 |
|
195 f = open(filepath, "rb") |
|
196 try: filedata = f.read() |
|
197 finally: f.close() |
|
198 |
|
199 if filedata != expected_data: |
|
200 msg = ("The content of the file '%s' is not what was expected!\n" % filepath) +\ |
|
201 ("Expected: %r\nActual: %r" % (expected_data, filedata)) |
|
202 self.fail(msg) |
|
203 |
|
204 def assert_file_contains(self, filepath, data, encoding=None): |
|
205 """ |
|
206 Assert that the given file contains the given text somewhere in its contents. |
|
207 @param filepath: Path to the file to check. |
|
208 @param data: The data the file is expected to contain. |
|
209 @param encoding: Encoding used to decode the contents of the file. |
|
210 If None, noe decoding is done. |
|
211 """ |
|
212 self.assertTrue(os.path.exists(filepath), "'%s' does not exist!" % filepath) |
|
213 self.assertTrue(os.path.isfile(filepath), "'%s' is not a file!" % filepath) |
|
214 |
|
215 f = open(filepath, "rb") |
|
216 try: filedata = f.read() |
|
217 finally: f.close() |
|
218 |
|
219 if encoding is not None: |
|
220 filedata = filedata.decode(encoding) |
|
221 |
|
222 if not isinstance(data, list): |
|
223 data = [data] |
|
224 |
|
225 for entry in data: |
|
226 if not filedata.find(entry) != -1: |
|
227 self.fail("The file '%s' does not contain the data '%s'" % (filepath, entry)) |
|
228 |
|
229 def assert_file_does_not_contain(self, filepath, data, encoding=None): |
|
230 """ |
|
231 Assert that the given file doesn't contain the given text somewhere in its contents. |
|
232 @param filepath: Path to the file to check. |
|
233 @param data: The data the file is expected to not contain. |
|
234 @param encoding: Encoding used to decode the contents of the file. |
|
235 If None, noe decoding is done. |
|
236 """ |
|
237 self.assertTrue(os.path.exists(filepath), "'%s' does not exist!" % filepath) |
|
238 self.assertTrue(os.path.isfile(filepath), "'%s' is not a file!" % filepath) |
|
239 |
|
240 f = open(filepath, "rb") |
|
241 try: filedata = f.read() |
|
242 finally: f.close() |
|
243 |
|
244 if encoding is not None: |
|
245 filedata = filedata.decode(encoding) |
|
246 |
|
247 if not isinstance(data, list): |
|
248 data = [data] |
|
249 |
|
250 for entry in data: |
|
251 if not filedata.find(entry) == -1: |
|
252 self.fail("The file '%s' contains the data '%s'" % (filepath, entry)) |
|
253 |
|
254 def read_data_from_file(self, path): |
|
255 """Read the raw binary data from the given file.""" |
|
256 f = open(path, "rb") |
|
257 try: return f.read() |
|
258 finally: f.close() |
|
259 |
|
260 def read_data_from_zip_file(self, path, entry): |
|
261 """Read the raw binary data from the given ZIP file with the given ZIP entry.""" |
|
262 zf = zipfile.ZipFile(path, "r") |
|
263 try: return zf.read(entry) |
|
264 finally: zf.close() |
|
265 |
|
266 def write_data_to_file(self, path, data): |
|
267 """Write raw binary data into the given file.""" |
|
268 f = open(path, "wb") |
|
269 try: f.write(data) |
|
270 finally: f.close() |
|
271 |
|
272 def run_command(self, command, expected_return_code=0): |
|
273 """ |
|
274 Run the given command, asserting that it returns the expected value. |
|
275 @param command: The command to run. |
|
276 @param expected_return_code: The expected return code. Can be None if the return |
|
277 code doesn't matter. |
|
278 @return: The command output. |
|
279 """ |
|
280 # Using shell=True on windows uses |
|
281 # cmd.exe /c <command> |
|
282 # to run the actual command, and if cmd.exe sees that the first |
|
283 # character in the command is ", it strips that and a trailing ". |
|
284 # For this reason we add quotes to the command to prevent e.g. |
|
285 # "C:\some\command.cmd" --some-arg "xyz" |
|
286 # from becoming |
|
287 # C:\some\command.cmd" --some-arg "xyz |
|
288 if sys.platform == 'win32' and command.startswith('"'): |
|
289 command = '"' + command + '"' |
|
290 |
|
291 p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) |
|
292 out, err = p.communicate() |
|
293 if expected_return_code is not None: |
|
294 self.assertTrue(p.returncode == expected_return_code, |
|
295 "Could not execute command (%s)\n"\ |
|
296 "Return code is not what was expected (expected %d, got %d)\n"\ |
|
297 "Output: \n%s" % (command, expected_return_code, p.returncode, out)) |
|
298 return out |
|
299 |
|
300 # ===================================================== |
|
301 # Private helper methods |
|
302 # ===================================================== |
|
303 |
|
304 def _get_dir_modtime_dict(self, dir_path): |
|
305 """ |
|
306 Return a dictionary of all files and directories and their last |
|
307 modification times in a given directory. |
|
308 """ |
|
309 refdict = {} |
|
310 for root, dirs, files in os.walk(dir_path): |
|
311 for f in files: |
|
312 path = os.path.join(root, f) |
|
313 refdict[path] = os.stat(path).st_mtime |
|
314 for d in dirs: |
|
315 path = os.path.join(root, d) |
|
316 refdict[path] = os.stat(path).st_mtime |
|
317 return refdict |
|
318 |
|
319 def _assert_modification(self, path, assert_not_modified=True): |
|
320 if os.path.isdir(path): |
|
321 if assert_not_modified: |
|
322 self._assert_dir_not_modified(path) |
|
323 else: |
|
324 self.assert_dir_modified(path) |
|
325 elif os.path.isfile(path): |
|
326 if assert_not_modified: |
|
327 self._assert_file_not_modified(path) |
|
328 else: |
|
329 self._assert_file_modified(path) |
|
330 else: |
|
331 self.fail("'%s' does not exist" % path) |
|
332 |
|
333 def _assert_dir_not_modified(self, dir_path): |
|
334 refdict = self._mod_refs[dir_path] |
|
335 curdict = self._get_dir_modtime_dict(dir_path) |
|
336 |
|
337 # If the keys of the dicts are not the same, the contents of the |
|
338 # dir have been modified (added or removed files/subdirs) |
|
339 self.assertEquals(curdict.keys(), refdict.keys()) |
|
340 |
|
341 # Compare manually so that assertion error output shows the specific file/dir |
|
342 for path in curdict.iterkeys(): |
|
343 self.assertEquals(curdict[path], refdict[path], "File or dir '%s' modified" % path) |
|
344 |
|
345 def assert_dir_modified(self, dir_path): |
|
346 refdict = self._mod_refs[dir_path] |
|
347 curdict = self._get_dir_modtime_dict(dir_path) |
|
348 |
|
349 self.assertNotEqual(curdict, refdict, "Directory '%s' has not been modified when it was expected to be" % dir_path) |
|
350 |
|
351 def _assert_file_not_modified(self, file_path): |
|
352 time1 = self._mod_refs[file_path] |
|
353 time2 = os.stat(file_path).st_mtime |
|
354 self.assertEquals(time1, time2, |
|
355 ("File '%s' was modified when it should not have been "+\ |
|
356 "(mod time %f vs. %f)") % (file_path, time1, time2)) |
|
357 |
|
358 def _assert_file_modified(self, file_path): |
|
359 time1 = self._mod_refs[file_path] |
|
360 time2 = os.stat(file_path).st_mtime |
|
361 self.assertNotEqual(time1, time2, |
|
362 ("File '%s' was modified not when it should have been "+\ |
|
363 "(mod time %f vs. %f)") % (file_path, time1, time2)) |