fdf7b8392b5d39b26461c5dccb43676e5bbdcf77
[scons.git] / test / option-j.py
1 #!/usr/bin/env python
2 #
3 # __COPYRIGHT__
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 #
24
25 """
26 This tests the -j command line option, and the num_jobs
27 SConscript settable option.
28 """
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 import os.path
33 import string
34 import sys
35 import TestSCons
36
37 python = TestSCons.python
38
39 try:
40     import threading
41 except ImportError:
42     # if threads are not supported, then
43     # there is nothing to test
44     TestCmd.no_result()
45     sys.exit()
46
47
48 test = TestSCons.TestSCons()
49
50 test.write('build.py', r"""
51 import time
52 import sys
53 file = open(sys.argv[1], 'wb')
54 file.write(str(time.time()) + '\n')
55 time.sleep(1)
56 file.write(str(time.time()))
57 file.close()
58 """)
59
60 test.subdir('foo')
61
62 test.write(['foo','foo.in'], r"""
63 foo you
64 """)
65
66 test.write('SConstruct', """
67 MyBuild = Builder(action = r'%s build.py $TARGETS')
68 env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
69 env.MyBuild(target = 'f1', source = 'f1.in')
70 env.MyBuild(target = 'f2', source = 'f2.in')
71
72 def copyn(env, target, source):
73     import shutil
74     import time
75     time.sleep(1)
76     for t in target:
77         shutil.copy(str(source[0]), str(t))
78
79 t = env.Command(target=['foo/foo1.out', 'foo/foo2.out'],
80                 source='foo/foo.in',
81                 action=copyn)
82 env.Install('out', t)
83 """ % python)
84
85 def RunTest(args, extra):
86     """extra is used to make scons rebuild the output file"""
87     test.write('f1.in', 'f1.in'+extra)
88     test.write('f2.in', 'f2.in'+extra)
89
90     test.run(arguments = args)
91
92     str = test.read("f1")
93     start1,finish1 = map(float, string.split(str, "\n"))
94
95     str = test.read("f2")
96     start2,finish2 = map(float, string.split(str, "\n"))
97
98     return start2, finish1
99
100 # Test 2 parallel jobs.
101 # fail if the second file was not started
102 # before the first one was finished.
103 start2, finish1 = RunTest('-j 2 f1 f2', "first")
104 test.fail_test(not (start2 < finish1))
105
106 # re-run the test with the same input, fail if we don't
107 # get back the same times, which would indicate that
108 # SCons rebuilt the files even though nothing changed
109 s2, f1 = RunTest('-j 2 f1 f2', "first")
110 test.fail_test(start2 != s2)
111 test.fail_test(finish1 != f1)
112
113 # Test a single serial job.
114 # fail if the second file was started
115 # before the first one was finished
116 start2, finish1 = RunTest('f1 f2', "second")
117 test.fail_test(start2 < finish1)
118
119 # Make sure that a parallel build using a list builder
120 # succeeds.
121 test.run(arguments='-j 2 out')
122
123
124 # Test that we fall back and warn properly if there's no threading.py
125 # module (simulated), which is the case if this version of Python wasn't
126 # built with threading support.
127
128 test.subdir('pythonlib')
129
130 test.write(['pythonlib', 'threading.py'], """\
131 raise ImportError
132 """)
133
134 save_pythonpath = os.environ.get('PYTHONPATH', '')
135 os.environ['PYTHONPATH'] = test.workpath('pythonlib')
136
137 #start2, finish1 = RunTest('-j 2 f1, f2', "fifth")
138
139 test.write('f1.in', 'f1.in pythonlib\n')
140 test.write('f2.in', 'f2.in pythonlib\n')
141
142 test.run(arguments = "-j 2 f1 f2", stderr=None)
143
144 warn = \
145 """scons: warning: parallel builds are unsupported by this version of Python;
146 \tignoring -j or num_jobs option.
147 """
148 test.fail_test(string.find(test.stderr(), warn) == -1)
149
150 str = test.read("f1")
151 start1,finish1 = map(float, string.split(str, "\n"))
152
153 str = test.read("f2")
154 start2,finish2 = map(float, string.split(str, "\n"))
155
156 test.fail_test(start2 < finish1)
157
158 os.environ['PYTHONPATH'] = save_pythonpath
159
160
161 # Test SetJobs() with no -j:
162 test.write('SConstruct', """
163 MyBuild = Builder(action = r'%s build.py $TARGETS')
164 env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
165 env.MyBuild(target = 'f1', source = 'f1.in')
166 env.MyBuild(target = 'f2', source = 'f2.in')
167
168 def copyn(env, target, source):
169     import shutil
170     import time
171     time.sleep(1)
172     for t in target:
173         shutil.copy(str(source[0]), str(t))
174
175 t = env.Command(target=['foo/foo1.out', 'foo/foo2.out'], source='foo/foo.in', action=copyn)
176 env.Install('out', t)
177
178 assert GetOption('num_jobs') == 1
179 SetOption('num_jobs', 2)
180 assert GetOption('num_jobs') == 2
181 """ % python)
182
183 # This should be a parallel build because the SConscript sets jobs to 2.
184 # fail if the second file was not started
185 # before the first one was finished
186 start2, finish1 = RunTest('f1 f2', "third")
187 test.fail_test(not (start2 < finish1))
188
189 # Test SetJobs() with -j:
190 test.write('SConstruct', """
191 MyBuild = Builder(action = r'%s build.py $TARGETS')
192 env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
193 env.MyBuild(target = 'f1', source = 'f1.in')
194 env.MyBuild(target = 'f2', source = 'f2.in')
195
196 def copyn(env, target, source):
197     import shutil
198     import time
199     time.sleep(1)
200     for t in target:
201         shutil.copy(str(source[0]), str(t))
202
203 t = env.Command(target=['foo/foo1.out', 'foo/foo2.out'], source='foo/foo.in', action=copyn)
204 env.Install('out', t)
205
206 assert GetOption('num_jobs') == 1
207 SetOption('num_jobs', 2)
208 assert GetOption('num_jobs') == 1
209 """ % python)
210
211 # This should be a serial build since -j 1 overrides the call to SetJobs().
212 # fail if the second file was started
213 # before the first one was finished
214 start2, finish1 = RunTest('-j 1 f1 f2', "fourth")
215 test.fail_test(start2 < finish1)
216
217
218 # Test that a failed build with -j works properly.
219
220 test.write('mycopy.py', r"""\
221 import sys
222 import time
223 time.sleep(1)
224 open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
225 """)
226
227 test.write('myfail.py', r"""\
228 import sys
229 sys.exit(1)
230 """)
231
232 test.write('SConstruct', """
233 MyCopy = Builder(action = r'%s mycopy.py $TARGET $SOURCE')
234 Fail = Builder(action = r'%s myfail.py $TARGETS $SOURCE')
235 env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail })
236 env.Fail(target = 'f3', source = 'f3.in')
237 env.MyCopy(target = 'f4', source = 'f4.in')
238 env.MyCopy(target = 'f5', source = 'f5.in')
239 env.MyCopy(target = 'f6', source = 'f6.in')
240 """ % (python, python))
241
242 test.write('f3.in', "f3.in\n")
243 test.write('f4.in', "f4.in\n")
244 test.write('f5.in', "f5.in\n")
245 test.write('f6.in', "f6.in\n")
246
247 test.run(arguments = '-j 2 .',
248          status = 2,
249          stderr = "scons: *** [f3] Error 1\n")
250
251 test.fail_test(os.path.exists(test.workpath('f3')))
252 test.fail_test(test.read(test.workpath('f4')) != 'f4.in\n')
253 test.fail_test(os.path.exists(test.workpath('f5'))) 
254 test.fail_test(os.path.exists(test.workpath('f6'))) 
255
256 test.pass_test()