Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / tests / run_isolated_smoke_test.py
1 #!/usr/bin/env python
2 # Copyright 2012 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 that
4 # can be found in the LICENSE file.
5
6 import hashlib
7 import json
8 import logging
9 import os
10 import shutil
11 import subprocess
12 import sys
13 import unittest
14
15 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16 sys.path.insert(0, ROOT_DIR)
17
18 import isolated_format
19 import run_isolated
20 from utils import file_path
21
22 import test_utils
23
24 ALGO = hashlib.sha1
25
26
27 class CalledProcessError(subprocess.CalledProcessError):
28   """Makes 2.6 version act like 2.7"""
29   def __init__(self, returncode, cmd, output, stderr, cwd):
30     super(CalledProcessError, self).__init__(returncode, cmd)
31     self.output = output
32     self.stderr = stderr
33     self.cwd = cwd
34
35   def __str__(self):
36     return super(CalledProcessError, self).__str__() + (
37         '\n'
38         'cwd=%s\n%s\n%s\n%s') % (
39             self.cwd,
40             self.output,
41             self.stderr,
42             ' '.join(self.cmd))
43
44
45 def list_files_tree(directory):
46   """Returns the list of all the files in a tree."""
47   actual = []
48   for root, _dirs, files in os.walk(directory):
49     actual.extend(os.path.join(root, f)[len(directory)+1:] for f in files)
50   return sorted(actual)
51
52
53 def read_content(filepath):
54   with open(filepath, 'rb') as f:
55     return f.read()
56
57
58 def write_content(filepath, content):
59   with open(filepath, 'wb') as f:
60     f.write(content)
61
62
63 def tree_modes(root):
64   """Returns the dict of files in a directory with their filemode.
65
66   Includes |root| as '.'.
67   """
68   out = {}
69   offset = len(root.rstrip('/\\')) + 1
70   out['.'] = oct(os.stat(root).st_mode)
71   for dirpath, dirnames, filenames in os.walk(root):
72     for filename in filenames:
73       p = os.path.join(dirpath, filename)
74       out[p[offset:]] = oct(os.stat(p).st_mode)
75     for dirname in dirnames:
76       p = os.path.join(dirpath, dirname)
77       out[p[offset:]] = oct(os.stat(p).st_mode)
78   return out
79
80
81 class RunIsolatedTest(unittest.TestCase):
82   def setUp(self):
83     super(RunIsolatedTest, self).setUp()
84     self.tempdir = run_isolated.make_temp_dir(
85         'run_isolated_smoke_test', ROOT_DIR)
86     logging.debug(self.tempdir)
87     # run_isolated.zip executable package.
88     self.run_isolated_zip = os.path.join(self.tempdir, 'run_isolated.zip')
89     run_isolated.get_as_zip_package().zip_into_file(
90         self.run_isolated_zip, compress=False)
91     # The "source" hash table.
92     self.table = os.path.join(self.tempdir, 'table')
93     os.mkdir(self.table)
94     # The slave-side cache.
95     self.cache = os.path.join(self.tempdir, 'cache')
96
97     self.data_dir = os.path.join(ROOT_DIR, 'tests', 'run_isolated')
98
99   def tearDown(self):
100     file_path.rmtree(self.tempdir)
101     super(RunIsolatedTest, self).tearDown()
102
103   def _result_tree(self):
104     return list_files_tree(self.tempdir)
105
106   def _run(self, args):
107     cmd = [sys.executable, self.run_isolated_zip]
108     cmd.extend(args)
109     pipe = subprocess.PIPE
110     logging.debug(' '.join(cmd))
111     proc = subprocess.Popen(
112         cmd,
113         stdout=pipe,
114         stderr=pipe,
115         universal_newlines=True,
116         cwd=self.tempdir)
117     out, err = proc.communicate()
118     return out, err, proc.returncode
119
120   def _store_result(self, result_data):
121     """Stores a .isolated file in the hash table."""
122     # Need to know the hash before writting the file.
123     result_text = json.dumps(result_data, sort_keys=True, indent=2)
124     result_hash = ALGO(result_text).hexdigest()
125     write_content(os.path.join(self.table, result_hash), result_text)
126     return result_hash
127
128   def _store(self, filename):
129     """Stores a test data file in the table.
130
131     Returns its sha-1 hash.
132     """
133     filepath = os.path.join(self.data_dir, filename)
134     h = isolated_format.hash_file(filepath, ALGO)
135     shutil.copyfile(filepath, os.path.join(self.table, h))
136     return h
137
138   def _generate_args_with_isolated(self, isolated):
139     """Generates the standard arguments used with isolated as the isolated file.
140
141     Returns a list of the required arguments.
142     """
143     return [
144       '--isolated', isolated,
145       '--cache', self.cache,
146       '--indir', self.table,
147       '--namespace', 'default',
148     ]
149
150   def _generate_args_with_hash(self, hash_value):
151     """Generates the standard arguments used with |hash_value| as the hash.
152
153     Returns a list of the required arguments.
154     """
155     return [
156       '--hash', hash_value,
157       '--cache', self.cache,
158       '--indir', self.table,
159       '--namespace', 'default',
160     ]
161
162   def assertTreeModes(self, root, expected):
163     """Compares the file modes of everything in |root| with |expected|.
164
165     Arguments:
166       root: directory to list its tree.
167       expected: dict(relpath: (linux_mode, mac_mode, win_mode)) where each mode
168                 is the expected file mode on this OS. For practical purposes,
169                 linux is "anything but OSX or Windows". The modes should be
170                 ints.
171     """
172     actual = tree_modes(root)
173     if sys.platform == 'win32':
174       index = 2
175     elif sys.platform == 'darwin':
176       index = 1
177     else:
178       index = 0
179     expected_mangled = dict((k, oct(v[index])) for k, v in expected.iteritems())
180     self.assertEqual(expected_mangled, actual)
181
182
183   def test_result(self):
184     # Loads an arbitrary .isolated on the file system.
185     isolated = os.path.join(self.data_dir, 'repeated_files.isolated')
186     expected = [
187       'state.json',
188       self._store('file1.txt'),
189       self._store('file1_copy.txt'),
190       self._store('repeated_files.py'),
191       isolated_format.hash_file(isolated, ALGO),
192     ]
193     out, err, returncode = self._run(
194         self._generate_args_with_isolated(isolated))
195     self.assertEqual('Success\n', out, (out, err))
196     self.assertEqual(0, returncode)
197     actual = list_files_tree(self.cache)
198     self.assertEqual(sorted(set(expected)), actual)
199
200   def test_hash(self):
201     # Loads the .isolated from the store as a hash.
202     result_hash = self._store('repeated_files.isolated')
203     expected = [
204       'state.json',
205       self._store('file1.txt'),
206       self._store('file1_copy.txt'),
207       self._store('repeated_files.py'),
208       result_hash,
209     ]
210
211     out, err, returncode = self._run(self._generate_args_with_hash(result_hash))
212     self.assertEqual('', err)
213     self.assertEqual('Success\n', out, out)
214     self.assertEqual(0, returncode)
215     actual = list_files_tree(self.cache)
216     self.assertEqual(sorted(set(expected)), actual)
217
218   def test_fail_empty_isolated(self):
219     result_hash = self._store_result({})
220     expected = [
221       'state.json',
222       result_hash,
223     ]
224     out, err, returncode = self._run(self._generate_args_with_hash(result_hash))
225     self.assertEqual('', out)
226     self.assertIn('No command to run\n', err)
227     self.assertEqual(1, returncode)
228     actual = list_files_tree(self.cache)
229     self.assertEqual(sorted(expected), actual)
230
231   def test_includes(self):
232     # Loads an .isolated that includes another one.
233
234     # References manifest2.isolated and repeated_files.isolated. Maps file3.txt
235     # as file2.txt.
236     result_hash = self._store('check_files.isolated')
237     expected = [
238       'state.json',
239       self._store('check_files.py'),
240       self._store('file1.txt'),
241       self._store('file3.txt'),
242       # Maps file1.txt.
243       self._store('manifest1.isolated'),
244       # References manifest1.isolated. Maps file2.txt but it is overriden.
245       self._store('manifest2.isolated'),
246       result_hash,
247       self._store('repeated_files.py'),
248       self._store('repeated_files.isolated'),
249     ]
250     out, err, returncode = self._run(self._generate_args_with_hash(result_hash))
251     self.assertEqual('', err)
252     self.assertEqual('Success\n', out)
253     self.assertEqual(0, returncode)
254     actual = list_files_tree(self.cache)
255     self.assertEqual(sorted(expected), actual)
256
257   def test_link_all_hash_instances(self):
258     # Load an isolated file with the same file (same sha-1 hash), listed under
259     # two different names and ensure both are created.
260     result_hash = self._store('repeated_files.isolated')
261     expected = [
262         'state.json',
263         result_hash,
264         self._store('file1.txt'),
265         self._store('repeated_files.py')
266     ]
267
268     out, err, returncode = self._run(self._generate_args_with_hash(result_hash))
269     self.assertEqual('', err)
270     self.assertEqual('Success\n', out)
271     self.assertEqual(0, returncode)
272     actual = list_files_tree(self.cache)
273     self.assertEqual(sorted(expected), actual)
274
275   def test_delete_quite_corrupted_cache_entry(self):
276     # Test that an entry with an invalid file size properly gets removed and
277     # fetched again. This test case also check for file modes.
278     isolated_file = os.path.join(self.data_dir, 'file_with_size.isolated')
279     isolated_hash = isolated_format.hash_file(isolated_file, ALGO)
280     file1_hash = self._store('file1.txt')
281     # Note that <tempdir>/table/<file1_hash> has 640 mode.
282
283     # Run the test once to generate the cache.
284     _out, _err, returncode = self._run(self._generate_args_with_isolated(
285         isolated_file))
286     self.assertEqual(0, returncode)
287     expected = {
288       '.': (040707, 040707, 040777),
289       'state.json': (0100606, 0100606, 0100666),
290       # The reason for 0100666 on Windows is that the file node had to be
291       # modified to delete the hardlinked node. The read only bit is reset on
292       # load.
293       file1_hash: (0100400, 0100400, 0100666),
294       isolated_hash: (0100400, 0100400, 0100444),
295     }
296     self.assertTreeModes(self.cache, expected)
297
298     # Modify one of the files in the cache to be invalid.
299     cached_file_path = os.path.join(self.cache, file1_hash)
300     previous_mode = os.stat(cached_file_path).st_mode
301     os.chmod(cached_file_path, 0600)
302     old_content = read_content(cached_file_path)
303     write_content(cached_file_path, old_content + ' but now invalid size')
304     os.chmod(cached_file_path, previous_mode)
305     logging.info('Modified %s', cached_file_path)
306     # Ensure that the cache has an invalid file.
307     self.assertNotEqual(
308         os.stat(os.path.join(self.data_dir, 'file1.txt')).st_size,
309         os.stat(cached_file_path).st_size)
310
311     # Rerun the test and make sure the cache contains the right file afterwards.
312     _out, _err, returncode = self._run(self._generate_args_with_isolated(
313         isolated_file))
314     self.assertEqual(0, returncode)
315     expected = {
316       '.': (040700, 040700, 040777),
317       'state.json': (0100600, 0100600, 0100666),
318       file1_hash: (0100400, 0100400, 0100666),
319       isolated_hash: (0100400, 0100400, 0100444),
320     }
321     self.assertTreeModes(self.cache, expected)
322
323     self.assertEqual(os.stat(os.path.join(self.data_dir, 'file1.txt')).st_size,
324                      os.stat(cached_file_path).st_size)
325     self.assertEqual(old_content, read_content(cached_file_path))
326
327   def test_delete_slightly_corrupted_cache_entry(self):
328     # Test that an entry with an invalid file size properly gets removed and
329     # fetched again. This test case also check for file modes.
330     isolated_file = os.path.join(self.data_dir, 'file_with_size.isolated')
331     isolated_hash = isolated_format.hash_file(isolated_file, ALGO)
332     file1_hash = self._store('file1.txt')
333     # Note that <tempdir>/table/<file1_hash> has 640 mode.
334
335     # Run the test once to generate the cache.
336     _out, _err, returncode = self._run(self._generate_args_with_isolated(
337         isolated_file))
338     self.assertEqual(0, returncode)
339     expected = {
340       '.': (040707, 040707, 040777),
341       'state.json': (0100606, 0100606, 0100666),
342       file1_hash: (0100400, 0100400, 0100666),
343       isolated_hash: (0100400, 0100400, 0100444),
344     }
345     self.assertTreeModes(self.cache, expected)
346
347     # Modify one of the files in the cache to be invalid.
348     cached_file_path = os.path.join(self.cache, file1_hash)
349     previous_mode = os.stat(cached_file_path).st_mode
350     os.chmod(cached_file_path, 0600)
351     old_content = read_content(cached_file_path)
352     write_content(cached_file_path, old_content[1:] + 'b')
353     os.chmod(cached_file_path, previous_mode)
354     logging.info('Modified %s', cached_file_path)
355     self.assertEqual(
356         os.stat(os.path.join(self.data_dir, 'file1.txt')).st_size,
357         os.stat(cached_file_path).st_size)
358
359     # Rerun the test and make sure the cache contains the right file afterwards.
360     _out, _err, returncode = self._run(self._generate_args_with_isolated(
361         isolated_file))
362     self.assertEqual(0, returncode)
363     expected = {
364       '.': (040700, 040700, 040777),
365       'state.json': (0100600, 0100600, 0100666),
366       file1_hash: (0100400, 0100400, 0100666),
367       isolated_hash: (0100400, 0100400, 0100444),
368     }
369     self.assertTreeModes(self.cache, expected)
370
371     self.assertEqual(os.stat(os.path.join(self.data_dir, 'file1.txt')).st_size,
372                      os.stat(cached_file_path).st_size)
373     # TODO(maruel): This corruption is NOT detected.
374     # This needs to be fixed.
375     self.assertNotEqual(old_content, read_content(cached_file_path))
376
377
378 if __name__ == '__main__':
379   test_utils.main()