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