Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / tests / isolate_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 cStringIO
7 import hashlib
8 import json
9 import logging
10 import optparse
11 import os
12 import shutil
13 import subprocess
14 import sys
15 import tempfile
16
17 ROOT_DIR = unicode(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18 sys.path.insert(0, ROOT_DIR)
19 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party'))
20
21 from depot_tools import auto_stub
22 import auth
23 import isolate
24 import isolate_format
25 import isolated_format
26 import isolateserver
27 from utils import file_path
28 from utils import tools
29 import test_utils
30
31 ALGO = hashlib.sha1
32
33
34 def _size(*args):
35   return os.stat(os.path.join(ROOT_DIR, *args)).st_size
36
37
38 def hash_file(*args):
39   return isolated_format.hash_file(os.path.join(ROOT_DIR, *args), ALGO)
40
41
42 class IsolateBase(auto_stub.TestCase):
43   def setUp(self):
44     super(IsolateBase, self).setUp()
45     self.mock(auth, 'ensure_logged_in', lambda _: None)
46     self.old_cwd = os.getcwd()
47     self.cwd = tempfile.mkdtemp(prefix='isolate_')
48     # Everything should work even from another directory.
49     os.chdir(self.cwd)
50
51   def tearDown(self):
52     try:
53       os.chdir(self.old_cwd)
54       file_path.rmtree(self.cwd)
55     finally:
56       super(IsolateBase, self).tearDown()
57
58
59 class IsolateTest(IsolateBase):
60   def test_savedstate_load_minimal(self):
61     # The file referenced by 'isolate_file' must exist even if its content is
62     # not read.
63     open(os.path.join(self.cwd, 'fake.isolate'), 'wb').close()
64     values = {
65       'OS': sys.platform,
66       'algo': 'sha-1',
67       'isolate_file': 'fake.isolate',
68     }
69     expected = {
70       'OS': sys.platform,
71       'algo': 'sha-1',
72       'child_isolated_files': [],
73       'config_variables': {},
74       'command': [],
75       'extra_variables': {},
76       'files': {},
77       'isolate_file': 'fake.isolate',
78       'path_variables': {},
79       'version': isolate.SavedState.EXPECTED_VERSION,
80     }
81     saved_state = isolate.SavedState.load(values, self.cwd)
82     self.assertEqual(expected, saved_state.flatten())
83
84   def test_savedstate_load(self):
85     # The file referenced by 'isolate_file' must exist even if its content is
86     # not read.
87     open(os.path.join(self.cwd, 'fake.isolate'), 'wb').close()
88     values = {
89       'OS': sys.platform,
90       'algo': 'sha-1',
91       'config_variables': {},
92       'extra_variables': {
93         'foo': 42,
94       },
95       'isolate_file': 'fake.isolate',
96     }
97     expected = {
98       'OS': sys.platform,
99       'algo': 'sha-1',
100       'child_isolated_files': [],
101       'command': [],
102       'config_variables': {},
103       'extra_variables': {
104         'foo': 42,
105       },
106       'files': {},
107       'isolate_file': 'fake.isolate',
108       'path_variables': {},
109       'version': isolate.SavedState.EXPECTED_VERSION,
110     }
111     saved_state = isolate.SavedState.load(values, self.cwd)
112     self.assertEqual(expected, saved_state.flatten())
113
114   def test_variable_arg(self):
115     parser = optparse.OptionParser()
116     isolate.add_isolate_options(parser)
117     options, args = parser.parse_args(
118         ['--config-variable', 'Foo', 'bar',
119           '--path-variable', 'Baz=sub=string',
120           '--extra-variable', 'biz', 'b uz=a'])
121     isolate.process_isolate_options(parser, options, require_isolated=False)
122
123     expected_path = {
124       'Baz': 'sub=string',
125     }
126     expected_config = {
127       'Foo': 'bar',
128     }
129     expected_extra = {
130       'biz': 'b uz=a',
131       'EXECUTABLE_SUFFIX': '.exe' if sys.platform == 'win32' else '',
132     }
133     self.assertEqual(expected_path, options.path_variables)
134     self.assertEqual(expected_config, options.config_variables)
135     self.assertEqual(expected_extra, options.extra_variables)
136     self.assertEqual([], args)
137
138   def test_variable_arg_fail(self):
139     parser = optparse.OptionParser()
140     isolate.add_isolate_options(parser)
141     self.mock(sys, 'stderr', cStringIO.StringIO())
142     with self.assertRaises(SystemExit):
143       parser.parse_args(['--config-variable', 'Foo'])
144
145   def test_blacklist_default(self):
146     ok = [
147       '.git2',
148       '.pyc',
149       '.swp',
150       'allo.git',
151       'foo',
152     ]
153     blocked = [
154       '.git',
155       os.path.join('foo', '.git'),
156       'foo.pyc',
157       'bar.swp',
158     ]
159     blacklist = tools.gen_blacklist(isolateserver.DEFAULT_BLACKLIST)
160     for i in ok:
161       self.assertFalse(blacklist(i), i)
162     for i in blocked:
163       self.assertTrue(blacklist(i), i)
164
165   def test_blacklist_custom(self):
166     ok = [
167       '.run_test_cases',
168       'testserver.log2',
169     ]
170     blocked = [
171       'foo.run_test_cases',
172       'testserver.log',
173       os.path.join('foo', 'testserver.log'),
174     ]
175     blacklist = tools.gen_blacklist([r'^.+\.run_test_cases$', r'^.+\.log$'])
176     for i in ok:
177       self.assertFalse(blacklist(i), i)
178     for i in blocked:
179       self.assertTrue(blacklist(i), i)
180
181   def test_read_only(self):
182     isolate_file = os.path.join(self.cwd, 'fake.isolate')
183     isolate_content = {
184       'variables': {
185         'read_only': 0,
186       },
187     }
188     tools.write_json(isolate_file, isolate_content, False)
189     expected = {
190       'algo': 'sha-1',
191       'files': {},
192       'read_only': 0,
193       'relative_cwd': '.',
194       'version': isolated_format.ISOLATED_FILE_VERSION,
195     }
196     complete_state = isolate.CompleteState(None, isolate.SavedState(self.cwd))
197     complete_state.load_isolate(
198         unicode(self.cwd), unicode(isolate_file), {}, {}, {}, None, False)
199     self.assertEqual(expected, complete_state.saved_state.to_isolated())
200
201
202 class IsolateLoad(IsolateBase):
203   def setUp(self):
204     super(IsolateLoad, self).setUp()
205     self.directory = tempfile.mkdtemp(prefix='isolate_')
206
207   def tearDown(self):
208     try:
209       file_path.rmtree(self.directory)
210     finally:
211       super(IsolateLoad, self).tearDown()
212
213   def _get_option(self, isolate_file):
214     class Options(object):
215       isolated = os.path.join(self.directory, 'foo.isolated')
216       outdir = os.path.join(self.directory, 'outdir')
217       isolate = isolate_file
218       blacklist = list(isolateserver.DEFAULT_BLACKLIST)
219       path_variables = {}
220       config_variables = {
221         'OS': 'linux',
222         'chromeos': 1,
223       }
224       extra_variables = {'foo': 'bar'}
225       ignore_broken_items = False
226     return Options()
227
228   def _cleanup_isolated(self, expected_isolated):
229     """Modifies isolated to remove the non-deterministic parts."""
230     if sys.platform == 'win32':
231       # 'm' are not saved in windows.
232       for values in expected_isolated['files'].itervalues():
233         self.assertTrue(values.pop('m'))
234
235   def _cleanup_saved_state(self, actual_saved_state):
236     for item in actual_saved_state['files'].itervalues():
237       self.assertTrue(item.pop('t'))
238
239   def test_load_stale_isolated(self):
240     isolate_file = os.path.join(
241         ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
242
243     # Data to be loaded in the .isolated file. Do not create a .state file.
244     input_data = {
245       'command': ['python'],
246       'files': {
247         'foo': {
248           "m": 416,
249           "h": "invalid",
250           "s": 538,
251           "t": 1335146921,
252         },
253         os.path.join('tests', 'isolate', 'touch_root.py'): {
254           "m": 488,
255           "h": "invalid",
256           "s": 538,
257           "t": 1335146921,
258         },
259       },
260     }
261     options = self._get_option(isolate_file)
262     tools.write_json(options.isolated, input_data, False)
263
264     # A CompleteState object contains two parts:
265     # - Result instance stored in complete_state.isolated, corresponding to the
266     #   .isolated file, is what is read by run_test_from_archive.py.
267     # - SavedState instance stored in compelte_state.saved_state,
268     #   corresponding to the .state file, which is simply to aid the developer
269     #   when re-running the same command multiple times and contain
270     #   discardable information.
271     complete_state = isolate.load_complete_state(options, self.cwd, None, False)
272     actual_isolated = complete_state.saved_state.to_isolated()
273     actual_saved_state = complete_state.saved_state.flatten()
274
275     expected_isolated = {
276       'algo': 'sha-1',
277       'command': ['python', 'touch_root.py'],
278       'files': {
279         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
280           'm': 488,
281           'h': hash_file('tests', 'isolate', 'touch_root.py'),
282           's': _size('tests', 'isolate', 'touch_root.py'),
283         },
284         u'isolate.py': {
285           'm': 488,
286           'h': hash_file('isolate.py'),
287           's': _size('isolate.py'),
288         },
289       },
290       'read_only': 1,
291       'relative_cwd': os.path.join(u'tests', 'isolate'),
292       'version': isolated_format.ISOLATED_FILE_VERSION,
293     }
294     self._cleanup_isolated(expected_isolated)
295     self.assertEqual(expected_isolated, actual_isolated)
296
297     expected_saved_state = {
298       'OS': sys.platform,
299       'algo': 'sha-1',
300       'child_isolated_files': [],
301       'command': ['python', 'touch_root.py'],
302       'config_variables': {
303         'OS': 'linux',
304         'chromeos': options.config_variables['chromeos'],
305       },
306       'extra_variables': {
307         'foo': 'bar',
308       },
309       'files': {
310         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
311           'm': 488,
312           'h': hash_file('tests', 'isolate', 'touch_root.py'),
313           's': _size('tests', 'isolate', 'touch_root.py'),
314         },
315         u'isolate.py': {
316           'm': 488,
317           'h': hash_file('isolate.py'),
318           's': _size('isolate.py'),
319         },
320       },
321       'isolate_file': file_path.safe_relpath(
322           file_path.get_native_path_case(isolate_file),
323           os.path.dirname(options.isolated)),
324       'path_variables': {},
325       'relative_cwd': os.path.join(u'tests', 'isolate'),
326       'root_dir': file_path.get_native_path_case(ROOT_DIR),
327       'version': isolate.SavedState.EXPECTED_VERSION,
328     }
329     self._cleanup_isolated(expected_saved_state)
330     self._cleanup_saved_state(actual_saved_state)
331     self.assertEqual(expected_saved_state, actual_saved_state)
332
333   def test_subdir(self):
334     # The resulting .isolated file will be missing ../../isolate.py. It is
335     # because this file is outside the --subdir parameter.
336     isolate_file = os.path.join(
337         ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
338     options = self._get_option(isolate_file)
339     complete_state = isolate.load_complete_state(
340         options, self.cwd, os.path.join('tests', 'isolate'), False)
341     actual_isolated = complete_state.saved_state.to_isolated()
342     actual_saved_state = complete_state.saved_state.flatten()
343
344     expected_isolated =  {
345       'algo': 'sha-1',
346       'command': ['python', 'touch_root.py'],
347       'files': {
348         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
349           'm': 488,
350           'h': hash_file('tests', 'isolate', 'touch_root.py'),
351           's': _size('tests', 'isolate', 'touch_root.py'),
352         },
353       },
354       'read_only': 1,
355       'relative_cwd': os.path.join(u'tests', 'isolate'),
356       'version': isolated_format.ISOLATED_FILE_VERSION,
357     }
358     self._cleanup_isolated(expected_isolated)
359     self.assertEqual(expected_isolated, actual_isolated)
360
361     expected_saved_state = {
362       'OS': sys.platform,
363       'algo': 'sha-1',
364       'child_isolated_files': [],
365       'command': ['python', 'touch_root.py'],
366       'config_variables': {
367         'OS': 'linux',
368         'chromeos': 1,
369       },
370       'extra_variables': {
371         'foo': 'bar',
372       },
373       'files': {
374         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
375           'm': 488,
376           'h': hash_file('tests', 'isolate', 'touch_root.py'),
377           's': _size('tests', 'isolate', 'touch_root.py'),
378         },
379       },
380       'isolate_file': file_path.safe_relpath(
381           file_path.get_native_path_case(isolate_file),
382           os.path.dirname(options.isolated)),
383       'path_variables': {},
384       'relative_cwd': os.path.join(u'tests', 'isolate'),
385       'root_dir': file_path.get_native_path_case(ROOT_DIR),
386       'version': isolate.SavedState.EXPECTED_VERSION,
387     }
388     self._cleanup_isolated(expected_saved_state)
389     self._cleanup_saved_state(actual_saved_state)
390     self.assertEqual(expected_saved_state, actual_saved_state)
391
392   def test_subdir_variable(self):
393     # the resulting .isolated file will be missing ../../isolate.py. it is
394     # because this file is outside the --subdir parameter.
395     isolate_file = os.path.join(
396         ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
397     options = self._get_option(isolate_file)
398     # Path variables are keyed on the directory containing the .isolate file.
399     options.path_variables['TEST_ISOLATE'] = '.'
400     # Note that options.isolated is in self.directory, which is a temporary
401     # directory.
402     complete_state = isolate.load_complete_state(
403         options, os.path.join(ROOT_DIR, 'tests', 'isolate'),
404         '<(TEST_ISOLATE)', False)
405     actual_isolated = complete_state.saved_state.to_isolated()
406     actual_saved_state = complete_state.saved_state.flatten()
407
408     expected_isolated =  {
409       'algo': 'sha-1',
410       'command': ['python', 'touch_root.py'],
411       'files': {
412         os.path.join('tests', 'isolate', 'touch_root.py'): {
413           'm': 488,
414           'h': hash_file('tests', 'isolate', 'touch_root.py'),
415           's': _size('tests', 'isolate', 'touch_root.py'),
416         },
417       },
418       'read_only': 1,
419       'relative_cwd': os.path.join(u'tests', 'isolate'),
420       'version': isolated_format.ISOLATED_FILE_VERSION,
421     }
422     self._cleanup_isolated(expected_isolated)
423     self.assertEqual(expected_isolated, actual_isolated)
424
425     # It is important to note:
426     # - the root directory is ROOT_DIR.
427     # - relative_cwd is tests/isolate.
428     # - TEST_ISOLATE is based of relative_cwd, so it represents tests/isolate.
429     # - anything outside TEST_ISOLATE was not included in the 'files' section.
430     expected_saved_state = {
431       'OS': sys.platform,
432       'algo': 'sha-1',
433       'child_isolated_files': [],
434       'command': ['python', 'touch_root.py'],
435       'config_variables': {
436         'OS': 'linux',
437         'chromeos': 1,
438       },
439       'extra_variables': {
440         'foo': 'bar',
441       },
442       'files': {
443         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
444           'm': 488,
445           'h': hash_file('tests', 'isolate', 'touch_root.py'),
446           's': _size('tests', 'isolate', 'touch_root.py'),
447         },
448       },
449       'isolate_file': file_path.safe_relpath(
450           file_path.get_native_path_case(isolate_file),
451           os.path.dirname(options.isolated)),
452       'path_variables': {
453         'TEST_ISOLATE': '.',
454       },
455       'relative_cwd': os.path.join(u'tests', 'isolate'),
456       'root_dir': file_path.get_native_path_case(ROOT_DIR),
457       'version': isolate.SavedState.EXPECTED_VERSION,
458     }
459     self._cleanup_isolated(expected_saved_state)
460     self._cleanup_saved_state(actual_saved_state)
461     self.assertEqual(expected_saved_state, actual_saved_state)
462
463   def test_variable_not_exist(self):
464     isolate_file = os.path.join(
465         ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
466     options = self._get_option(isolate_file)
467     options.path_variables['PRODUCT_DIR'] = os.path.join(u'tests', u'isolate')
468     native_cwd = file_path.get_native_path_case(unicode(self.cwd))
469     try:
470       isolate.load_complete_state(options, self.cwd, None, False)
471       self.fail()
472     except isolate.ExecutionError, e:
473       self.assertEqual(
474           'PRODUCT_DIR=%s is not a directory' %
475             os.path.join(native_cwd, 'tests', 'isolate'),
476           e.args[0])
477
478   def test_variable(self):
479     isolate_file = os.path.join(
480         ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
481     options = self._get_option(isolate_file)
482     options.path_variables['PRODUCT_DIR'] = os.path.join('tests', 'isolate')
483     complete_state = isolate.load_complete_state(options, ROOT_DIR, None, False)
484     actual_isolated = complete_state.saved_state.to_isolated()
485     actual_saved_state = complete_state.saved_state.flatten()
486
487     expected_isolated =  {
488       'algo': 'sha-1',
489       'command': ['python', 'touch_root.py'],
490       'files': {
491         u'isolate.py': {
492           'm': 488,
493           'h': hash_file('isolate.py'),
494           's': _size('isolate.py'),
495         },
496         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
497           'm': 488,
498           'h': hash_file('tests', 'isolate', 'touch_root.py'),
499           's': _size('tests', 'isolate', 'touch_root.py'),
500         },
501       },
502       'read_only': 1,
503       'relative_cwd': os.path.join(u'tests', 'isolate'),
504       'version': isolated_format.ISOLATED_FILE_VERSION,
505     }
506     self._cleanup_isolated(expected_isolated)
507     self.assertEqual(expected_isolated, actual_isolated)
508
509     expected_saved_state = {
510       'OS': sys.platform,
511       'algo': 'sha-1',
512       'child_isolated_files': [],
513       'command': ['python', 'touch_root.py'],
514       'config_variables': {
515         'OS': 'linux',
516         'chromeos': 1,
517       },
518       'extra_variables': {
519         'foo': 'bar',
520       },
521       'files': {
522         u'isolate.py': {
523           'm': 488,
524           'h': hash_file('isolate.py'),
525           's': _size('isolate.py'),
526         },
527         os.path.join(u'tests', 'isolate', 'touch_root.py'): {
528           'm': 488,
529           'h': hash_file('tests', 'isolate', 'touch_root.py'),
530           's': _size('tests', 'isolate', 'touch_root.py'),
531         },
532       },
533       'isolate_file': file_path.safe_relpath(
534           file_path.get_native_path_case(isolate_file),
535           os.path.dirname(options.isolated)),
536       'path_variables': {
537         'PRODUCT_DIR': '.',
538       },
539       'relative_cwd': os.path.join(u'tests', 'isolate'),
540       'root_dir': file_path.get_native_path_case(ROOT_DIR),
541       'version': isolate.SavedState.EXPECTED_VERSION,
542     }
543     self._cleanup_isolated(expected_saved_state)
544     self._cleanup_saved_state(actual_saved_state)
545     self.assertEqual(expected_saved_state, actual_saved_state)
546     self.assertEqual([], os.listdir(self.directory))
547
548   def test_root_dir_because_of_variable(self):
549     # Ensures that load_isolate() works even when path variables have deep root
550     # dirs. The end result is similar to touch_root.isolate, except that
551     # no_run.isolate doesn't reference '..' at all.
552     #
553     # A real world example would be PRODUCT_DIR=../../out/Release but nothing in
554     # this directory is mapped.
555     #
556     # Imagine base/base_unittests.isolate would not map anything in
557     # PRODUCT_DIR. In that case, the automatically determined root dir is
558     # src/base, since nothing outside this directory is mapped.
559     isolate_file = os.path.join(
560         ROOT_DIR, 'tests', 'isolate', 'no_run.isolate')
561     options = self._get_option(isolate_file)
562     # Any directory outside ROOT_DIR/tests/isolate.
563     options.path_variables['PRODUCT_DIR'] = os.path.join('third_party')
564     complete_state = isolate.load_complete_state(options, ROOT_DIR, None, False)
565     actual_isolated = complete_state.saved_state.to_isolated()
566     actual_saved_state = complete_state.saved_state.flatten()
567
568     expected_isolated = {
569       'algo': 'sha-1',
570       'files': {
571         os.path.join(u'tests', 'isolate', 'files1', 'subdir', '42.txt'): {
572           'm': 416,
573           'h': hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt'),
574           's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
575         },
576         os.path.join(u'tests', 'isolate', 'files1', 'test_file1.txt'): {
577           'm': 416,
578           'h': hash_file('tests', 'isolate', 'files1', 'test_file1.txt'),
579           's': _size('tests', 'isolate', 'files1', 'test_file1.txt'),
580         },
581         os.path.join(u'tests', 'isolate', 'files1', 'test_file2.txt'): {
582           'm': 416,
583           'h': hash_file('tests', 'isolate', 'files1', 'test_file2.txt'),
584           's': _size('tests', 'isolate', 'files1', 'test_file2.txt'),
585         },
586         os.path.join(u'tests', 'isolate', 'no_run.isolate'): {
587           'm': 416,
588           'h': hash_file('tests', 'isolate', 'no_run.isolate'),
589           's': _size('tests', 'isolate', 'no_run.isolate'),
590         },
591       },
592       'read_only': 1,
593       'relative_cwd': os.path.join(u'tests', 'isolate'),
594       'version': isolated_format.ISOLATED_FILE_VERSION,
595     }
596     self._cleanup_isolated(expected_isolated)
597     self.assertEqual(expected_isolated, actual_isolated)
598
599     expected_saved_state = {
600       'OS': sys.platform,
601       'algo': 'sha-1',
602       'child_isolated_files': [],
603       'command': [],
604       'config_variables': {
605         'OS': 'linux',
606         'chromeos': 1,
607       },
608       'extra_variables': {
609         'foo': 'bar',
610       },
611       'files': {
612         os.path.join(u'tests', 'isolate', 'files1', 'subdir', '42.txt'): {
613           'm': 416,
614           'h': hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt'),
615           's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
616         },
617         os.path.join(u'tests', 'isolate', 'files1', 'test_file1.txt'): {
618           'm': 416,
619           'h': hash_file('tests', 'isolate', 'files1', 'test_file1.txt'),
620           's': _size('tests', 'isolate', 'files1', 'test_file1.txt'),
621         },
622         os.path.join(u'tests', 'isolate', 'files1', 'test_file2.txt'): {
623           'm': 416,
624           'h': hash_file('tests', 'isolate', 'files1', 'test_file2.txt'),
625           's': _size('tests', 'isolate', 'files1', 'test_file2.txt'),
626         },
627         os.path.join(u'tests', 'isolate', 'no_run.isolate'): {
628           'm': 416,
629           'h': hash_file('tests', 'isolate', 'no_run.isolate'),
630           's': _size('tests', 'isolate', 'no_run.isolate'),
631         },
632       },
633       'isolate_file': file_path.safe_relpath(
634           file_path.get_native_path_case(isolate_file),
635           os.path.dirname(options.isolated)),
636       'path_variables': {
637         'PRODUCT_DIR': os.path.join(u'..', '..', 'third_party'),
638       },
639       'relative_cwd': os.path.join(u'tests', 'isolate'),
640       'root_dir': file_path.get_native_path_case(ROOT_DIR),
641       'version': isolate.SavedState.EXPECTED_VERSION,
642     }
643     self._cleanup_isolated(expected_saved_state)
644     self._cleanup_saved_state(actual_saved_state)
645     self.assertEqual(expected_saved_state, actual_saved_state)
646     self.assertEqual([], os.listdir(self.directory))
647
648   def test_chromium_split(self):
649     # Create an .isolate file and a tree of random stuff.
650     isolate_file = os.path.join(
651         ROOT_DIR, 'tests', 'isolate', 'split.isolate')
652     options = self._get_option(isolate_file)
653     options.path_variables = {
654       'DEPTH': '.',
655       'PRODUCT_DIR': os.path.join('files1'),
656     }
657     options.config_variables = {
658       'OS': 'linux',
659     }
660     complete_state = isolate.load_complete_state(
661         options, os.path.join(ROOT_DIR, 'tests', 'isolate'), None, False)
662     # By saving the files, it forces splitting the data up.
663     complete_state.save_files()
664
665     actual_isolated_master = tools.read_json(
666         os.path.join(self.directory, 'foo.isolated'))
667     expected_isolated_master = {
668       u'algo': u'sha-1',
669       u'command': [u'python', u'split.py'],
670       u'files': {
671         u'split.py': {
672           u'm': 488,
673           u'h': unicode(hash_file('tests', 'isolate', 'split.py')),
674           u's': _size('tests', 'isolate', 'split.py'),
675         },
676       },
677       u'includes': [
678         unicode(hash_file(os.path.join(self.directory, 'foo.0.isolated'))),
679         unicode(hash_file(os.path.join(self.directory, 'foo.1.isolated'))),
680       ],
681       u'read_only': 1,
682       u'relative_cwd': u'.',
683       u'version': unicode(isolated_format.ISOLATED_FILE_VERSION),
684     }
685     self._cleanup_isolated(expected_isolated_master)
686     self.assertEqual(expected_isolated_master, actual_isolated_master)
687
688     actual_isolated_0 = tools.read_json(
689         os.path.join(self.directory, 'foo.0.isolated'))
690     expected_isolated_0 = {
691       u'algo': u'sha-1',
692       u'files': {
693         os.path.join(u'test', 'data', 'foo.txt'): {
694           u'm': 416,
695           u'h': unicode(
696               hash_file('tests', 'isolate', 'test', 'data', 'foo.txt')),
697           u's': _size('tests', 'isolate', 'test', 'data', 'foo.txt'),
698         },
699       },
700       u'version': unicode(isolated_format.ISOLATED_FILE_VERSION),
701     }
702     self._cleanup_isolated(expected_isolated_0)
703     self.assertEqual(expected_isolated_0, actual_isolated_0)
704
705     actual_isolated_1 = tools.read_json(
706         os.path.join(self.directory, 'foo.1.isolated'))
707     expected_isolated_1 = {
708       u'algo': u'sha-1',
709       u'files': {
710         os.path.join(u'files1', 'subdir', '42.txt'): {
711           u'm': 416,
712           u'h': unicode(
713               hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt')),
714           u's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
715         },
716       },
717       u'version': unicode(isolated_format.ISOLATED_FILE_VERSION),
718     }
719     self._cleanup_isolated(expected_isolated_1)
720     self.assertEqual(expected_isolated_1, actual_isolated_1)
721
722     actual_saved_state = tools.read_json(
723         isolate.isolatedfile_to_state(options.isolated))
724     isolated_base = unicode(os.path.basename(options.isolated))
725     expected_saved_state = {
726       u'OS': unicode(sys.platform),
727       u'algo': u'sha-1',
728       u'child_isolated_files': [
729         isolated_base[:-len('.isolated')] + '.0.isolated',
730         isolated_base[:-len('.isolated')] + '.1.isolated',
731       ],
732       u'command': [u'python', u'split.py'],
733       u'config_variables': {
734         u'OS': u'linux',
735       },
736       u'extra_variables': {
737         u'foo': u'bar',
738       },
739       u'files': {
740         os.path.join(u'files1', 'subdir', '42.txt'): {
741           u'm': 416,
742           u'h': unicode(
743               hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt')),
744           u's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
745         },
746         u'split.py': {
747           u'm': 488,
748           u'h': unicode(hash_file('tests', 'isolate', 'split.py')),
749           u's': _size('tests', 'isolate', 'split.py'),
750         },
751         os.path.join(u'test', 'data', 'foo.txt'): {
752           u'm': 416,
753           u'h': unicode(
754               hash_file('tests', 'isolate', 'test', 'data', 'foo.txt')),
755           u's': _size('tests', 'isolate', 'test', 'data', 'foo.txt'),
756         },
757       },
758       u'isolate_file': file_path.safe_relpath(
759           file_path.get_native_path_case(isolate_file),
760           unicode(os.path.dirname(options.isolated))),
761       u'path_variables': {
762         u'DEPTH': u'.',
763         u'PRODUCT_DIR': u'files1',
764       },
765       u'relative_cwd': u'.',
766       u'root_dir': file_path.get_native_path_case(
767           os.path.dirname(isolate_file)),
768       u'version': unicode(isolate.SavedState.EXPECTED_VERSION),
769     }
770     self._cleanup_isolated(expected_saved_state)
771     self._cleanup_saved_state(actual_saved_state)
772     self.assertEqual(expected_saved_state, actual_saved_state)
773     self.assertEqual(
774         [
775           'foo.0.isolated', 'foo.1.isolated',
776           'foo.isolated', 'foo.isolated.state',
777         ],
778         sorted(os.listdir(self.directory)))
779
780   def test_load_isolate_include_command(self):
781     # Ensure that using a .isolate that includes another one in a different
782     # directory will lead to the proper relative directory. See
783     # test_load_with_includes_with_commands in isolate_format_test.py as
784     # reference.
785
786     # Exactly the same thing as in isolate_format_test.py
787     isolate1 = {
788       'conditions': [
789         ['OS=="amiga" or OS=="win"', {
790           'variables': {
791             'command': [
792               'foo', 'amiga_or_win',
793             ],
794           },
795         }],
796         ['OS=="linux"', {
797           'variables': {
798             'command': [
799               'foo', 'linux',
800             ],
801             'files': [
802               'file_linux',
803             ],
804           },
805         }],
806         ['OS=="mac" or OS=="win"', {
807           'variables': {
808             'files': [
809               'file_non_linux',
810             ],
811           },
812         }],
813       ],
814     }
815     isolate2 = {
816       'conditions': [
817         ['OS=="linux" or OS=="mac"', {
818           'variables': {
819             'command': [
820               'foo', 'linux_or_mac',
821             ],
822             'files': [
823               'other/file',
824             ],
825           },
826         }],
827       ],
828     }
829     isolate3 = {
830       'includes': [
831         '../1/isolate1.isolate',
832         '2/isolate2.isolate',
833       ],
834       'conditions': [
835         ['OS=="amiga"', {
836           'variables': {
837             'files': [
838               'file_amiga',
839             ],
840           },
841         }],
842         ['OS=="mac"', {
843           'variables': {
844             'command': [
845               'foo', 'mac',
846             ],
847             'files': [
848               'file_mac',
849             ],
850           },
851         }],
852       ],
853     }
854
855     def test_with_os(
856         config_os, files_to_create, expected_files, command, relative_cwd):
857       """Creates a tree of files in a subdirectory for testing and test this
858       set of conditions.
859       """
860       directory = os.path.join(unicode(self.directory), config_os)
861       os.mkdir(directory)
862       isolate_dir = os.path.join(directory, u'isolate')
863       isolate_dir_1 = os.path.join(isolate_dir, u'1')
864       isolate_dir_3 = os.path.join(isolate_dir, u'3')
865       isolate_dir_3_2 = os.path.join(isolate_dir_3, u'2')
866       isolated_dir = os.path.join(directory, u'isolated')
867       os.mkdir(isolated_dir)
868       os.mkdir(isolate_dir)
869       os.mkdir(isolate_dir_1)
870       os.mkdir(isolate_dir_3)
871       os.mkdir(isolate_dir_3_2)
872       isolated = os.path.join(isolated_dir, u'foo.isolated')
873
874       with open(os.path.join(isolate_dir_1, 'isolate1.isolate'), 'wb') as f:
875         isolate_format.pretty_print(isolate1, f)
876       with open(os.path.join(isolate_dir_3_2, 'isolate2.isolate'), 'wb') as f:
877         isolate_format.pretty_print(isolate2, f)
878       root_isolate = os.path.join(isolate_dir_3, 'isolate3.isolate')
879       with open(root_isolate, 'wb') as f:
880         isolate_format.pretty_print(isolate3, f)
881
882       # Make all the touched files.
883       mapping = {1: isolate_dir_1, 2: isolate_dir_3_2, 3: isolate_dir_3}
884       for k, v in files_to_create.iteritems():
885         f = os.path.join(mapping[k], v)
886         base = os.path.dirname(f)
887         if not os.path.isdir(base):
888           os.mkdir(base)
889         open(f, 'wb').close()
890
891       c = isolate.CompleteState(isolated, isolate.SavedState(isolated_dir))
892       config = {
893         'OS': config_os,
894       }
895       c.load_isolate(
896           unicode(self.cwd), root_isolate, {}, config, {}, None, False)
897       # Note that load_isolate() doesn't retrieve the meta data about each file.
898       expected = {
899         'algo': 'sha-1',
900         'command': command,
901         'files': {unicode(f):{} for f in expected_files},
902         'read_only': 1,
903         'relative_cwd': relative_cwd,
904         'version': isolated_format.ISOLATED_FILE_VERSION,
905       }
906       self.assertEqual(expected, c.saved_state.to_isolated())
907
908     # root is .../isolate/.
909     test_with_os(
910         'amiga',
911         {
912           3: 'file_amiga',
913         },
914         (
915           u'3/file_amiga',
916         ),
917         ['foo', 'amiga_or_win'],
918         '1')
919     # root is .../isolate/.
920     test_with_os(
921         'linux',
922         {
923           1: 'file_linux',
924           2: 'other/file',
925         },
926         (
927           u'1/file_linux',
928           u'3/2/other/file',
929         ),
930         ['foo', 'linux_or_mac'],
931         '3/2')
932     # root is .../isolate/.
933     test_with_os(
934         'mac',
935         {
936           1: 'file_non_linux',
937           2: 'other/file',
938           3: 'file_mac',
939         },
940         (
941           u'1/file_non_linux',
942           u'3/2/other/file',
943           u'3/file_mac',
944         ),
945         ['foo', 'mac'],
946         '3')
947     # root is .../isolate/1/.
948     test_with_os(
949         'win',
950         {
951           1: 'file_non_linux',
952         },
953         (
954           u'file_non_linux',
955         ),
956         ['foo', 'amiga_or_win'],
957         '.')
958
959   def test_load_isolate_include_command_and_variables(self):
960     # Ensure that using a .isolate that includes another one in a different
961     # directory will lead to the proper relative directory when using variables.
962     # See test_load_with_includes_with_commands_and_variables in
963     # isolate_format_test.py as reference.
964     #
965     # With path variables, 'cwd' is used instead of the path to the .isolate
966     # file. So the files have to be set towards the cwd accordingly. While this
967     # may seem surprising, this makes the whole thing work in the first place.
968
969     # Almost exactly the same thing as in isolate_format_test.py plus the EXTRA
970     # for better testing with variable replacement.
971     isolate1 = {
972       'conditions': [
973         ['OS=="amiga" or OS=="win"', {
974           'variables': {
975             'command': [
976               'foo', 'amiga_or_win', '<(PATH)', '<(EXTRA)',
977             ],
978           },
979         }],
980         ['OS=="linux"', {
981           'variables': {
982             'command': [
983               'foo', 'linux', '<(PATH)', '<(EXTRA)',
984             ],
985             'files': [
986               '<(PATH)/file_linux',
987             ],
988           },
989         }],
990         ['OS=="mac" or OS=="win"', {
991           'variables': {
992             'files': [
993               '<(PATH)/file_non_linux',
994             ],
995           },
996         }],
997       ],
998     }
999     isolate2 = {
1000       'conditions': [
1001         ['OS=="linux" or OS=="mac"', {
1002           'variables': {
1003             'command': [
1004               'foo', 'linux_or_mac', '<(PATH)', '<(EXTRA)',
1005             ],
1006             'files': [
1007               '<(PATH)/other/file',
1008             ],
1009           },
1010         }],
1011       ],
1012     }
1013     isolate3 = {
1014       'includes': [
1015         '../1/isolate1.isolate',
1016         '2/isolate2.isolate',
1017       ],
1018       'conditions': [
1019         ['OS=="amiga"', {
1020           'variables': {
1021             'files': [
1022               '<(PATH)/file_amiga',
1023             ],
1024           },
1025         }],
1026         ['OS=="mac"', {
1027           'variables': {
1028             'command': [
1029               'foo', 'mac', '<(PATH)', '<(EXTRA)',
1030             ],
1031             'files': [
1032               '<(PATH)/file_mac',
1033             ],
1034           },
1035         }],
1036       ],
1037     }
1038
1039     def test_with_os(config_os, expected_files, command, relative_cwd):
1040       """Creates a tree of files in a subdirectory for testing and test this
1041       set of conditions.
1042       """
1043       directory = os.path.join(unicode(self.directory), config_os)
1044       os.mkdir(directory)
1045       cwd = os.path.join(unicode(self.cwd), config_os)
1046       os.mkdir(cwd)
1047       isolate_dir = os.path.join(directory, u'isolate')
1048       isolate_dir_1 = os.path.join(isolate_dir, u'1')
1049       isolate_dir_3 = os.path.join(isolate_dir, u'3')
1050       isolate_dir_3_2 = os.path.join(isolate_dir_3, u'2')
1051       isolated_dir = os.path.join(directory, u'isolated')
1052       os.mkdir(isolated_dir)
1053       os.mkdir(isolate_dir)
1054       os.mkdir(isolate_dir_1)
1055       os.mkdir(isolate_dir_3)
1056       os.mkdir(isolate_dir_3_2)
1057       isolated = os.path.join(isolated_dir, u'foo.isolated')
1058
1059       with open(os.path.join(isolate_dir_1, 'isolate1.isolate'), 'wb') as f:
1060         isolate_format.pretty_print(isolate1, f)
1061       with open(os.path.join(isolate_dir_3_2, 'isolate2.isolate'), 'wb') as f:
1062         isolate_format.pretty_print(isolate2, f)
1063       root_isolate = os.path.join(isolate_dir_3, 'isolate3.isolate')
1064       with open(root_isolate, 'wb') as f:
1065         isolate_format.pretty_print(isolate3, f)
1066
1067       # Make all the touched files.
1068       path_dir = os.path.join(cwd, 'path')
1069       os.mkdir(path_dir)
1070       for v in expected_files:
1071         f = os.path.join(path_dir, v)
1072         base = os.path.dirname(f)
1073         if not os.path.isdir(base):
1074           os.makedirs(base)
1075         logging.warn(f)
1076         open(f, 'wb').close()
1077
1078       c = isolate.CompleteState(isolated, isolate.SavedState(isolated_dir))
1079       config = {
1080         'OS': config_os,
1081       }
1082       paths = {
1083         'PATH': 'path/',
1084       }
1085       extra = {
1086         'EXTRA': 'indeed',
1087       }
1088       c.load_isolate(
1089           unicode(cwd), root_isolate, paths, config, extra, None, False)
1090       # Note that load_isolate() doesn't retrieve the meta data about each file.
1091       expected = {
1092         'algo': 'sha-1',
1093         'command': command,
1094         'files': {
1095           unicode(os.path.join(cwd_name, config_os, 'path', f)): {}
1096           for f in expected_files
1097         },
1098         'read_only': 1,
1099         'relative_cwd': relative_cwd,
1100         'version': isolated_format.ISOLATED_FILE_VERSION,
1101       }
1102       self.assertEqual(expected, c.saved_state.to_isolated())
1103
1104     cwd_name = os.path.basename(self.cwd)
1105     dir_name = os.path.basename(self.directory)
1106     test_with_os(
1107         'amiga',
1108         (
1109           'file_amiga',
1110         ),
1111         [
1112           'foo',
1113           'amiga_or_win',
1114           u'../../../../%s/amiga/path' % cwd_name,
1115           'indeed',
1116         ],
1117         u'%s/amiga/isolate/1' % dir_name)
1118     test_with_os(
1119         'linux',
1120         (
1121           u'file_linux',
1122           u'other/file',
1123         ),
1124         [
1125           'foo',
1126           'linux_or_mac',
1127           u'../../../../../%s/linux/path' % cwd_name,
1128           'indeed',
1129         ],
1130         u'%s/linux/isolate/3/2' % dir_name)
1131     test_with_os(
1132         'mac',
1133         (
1134           'file_non_linux',
1135           'other/file',
1136           'file_mac',
1137         ),
1138         [
1139           'foo',
1140           'mac',
1141           u'../../../../%s/mac/path' % cwd_name,
1142           'indeed',
1143         ],
1144         u'%s/mac/isolate/3' % dir_name)
1145     test_with_os(
1146         'win',
1147         (
1148           'file_non_linux',
1149         ),
1150         [
1151           'foo',
1152           'amiga_or_win',
1153           u'../../../../%s/win/path' % cwd_name,
1154           'indeed',
1155         ],
1156         u'%s/win/isolate/1' % dir_name)
1157
1158
1159 class IsolateCommand(IsolateBase):
1160   def load_complete_state(self, *_):
1161     """Creates a minimalist CompleteState instance without an .isolated
1162     reference.
1163     """
1164     out = isolate.CompleteState(None, isolate.SavedState(self.cwd))
1165     out.saved_state.isolate_file = u'blah.isolate'
1166     out.saved_state.relative_cwd = u''
1167     out.saved_state.root_dir = ROOT_DIR
1168     return out
1169
1170   def test_CMDarchive(self):
1171     actual = []
1172
1173     def mocked_upload_tree(base_url, infiles, namespace):
1174       # |infiles| may be a generator of pair, materialize it into a list.
1175       actual.append({
1176         'base_url': base_url,
1177         'infiles': dict(infiles),
1178         'namespace': namespace,
1179       })
1180     self.mock(isolateserver, 'upload_tree', mocked_upload_tree)
1181
1182     def join(*path):
1183       return os.path.join(self.cwd, *path)
1184
1185     isolate_file = join('x.isolate')
1186     isolated_file = join('x.isolated')
1187     with open(isolate_file, 'wb') as f:
1188       f.write(
1189           '# Foo\n'
1190           '{'
1191           '  \'conditions\':['
1192           '    [\'OS=="dendy"\', {'
1193           '      \'variables\': {'
1194           '        \'files\': [\'foo\'],'
1195           '      },'
1196           '    }],'
1197           '  ],'
1198           '}')
1199     with open(join('foo'), 'wb') as f:
1200       f.write('fooo')
1201
1202     self.mock(sys, 'stdout', cStringIO.StringIO())
1203     cmd = [
1204         '-i', isolate_file,
1205         '-s', isolated_file,
1206         '--isolate-server', 'http://localhost:1',
1207         '--config-variable', 'OS', 'dendy',
1208     ]
1209     self.assertEqual(0, isolate.CMDarchive(optparse.OptionParser(), cmd))
1210     expected = [
1211         {
1212           'base_url': 'http://localhost:1',
1213           'infiles': {
1214             join(isolated_file): {
1215               'priority': '0',
1216             },
1217             join('foo'): {
1218               'h': '520d41b29f891bbaccf31d9fcfa72e82ea20fcf0',
1219               's': 4,
1220             },
1221           },
1222           'namespace': 'default-gzip',
1223         },
1224     ]
1225     # These always change.
1226     actual[0]['infiles'][join(isolated_file)].pop('h')
1227     actual[0]['infiles'][join(isolated_file)].pop('s')
1228     actual[0]['infiles'][join('foo')].pop('m')
1229     actual[0]['infiles'][join('foo')].pop('t')
1230     self.assertEqual(expected, actual)
1231
1232   def test_CMDbatcharchive(self):
1233     # Same as test_CMDarchive but via code path that parses *.gen.json files.
1234     actual = []
1235
1236     def mocked_upload_tree(base_url, infiles, namespace):
1237       # |infiles| may be a generator of pair, materialize it into a list.
1238       actual.append({
1239         'base_url': base_url,
1240         'infiles': dict(infiles),
1241         'namespace': namespace,
1242       })
1243     self.mock(isolateserver, 'upload_tree', mocked_upload_tree)
1244
1245     def join(*path):
1246       return os.path.join(self.cwd, *path)
1247
1248     # First isolate: x.isolate.
1249     isolate_file_x = join('x.isolate')
1250     isolated_file_x = join('x.isolated')
1251     with open(isolate_file_x, 'wb') as f:
1252       f.write(
1253           '# Foo\n'
1254           '{'
1255           '  \'conditions\':['
1256           '    [\'OS=="dendy"\', {'
1257           '      \'variables\': {'
1258           '        \'files\': [\'foo\'],'
1259           '      },'
1260           '    }],'
1261           '  ],'
1262           '}')
1263     with open(join('foo'), 'wb') as f:
1264       f.write('fooo')
1265     with open(join('x.isolated.gen.json'), 'wb') as f:
1266       json.dump({
1267         'args': [
1268           '-i', isolate_file_x,
1269           '-s', isolated_file_x,
1270           '--config-variable', 'OS', 'dendy',
1271         ],
1272         'dir': self.cwd,
1273         'version': 1,
1274       }, f)
1275
1276     # Second isolate: y.isolate.
1277     isolate_file_y = join('y.isolate')
1278     isolated_file_y = join('y.isolated')
1279     with open(isolate_file_y, 'wb') as f:
1280       f.write(
1281           '# Foo\n'
1282           '{'
1283           '  \'conditions\':['
1284           '    [\'OS=="dendy"\', {'
1285           '      \'variables\': {'
1286           '        \'files\': [\'bar\'],'
1287           '      },'
1288           '    }],'
1289           '  ],'
1290           '}')
1291     with open(join('bar'), 'wb') as f:
1292       f.write('barr')
1293     with open(join('y.isolated.gen.json'), 'wb') as f:
1294       json.dump({
1295         'args': [
1296           '-i', isolate_file_y,
1297           '-s', isolated_file_y,
1298           '--config-variable', 'OS', 'dendy',
1299         ],
1300         'dir': self.cwd,
1301         'version': 1,
1302       }, f)
1303
1304     self.mock(sys, 'stdout', cStringIO.StringIO())
1305     cmd = [
1306       '--isolate-server', 'http://localhost:1',
1307       '--dump-json', 'json_output.json',
1308       join('x.isolated.gen.json'),
1309       join('y.isolated.gen.json'),
1310     ]
1311     self.assertEqual(
1312         0, isolate.CMDbatcharchive(tools.OptionParserWithLogging(), cmd))
1313     expected = [
1314         {
1315           'base_url': 'http://localhost:1',
1316           'infiles': {
1317             join(isolated_file_x): {
1318               'priority': '0',
1319             },
1320             join('foo'): {
1321               'h': '520d41b29f891bbaccf31d9fcfa72e82ea20fcf0',
1322               's': 4,
1323             },
1324             join(isolated_file_y): {
1325               'priority': '0',
1326             },
1327             join('bar'): {
1328               'h': 'e918b3a3f9597e3cfdc62ce20ecf5756191cb3ec',
1329               's': 4,
1330             },
1331           },
1332           'namespace': 'default-gzip',
1333         },
1334     ]
1335     # These always change.
1336     actual[0]['infiles'][join(isolated_file_x)].pop('h')
1337     actual[0]['infiles'][join(isolated_file_x)].pop('s')
1338     actual[0]['infiles'][join('foo')].pop('m')
1339     actual[0]['infiles'][join('foo')].pop('t')
1340     actual[0]['infiles'][join(isolated_file_y)].pop('h')
1341     actual[0]['infiles'][join(isolated_file_y)].pop('s')
1342     actual[0]['infiles'][join('bar')].pop('m')
1343     actual[0]['infiles'][join('bar')].pop('t')
1344     self.assertEqual(expected, actual)
1345
1346     expected_json = {
1347       'x': isolated_format.hash_file('x.isolated', ALGO),
1348       'y': isolated_format.hash_file('y.isolated', ALGO),
1349     }
1350     self.assertEqual(expected_json, tools.read_json('json_output.json'))
1351
1352   def test_CMDcheck_empty(self):
1353     isolate_file = os.path.join(self.cwd, 'x.isolate')
1354     isolated_file = os.path.join(self.cwd, 'x.isolated')
1355     with open(isolate_file, 'wb') as f:
1356       f.write('# Foo\n{\n}')
1357
1358     self.mock(sys, 'stdout', cStringIO.StringIO())
1359     cmd = ['-i', isolate_file, '-s', isolated_file]
1360     isolate.CMDcheck(optparse.OptionParser(), cmd)
1361
1362   def test_CMDcheck_stale_version(self):
1363     isolate_file = os.path.join(self.cwd, 'x.isolate')
1364     isolated_file = os.path.join(self.cwd, 'x.isolated')
1365     with open(isolate_file, 'wb') as f:
1366       f.write(
1367           '# Foo\n'
1368           '{'
1369           '  \'conditions\':['
1370           '    [\'OS=="dendy"\', {'
1371           '      \'variables\': {'
1372           '        \'command\': [\'foo\'],'
1373           '      },'
1374           '    }],'
1375           '  ],'
1376           '}')
1377
1378     self.mock(sys, 'stdout', cStringIO.StringIO())
1379     cmd = [
1380         '-i', isolate_file,
1381         '-s', isolated_file,
1382         '--config-variable', 'OS=dendy',
1383     ]
1384     self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1385
1386     with open(isolate_file, 'rb') as f:
1387       actual = f.read()
1388     expected = (
1389         '# Foo\n{  \'conditions\':[    [\'OS=="dendy"\', {      '
1390         '\'variables\': {        \'command\': [\'foo\'],      },    }],  ],}')
1391     self.assertEqual(expected, actual)
1392
1393     with open(isolated_file, 'rb') as f:
1394       actual_isolated = f.read()
1395     expected_isolated = (
1396         '{"algo":"sha-1","command":["foo"],"files":{},'
1397         '"read_only":1,"relative_cwd":".","version":"%s"}'
1398     ) % isolated_format.ISOLATED_FILE_VERSION
1399     self.assertEqual(expected_isolated, actual_isolated)
1400     isolated_data = json.loads(actual_isolated)
1401
1402     with open(isolated_file + '.state', 'rb') as f:
1403       actual_isolated_state = f.read()
1404     expected_isolated_state = (
1405         '{"OS":"%s","algo":"sha-1","child_isolated_files":[],"command":["foo"],'
1406         '"config_variables":{"OS":"dendy"},'
1407         '"extra_variables":{"EXECUTABLE_SUFFIX":""},"files":{},'
1408         '"isolate_file":"x.isolate","path_variables":{},'
1409         '"relative_cwd":".","root_dir":"%s","version":"%s"}'
1410     ) % (sys.platform, self.cwd, isolate.SavedState.EXPECTED_VERSION)
1411     self.assertEqual(expected_isolated_state, actual_isolated_state)
1412     isolated_state_data = json.loads(actual_isolated_state)
1413
1414     # Now edit the .isolated.state file to break the version number and make
1415     # sure it doesn't crash.
1416     with open(isolated_file + '.state', 'wb') as f:
1417       isolated_state_data['version'] = '100.42'
1418       json.dump(isolated_state_data, f)
1419     self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1420
1421     # Now edit the .isolated file to break the version number and make
1422     # sure it doesn't crash.
1423     with open(isolated_file, 'wb') as f:
1424       isolated_data['version'] = '100.42'
1425       json.dump(isolated_data, f)
1426     self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1427
1428     # Make sure the files were regenerated.
1429     with open(isolated_file, 'rb') as f:
1430       actual_isolated = f.read()
1431     self.assertEqual(expected_isolated, actual_isolated)
1432     with open(isolated_file + '.state', 'rb') as f:
1433       actual_isolated_state = f.read()
1434     self.assertEqual(expected_isolated_state, actual_isolated_state)
1435
1436   def test_CMDcheck_new_variables(self):
1437     # Test bug #61.
1438     isolate_file = os.path.join(self.cwd, 'x.isolate')
1439     isolated_file = os.path.join(self.cwd, 'x.isolated')
1440     cmd = [
1441         '-i', isolate_file,
1442         '-s', isolated_file,
1443         '--config-variable', 'OS=dendy',
1444     ]
1445     with open(isolate_file, 'wb') as f:
1446       f.write(
1447           '# Foo\n'
1448           '{'
1449           '  \'conditions\':['
1450           '    [\'OS=="dendy"\', {'
1451           '      \'variables\': {'
1452           '        \'command\': [\'foo\'],'
1453           '        \'files\': [\'foo\'],'
1454           '      },'
1455           '    }],'
1456           '  ],'
1457           '}')
1458     with open(os.path.join(self.cwd, 'foo'), 'wb') as f:
1459       f.write('yeah')
1460
1461     self.mock(sys, 'stdout', cStringIO.StringIO())
1462     self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1463
1464     # Now add a new config variable.
1465     with open(isolate_file, 'wb') as f:
1466       f.write(
1467           '# Foo\n'
1468           '{'
1469           '  \'conditions\':['
1470           '    [\'OS=="dendy"\', {'
1471           '      \'variables\': {'
1472           '        \'command\': [\'foo\'],'
1473           '        \'files\': [\'foo\'],'
1474           '      },'
1475           '    }],'
1476           '    [\'foo=="baz"\', {'
1477           '      \'variables\': {'
1478           '        \'files\': [\'bar\'],'
1479           '      },'
1480           '    }],'
1481           '  ],'
1482           '}')
1483     with open(os.path.join(self.cwd, 'bar'), 'wb') as f:
1484       f.write('yeah right!')
1485
1486     # The configuration is OS=dendy and foo=bar. So it should load both
1487     # configurations.
1488     self.assertEqual(
1489         0,
1490         isolate.CMDcheck(
1491             optparse.OptionParser(), cmd + ['--config-variable', 'foo=bar']))
1492
1493   def test_CMDcheck_isolate_copied(self):
1494     # Note that moving the .isolate file is a different code path, this is about
1495     # copying the .isolate file to a new place and specifying the new location
1496     # on a subsequent execution.
1497     x_isolate_file = os.path.join(self.cwd, 'x.isolate')
1498     isolated_file = os.path.join(self.cwd, 'x.isolated')
1499     cmd = ['-i', x_isolate_file, '-s', isolated_file]
1500     with open(x_isolate_file, 'wb') as f:
1501       f.write('{}')
1502     self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1503     self.assertTrue(os.path.isfile(isolated_file + '.state'))
1504     with open(isolated_file + '.state', 'rb') as f:
1505       self.assertEqual(json.load(f)['isolate_file'], 'x.isolate')
1506
1507     # Move the .isolate file.
1508     y_isolate_file = os.path.join(self.cwd, 'Y.isolate')
1509     shutil.copyfile(x_isolate_file, y_isolate_file)
1510     cmd = ['-i', y_isolate_file, '-s', isolated_file]
1511     self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1512     with open(isolated_file + '.state', 'rb') as f:
1513       self.assertEqual(json.load(f)['isolate_file'], 'Y.isolate')
1514
1515   def test_CMDrun_extra_args(self):
1516     cmd = [
1517       'run',
1518       '--isolate', 'blah.isolate',
1519       '--', 'extra_args',
1520     ]
1521     self.mock(isolate, 'load_complete_state', self.load_complete_state)
1522     self.mock(subprocess, 'call', lambda *_, **_kwargs: 0)
1523     self.assertEqual(0, isolate.CMDrun(optparse.OptionParser(), cmd))
1524
1525   def test_CMDrun_no_isolated(self):
1526     isolate_file = os.path.join(self.cwd, 'x.isolate')
1527     with open(isolate_file, 'wb') as f:
1528       f.write('{"variables": {"command": ["python", "-c", "print(\'hi\')"]} }')
1529
1530     def expect_call(cmd, cwd):
1531       self.assertEqual([sys.executable, '-c', "print('hi')", 'run'], cmd)
1532       self.assertTrue(os.path.isdir(cwd))
1533       return 0
1534     self.mock(subprocess, 'call', expect_call)
1535
1536     cmd = ['run', '--isolate', isolate_file]
1537     self.assertEqual(0, isolate.CMDrun(optparse.OptionParser(), cmd))
1538
1539
1540 def clear_env_vars():
1541   for e in ('ISOLATE_DEBUG', 'ISOLATE_SERVER'):
1542     os.environ.pop(e, None)
1543
1544
1545 if __name__ == '__main__':
1546   clear_env_vars()
1547   test_utils.main()