#! /usr/bin/env python ''' This file is a Yabs programme that contains tests for Yabs, and also Yabs targets that build releases of Yabs. Run all tests with: ./make.py test ''' import yabs, yabs2, yabs3 import os, string, re, signal, shutil, time, traceback, sys # import the example-1/make.py script. Targets in this script are built # directly in this programme by the test5 target, and also via separate # invocations of the example-1/make.py programme via os.system() in the # other tests. sys.path.insert( 0, 'example-1') import make yabs.print_exception = yabs.print_exception_compact def add_lineprefix( prefix, text): ''' Returns text with each line prefixed with . ''' ret = '' for line in text.split( '\n'): ret += prefix + line + '\n' return ret def do_clean(): yabs.subprocess( 'rm example-1/_yabs/*', echo=False) def check_same_text( prefix, testname, text1, text2): if text1 == text2: print prefix + ' succeeded - output text unchanged:' print add_lineprefix( prefix + '> ', text1), else: print prefix + ' failed:' print add_lineprefix( prefix + '1> ', text1), print prefix + 'is different from:' print add_lineprefix( prefix + '2> ', text2), file( 'check_same_text.1', 'w').write( text1) file( 'check_same_text.2', 'w').write( text2) os.system( 'diff -u check_same_text.1 check_same_text.2') raise Exception( testname + ' failed') def prefix( target): return ' ' + target + ': ' def test1( target, state): '''' Does a clean build and run of the executable in the example-1 directory. ''' if target!='test1': return def commands( context): rootdir = yabs.get_caller_directory() do_clean() cmd = 'cd ' + os.path.join( rootdir, 'example-1') + ' && '\ + os.path.join( '.', 'make.py foo.exe -dd') text = yabs.subprocess_text( cmd, echo=False) text = yabs.subprocess_text( 'cd ' + os.path.join( rootdir, 'example-1') + ' && ' + os.path.join( '.', yabs3.default_target( 'foo.exe')), echo=False) print add_lineprefix( prefix( target), text), print prefix( target) + 'succeeded' return commands, yabs.add_rule( test1, phony=True, root=yabs.caller) def test2( target, state): ''' Checks that modifying a header forces a rebuild. ''' if target!='test2': return def commands( context): do_clean() yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe', echo=False) print prefix( target) + 'building with main.cpp marked as new...' time.sleep(1) text1 = yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe -W main.cpp', echo=False) print prefix( target) + 'building with main.h marked as new - should give identical text...' time.sleep(1) text2 = yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe -W main.h', echo=False) check_same_text( prefix( target), target, text1, text2) print prefix( target) + 'succeeded' return commands, yabs.add_rule( test2, phony=True, root=yabs.caller) def test3( target, state): ''' Checks that modifying a cmd-file forces a rebuild of all sources. ''' if target!='test3': return def commands( context): do_clean() print prefix( target) + 'building after clean...' text1 = yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe', echo=False) cmdfile = yabs3.default_target( 'example-1/.cpp.o') print yabs.place(), 'cmdfile=', cmdfile assert isinstance( cmdfile, str), 'yabs3.default_target() returned ' + str(cmdfile) assert yabs.mtime_raw( cmdfile)>0 # append some text to the compiler-cmd file. this should force # recompiles of all source files. yabs.subprocess_text( 'echo "extra" >> ' + cmdfile, echo=False) print prefix( target) + 'building after altering command-file...' time.sleep( 1) text2 = yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe', echo=False) check_same_text( prefix( target), target, text1, text2) print prefix( target) + 'succeeded' return commands, yabs.add_rule( test3, phony=True, root=yabs.caller) def test4( target, state): ''' Checks that rebuild does nothing. ''' if target!='test4': return def commands( context): print prefix( target) + 'building...' text1 = yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe', echo=False) print prefix( target) + 'rebuilding...' time.sleep( 1) text = yabs.subprocess_text( 'cd ./example-1 && ./make.py foo.exe -e 21333 -d', echo=False) if text!='': print prefix( target) + 'rebuild did not do nothing. output was:' print add_lineprefix( prefix( target) + '> ', text), raise Exception( target + ' failed') print prefix( target) + 'succeeded' return commands, yabs.add_rule( test4, phony=True, root=yabs.caller) def test5( target, state): ''' This tests Yabs's support for importing build scripts from a different directory, and dealing with the imported rules and targets without having to worry about absolute/relative paths. For example, we can send a target 'example-1/foo.exe' to yabs.make(), and it will be built by the rules in the imported example-1/make.py module. fixme: this is broken when run standalone - rebuilding can return changed. ''' if target!='test5': return def commands( context): target2 = yabs3.default_target( 'example-1/foo.exe') print prefix( target) + 'building ' + target2 result = yabs.make( target2, prefix( target)) print prefix( target) + 'result1 was:', result print prefix( target) + 'rebuilding ' + target2 result = yabs.make( target2, prefix( target)) print prefix( target) + 'result2 was:', result if result!=yabs.unchanged: raise Exception( prefix( target) + 'direct rebuild didn\'t do nothing: ' + target2) print prefix( target) + 'rebuilding with source marked as changed and target cache unchanged: ' + target2 yabs.mtime_marknew( 'example-1/main.cpp') result = yabs.make( target2, prefix( target)) print prefix( target) + 'result3 was:', result if result!=yabs.unchanged: raise Exception( prefix( target) + 'direct rebuild without target-cache-flush didn\'t do nothing: ' + target2) if 0: # this is disabled because clearing state.targetcache breaks top-level target. print prefix( target) + 'rebuilding with source marked as changed and target cache cleared: ' + target2 context.state.targetcache = dict() yabs.mtime_marknew( 'example-1/main.cpp') result = yabs.make( target2, prefix( target)) if result!=yabs.changed: print 'result=', result raise Exception( prefix( target) + 'direct rebuild with changed source and flushed target cache did nothing: ' + target2) print prefix( target) + 'succeeded' return commands, yabs.add_rule( test5, phony=True, root=yabs.caller) def test6( target, state): ''' This tests the patternrule in example-1/make.py which simply touches the file example-1/patterntest. When run from this directory, the patternrule mechanism has to ensure that the target is an absolute pathname when the patternrule's command is generated. ''' if target!='test6': return f = os.path.join( yabs.get_caller_directory(1), 'example-1', 'patterntest') if yabs.mtime( f)>0: os.remove( f) yabs.mtime_flush( f) return 'echo test6 succeeded', [ os.path.join( 'example-1', 'patterntest')], [] yabs.add_rule( test6, phony=True, root=yabs.caller) test7_dir = os.path.join( yabs.get_caller_directory(), 'example-1') if os.name=='nt': yabs2.add_patternrule_phony( 'test7-helper', '', 'cd', root=test7_dir) else: yabs2.add_patternrule_phony( 'test7-helper', '', 'pwd', root=test7_dir) def test7( target, state): ''' Tests that commands are run in ruleroot directory. we run a new yabs build for the special target `test7-helper', which should output the current directory set by add_patternrule_phony's parameter. have to use -s flag, so that the only output is from the pwd command. ''' if target!='test7': return def commands( context): text = yabs.subprocess_text( os.path.join( '.', 'make.py') + ' -e 21233 ' + os.path.join( 'example-1', 'test7-helper')) text = text.strip() print prefix( target) + 'test7-helper target output was: ' + text if text != test7_dir: raise Exception( 'test7 failed - text=', text) print prefix( target) + ' succeeded' return commands, yabs.add_rule( test7, phony=True, root=yabs.caller) yabs2.add_patternrule( 'test8-file', 'foobar.cantmake', '') def test8( target, state): ''' Tests that the last-resort yabs._file_rule() is not used when previous rules have failed due to failed prerequisites. ''' if target!='test8': return def commands( context): yabs.subprocess_text( 'touch test8-file') yabs.subprocess_text( 'touch test8-file2') e = yabs.make( 'test8-file') if e==yabs.changed or e==yabs.unchanged: raise Exception( 'make of test8-file should have failed') print prefix( target) + 'build of test8-file failed, as it should' e = yabs.make( 'test8-file2') if e!=yabs.changed and e!=yabs.unchanged: raise Exception( 'make of test8-file2 should have succeeded') print prefix( target) + 'build of test8-file2 succeeded, as it should' return commands, yabs.add_rule( test8, phony=True, root=yabs.caller) root=yabs.get_caller_directory() def test9_helper( target, state): if target != 'test9-foo': return None return 'touch ' + target, yabs.add_rule( test9_helper, root=yabs.caller, autocmds='.cmdfile') def test9( target, state): ''' test9_helper uses yabs' support for command files. this test checks that modifying the command file forces the target to be rebuilt. ''' if target!='test9': return def commands( context): f = os.path.join( root, 'test9-foo') cmdfile = f + '.cmdfile' yabs.subprocess( 'rm ' + f) yabs.subprocess( 'rm ' + cmdfile) print prefix( target) + 'calling make 1' e = yabs.make( f, prefix( target), prefix( target)) assert e==yabs.changed or e==yabs.unchanged, 'couldn\'t make ' + str(e) print prefix( target) + 'calling make 2' time.sleep(1) del context.state.targetcache[ f] del context.state.targetcache[ cmdfile] e = yabs.make( f, prefix( target)) assert e==yabs.unchanged, 'remake should have done nothing but:' + str(e) print prefix( target) + 'calling make 3' time.sleep(1) del context.state.targetcache[ f] del context.state.targetcache[ cmdfile] yabs.subprocess_text( 'echo newtext >>' + cmdfile) yabs.subprocess_text( 'cat ' + cmdfile, echo=False) e = yabs.make( f, prefix( target)) assert e==yabs.changed, 'remake should have changed ' + f print prefix( target) + ' succeeded' return commands, yabs.add_rule( test9, phony=True, root=yabs.caller) yabs2.add_patternrule( 'intercept.so', 'intercept.c', 'echo building $@\ngcc -W -Wall -c -o intercept.o intercept.c\n' + 'gcc -shared -fpic -o $@ intercept.o' ) def test10( target, prereq, match, state): ''' raw test of LD_PRELOAD. defunct ''' try: os.remove( 'test10.deps') except OSError: pass yabs.subprocess( 'export LD_PRELOAD=`pwd`/intercept.so YABS_AUTODEPS_FILENAME=`pwd`/test10.deps && cd . && ' 'gcc -c -o /dev/null intercept.c && echo $LD_PRELOAD') yabs.subprocess_text( 'cat test10.deps', state=state) yabs2.add_patternrule_phony( 'test10', 'intercept.so', test10) def test11( target, state): ''' This isn't really a test. it simply outputs the contents of the autodeps file created when running gcc. ''' if target!='test11': return None #print prefix( target) + 'returning' return ( '-rm ' + target + '.dep\n' + 'echo LD_PRELOAD is: $LD_PRELOAD\n' + 'echo "#include " > test11.c\n' + 'gcc -c -o /dev/null test11.c\n' + 'cat ' + target + '.dep', [], [ None], ) yabs.add_rule( test11, phony=True, root=yabs.caller, autodeps='.dep') def test12( target, state): ''' checks yabs.subprocess_text returns error/noerror as appropriate. ''' if target!='test12': return def commands( context): n = 'ksjndv' if os.name=='nt': t = 'echo' f = 'cl asdasdasdasdasd' else: t = 'true' f = 'false' print prefix( target) + 'running: ' + f e, text = yabs.subprocess( f, echo=True) assert e!=0, 'comand ' + f + ' should have returned error: ' + text print prefix( target) + 'running: ' + n e, text = yabs.subprocess( n, echo=True) assert e!=0, 'comand ' + f + ' should have returned error: ' + text print prefix( target) + 'running: ' + t e, text = yabs.subprocess( t, echo=True) assert e==0, 'comand ' + f + ' should not have returned error: ' + str(e) + ', ' + text print prefix( target) + 'succeeded' return commands, yabs.add_rule( test12, phony=True, root=yabs.caller) def simple_compile_rule( target, state): if not target.endswith( '.cpp.o'): return None src = target[ :-2] cmd = 'g++ -c -W -Wall -o ' + target + ' ' + src return cmd, [ src] yabs.add_rule( simple_compile_rule, autocmds='.cmds', autodeps='.deps') def simple_addexe( exe, src, root=None): ''' Simple rule for compiling and linking executables. uses autocmds and autodeps to ensure that things are rebuilt is flags are changed or headers are modified. yabs3 takes this technique a lot further. ''' if root==None: root=yabs.get_caller_directory() def rule( target, state): #print 'simple_addexe', exe, target if target!=exe: return objs = [] for s in src: objs.append( s+'.o') return 'g++ -W -Wall -o ' + target + ' ' + string.join( objs, ' '), objs yabs.add_rule( rule, autocmds='.cmds', autodeps='.deps', root=root) simple_addexe( 'bar.exe', [ 'main.cpp'], root=os.path.join( yabs.get_caller_directory(), 'example-1')) def test13( target, state): ''' check that autodeps works, using executable built with simple_addexe(). we run compile/link, then modify a .h file and check that this forces a rebuild. ''' if target!='test13': return def commands( context): main = os.path.join( root, 'example-1', 'main.cpp') exe = os.path.join( root, 'example-1', 'bar.exe') header = os.path.join( root, 'example-1', 'main.h') # We use a temporary yabs.State, so that we can mess around with # the caches without destroying .state: s = yabs.State() s.rules = context.state.rules[:] yabs3.setup( s) def clear_s(): s.mtimecache = yabs._MtimeCache() s.rulecache = {} s.targetcache = {} old_state = yabs.default_state yabs.default_state = None # force clean build initially by marking main.cpp as new: print prefix( target) + 'making', exe yabs.mtime_marknew( main, mtimecache=s.mtimecache) e = yabs.make( exe, state=s) assert e==yabs.changed or e==yabs.unchanged print prefix( target) + 'remaking - should do nothing' time.sleep( 1) clear_s() e = yabs.make( exe, state=s, echo_prefix='') assert e==yabs.unchanged, e print prefix( target) + 'remaking with header marked as new' time.sleep( 1) clear_s() yabs.mtime_marknew( header, mtimecache=s.mtimecache) e = yabs.make( exe, state=s) assert e==yabs.changed, repr( e) print prefix( target) + 'succeeded' yabs.default_state = old_state return commands, yabs.add_rule( test13, root=root, phony=True) def test14_helper( target, state): if target!='test14.o': return None return ( 'echo test14-recompiling && gcc -Wall -W -I include-0 -I include-1 -c -o test14.o test14.c', 'test14.c', ) yabs.add_rule( test14_helper, root=root, autodeps='.deps') def test14( target, state): ''' test that autodeps works with multiple include-paths when a header file is created that previously failed to open. note that this test fails if we don't create the empty include-0 directory before the first build - unfortunately gcc doesn't attempt to open the header in a non-existant directory. Also, this test only works on openbsd, but not on linux - linux doesn't seem to support $LD_PRELOAD to the extent that openbsd does. ''' if target!='test14': return None if os.uname()[0]=='Linux': return 'echo test14 ignored on Linux', def fn( context): cmd = ''' -rm -r include-0 2>/dev/null -rm -r include-1 2>/dev/null mkdir include-0 mkdir include-1 touch include-1/foo.h echo '#include "foo.h"' > test14.c echo 'int main( void) { return 0; }' >> test14.c ./make.py -e 31233 test14.o touch include-0/foo.h ./make.py -e 31233 test14.o ''' e, text = yabs.command_run( cmd) correct_text = 'test14-recompiling\n' if e!=0 or text.count( correct_text)!=2: raise Exception( 'test14 failed - expecting second build to recompile' + ', e=' + str(e) + ', but output text was wrong:\n\n' + repr( text) + '\nexpected two occurrencies of: \n' + repr(correct_text)) return fn, yabs.add_rule( test14, root=root,phony=True) yabs2.add_patternrule_phony( 'test15', '', 'echo hello && echo world 1>&2 && false') # unused - always fails. def test16( target, state): ''' Unused ''' if target!='test16': return None def cmd( state): a = yabs.subprocess_text( 'echo hello 2>&1', echo=False) b = yabs.subprocess_text( 'echo world 1>&2', echo=False) print prefix( target) + 'a=', a print prefix( target) + 'b=', b return cmd, yabs.add_rule( test16, root=root, phony=True) # checks that we can build _yabs/test17.exe, which will be a copy of # _yabs/test17,.exe. this test requires that yabs3._singlebuild # be registered. # unused yabs2.add_patternrule( 'test17.c', '', 'printf \'#include \\n' 'int main(void){\\n' ' printf("hello world\\\\n");\\n' ' return 0;\\n' '}\\n\' >$@') yabs3.add_exe( 'test17', 'test17.c') yabs2.add_patternrule_phony( 'test17', yabs3.default_target( 'test17.exe') + ' _yabs/test17.exe', '') def test18( target, state): ''' check that we can build test17,default.exe. unused - removed support for default 2008 feb 15. ''' if target!='test18': return def commands( context): yabs.subprocess( 'rm _yabs/test17*', echo=False) e, text = yabs.subprocess( './make.py test17,default.exe', echo=False) assert e==0, 'failed to build test17,default.exe: ' + text return commands, yabs.add_rule( test18, root=yabs.caller, phony=True) # test19: check yabs.fn() works. yabs.add_rule( yabs.fn( ''' def ( target, state): if target!='test19': return None return 'touch ' + target, '''), root=yabs.caller) yabs2.add_patternrule( 'test20b', 'test20c test20d test20e', 'echo test20bcommand') yabs2.add_patternrule( 'test20c', '', 'touch $@') yabs2.add_patternrule( 'test20e', '', 'asdasdasda') def test20( target, state): ''' test that -k option works. ''' if target!='test20': return def commands( context): yabs.subprocess( 'rm test20b test20c test20e', echo=False) e, text = yabs.subprocess( './make.py -ks test20b test20d', echo=False) assert e assert re.search( 'target failed:.*_yabs_multiple_targets.*test20b.*test20d.*test20e', text, re.DOTALL) assert not os.path.isfile( 'test20b') assert os.path.isfile( 'test20c') assert not os.path.isfile( 'test20e') print prefix( target) + ' succeeded' return commands, yabs.add_rule( test20, root=yabs.caller, phony=True) def test21( target, state): ''' check yabs._uplocals,_upglobals work as expected. ''' if target!='test21': return assert locals() == yabs._uplocals(0) assert globals() == yabs._upglobals(0) return '', yabs.add_rule( test21, root=yabs.caller, phony=True) def test22( target, state): ''' not really a test. used to invesigate namespace of fns created using yabs.fn() - not quite the same as namespace of fns created conventionally using def. ''' if target!='test22': return x = 45 f = yabs.fn( ''' def ( context): print prefix( 'test22') + 'f():', test22 if 0: print 'test22.f(): x=', x # unfortunately fails return '' ''') def g( state): print 'test22.g(): x=', x # ok return f, yabs.add_rule( test22, root=yabs.caller, phony=True) def test23( target, state): if target=='test23a' or target=='test23b': return 'sleep 2', yabs.add_rule( test23, root=yabs.caller, phony=True) def test24( target, state): ''' check that -j 2 builds test23a and test23b much faster. ''' if target!='test24': return def commands( context): print prefix( target) + 'single-threaded build.' t0 = time.time() e, text = yabs.subprocess( './make.py test23a test23b', echo=False) t1 = time.time() assert e==0 t_serial = t1-t0 print prefix( target) + 't_serial=', t_serial print prefix( target) + 'multi-threaded build.' e, text = yabs.subprocess( './make.py test23a test23b -j 2', echo=False) t2 = time.time() assert e==0 t_mt = t2-t1 print prefix( target) + 't_mt=', t_mt assert t_mt < 0.7*t_serial return commands, yabs.add_rule( test24, root=yabs.caller, phony=True) def test25_( target, state): if target=='/test25_': return '', 'foo' yabs.add_rule( test25_) def test25( target, state): ''' check that yabs complains when a rule without a root dir returns a non-absolute prerequisite. ''' if target!='test25': return def commands( context): e, text = yabs.subprocess( './make.py /test25_', echo=False) assert 'warning: rule returned a relative prerequisite' in text assert e print prefix( target) + 'succeeded' return commands, yabs.add_rule( test25, root=yabs.caller, phony=True) def test26_( target, state): if target=='test26_': foo = test26_bar # deliberate error. yabs.add_rule( test26_, root=yabs.caller, phony=True) def test26( target, state): if target!='test26': return def commands( context): print prefix( target) + 'running test26_' e, text = yabs.subprocess( './make.py test26_', echo=False) assert e assert text.count( '*** rule failed')==1, 'text is:\n' + text print prefix( target) + 'running test26_ with -s' e, text = yabs.subprocess( './make.py test26_ -s', echo=False) assert text.count( '*** rule failed')==1, 'text is:\n' + text assert e, 'text is: ' + repr(text) print prefix( target) + 'succeeded' return commands, yabs.add_rule( test26, root=yabs.caller, phony=True) def test27_foo( target, state): ''' deliberately incorrect rule - generates the wrong file. ''' if target!='test27_foo': return return 'touch test27_bar', yabs.add_rule( test27_foo, root=yabs.caller) def test27( target, state): ''' check that we get an error when rule is incorrect. ''' if target != 'test27': return def commands( context): e, text = yabs.subprocess( './make.py test27_foo', echo=False) assert e, 'output was: ' + repr(text) print prefix( target) + 'succeeded' return commands, yabs.add_rule( test27, root=yabs.caller, phony=True) def test28a( target, state): ''' sleeps for number of seconds specified after `test28.'. ''' if target.startswith( 'test28.'): t = target[7:] c = '' return 'sleep ' + t + '; echo ' + t + ' finished', yabs.add_rule( test28a, root=yabs.caller, phony=True) def test28( target, state): ''' This checks that concurrent builds don't get blocked in nested prerequisites. Prior to 2008 Mar 21, Yabs would block in nested prerequisites, which would prevent full use of the available concurrency. We schedule the following targets, using -j 2, so that only two targets are ever built at a time: test28 test28d test28a test28.7 test28b test28.2 test28c test28.1 If things go right, the targets will be built in the order test28.2, test28.1, test28.7. Prior to 2008 Mar 21, the building of test28a would block waiting for test28.7 to complete before allowing test28.1 to be scheduled. ''' if target == 'test28a': return '', 'test28.7', if target == 'test28b': return '', 'test28.2', if target == 'test28c': return '', 'test28.1', if target == 'test28d': return '', [ 'test28a', 'test28b', ], if target == 'test28e': return '', [ 'test28d', 'test28c', ], if target == 'test28': def commands( context): t0 = time.time() e, text = yabs.subprocess( './make.py test28e -j 2 -s', echo=True) assert e==0 a = text.find( '7 finished') b = text.find( '2 finished') c = text.find( '1 finished') print 'a,b,c=', a,b,c assert b < c and c < a print prefix( target) + 'succeeded' return commands, yabs.add_rule( test28, root=yabs.caller, phony=True) def test29( target, state): if target=='test29.fail': return 'false', if target=='test29.succeed': return 'true', if target!='test29': return def commands( context): e, text = yabs.subprocess( './make.py test29.fail test29.succeed', echo=True) assert 'test29.succeed' not in text print >>context.out, yabs.place(), 'test29 succeeded' return commands, yabs.add_rule( test29, root=yabs.caller, phony=True) def test30( target, state): if target!='test30': return def commands( context): t = time.time() e, text = yabs._subprocess_pty( 'sleep 10', endtime=t+2) t = time.time()-t print yabs.place(), 't=', t, 'e=', e assert os.WIFSIGNALED(e) assert os.WTERMSIG(e)==signal.SIGTERM # timeout. assert t >=1.9 and t<3 return commands, yabs.add_rule( test30, root=yabs.caller, phony=True) def test31( target, state): ''' test resource timeouts ''' class resource: def __init__( self, t): self.t = t def tryacquire( self): #print yabs.place(), 'self.t=', self.t, 'time.time()=', time.time() if time.time() > self.t: return True return self.t def release( self): pass if target=='test31.a': return 'date; touch ' + target, [], [], None, 0, resource( time.time()+4), elif target=='test31.b': return 'date; touch ' + target, [], [], None, 0, resource( time.time()+2), elif target=='test31': def commands( context): t = time.time() e, text = yabs.subprocess( './make.py test31.a test31.b -j 3', echo=True) assert e==0 a = os.stat( 'test31.a').st_mtime b = os.stat( 'test31.b').st_mtime assert a>t and b>t assert b - t > 1 assert a - b > 0.5 return commands, else: return yabs.add_rule( test31, root=yabs.caller, phony=True) def test32( target, state): ''' Check that we can build a single target in multi-thread mode. See hack in yabs2.appmake2(). ''' if target=='test32.a' or target=='test32.b': return 'echo ' + target, elif target=='test32': def commands( context): t = time.time() e, text = yabs.subprocess( './make.py test32.a -j 2', echo=True) assert e==0 e, text = yabs.subprocess( './make.py test32.a test32.b -j 2', echo=True) assert e==0 return commands, yabs.add_rule( test32, root=yabs.caller, phony=True) import threading def test33( target, state): ''' Checks that concurrent yabs doesn't interleave the output lines from different targets. ''' linetext = 'foo: 01234567890123456789012345678901234567890123456789' if target=='test33.a': # for this target, simply print out lots of identical lines. def fn( context): for i in range(500): #print 'foo: ' + os.getpid(), i, ( str(time.time()) + ' ') * 2 print linetext time.sleep(0.001) return fn, elif re.match( 'test33\.[0-9]+', target): # For any target of the form test33., run a subprocess that prints # out lots of lines using test33.a: return './make.py test33.a', elif target=='test33': # Run concurrent yabs with many test33. targets, and check that # there is no interleaving of output lines. def fn( context): text = './make.py -j 4' for i in range(5): text += ' test33.' + str(i) e, text = yabs.subprocess( text, echo=False) assert e==0 i = 0 bad = 0 for l in text.split('\n'): if l.startswith( 'foo: '): if l != linetext: print 'line ' + str(i) + ' is bad: ' + l bad += 1 i += 1 assert bad == 0 #n = text.count( '\n\n') #assert n==0, 'found ' + str(n) + ' blank lines' return fn, yabs.add_rule( test33, root=yabs.caller, phony=True) def test34( target, state): ''' check we pick up both stdout and stderr in subprocess(). ''' if target=='test34.a': def commands( context): sys.stdout.write( 'this is to stdout\n') sys.stderr.write( 'AND THIS IS TO stderr\n') return commands, if target=='test34': def commands( context): e, text = yabs._subprocess_popen4( './make.py test34.a', echo=True, prefix='test34: ') assert ( 'this is to stdout\n' in text and 'AND THIS IS TO stderr\n' in text), repr( text) return commands, if target=='test34.pty': def commands( context): e, text = yabs._subprocess_pty( './make.py test34.a', echo=True, prefix='test34: ') print yabs.place(), repr(text) assert ( 'this is to stdout\r\n' in text and 'AND THIS IS TO stderr\r\n' in text), repr( text) return commands, yabs.add_rule( test34, root=yabs.caller, phony=True) def test35( target, state): ''' test that gdb works with popen2.Popen4. not terribly meaningful - gdb doesn't interact with the user correctly in more detailed tests, e.g. it doesn't offer `y or n' options to the user. need proper terminal stuff for that. ''' if target=='test35': import popen2 def commands( context): child = popen2.Popen4( 'gdb') def getprompt(): #print yabs.place(), 'looking for prompt' text = '' while 1: t = child.fromchild.read(1) #sys.stdout.write( repr(t)+'\n') sys.stdout.write( t) sys.stdout.flush() text += t if text.endswith( '\n(gdb) '): #print yabs.place(), 'text ends with prompt' break getprompt() print yabs.place(), 'entering help' child.tochild.write( 'help\n') child.tochild.flush() getprompt() child.tochild.write( 'q\n') child.tochild.flush() r = child.wait() assert r==0 #print yabs.place(), 'r=', r return commands, yabs.add_rule( test35, root=yabs.caller, phony=True) def test36( target, state): if target=='test36': def commands( context): t = time.time() e, text = yabs.subprocess( 'sleep 10', endtime=time.time()+5) print yabs.place(), e, text assert e==-1 # timeout t = time.time() - t print 't=', t assert t >= 4.9 and t < 7 e, text = yabs._subprocess_popen4( 'true', endtime=time.time()+5) print yabs.place(), e, text assert os.WIFEXITED(e) and os.WEXITSTATUS(e)==0 e, text = yabs._subprocess_popen4( 'false', endtime=time.time()+5) assert os.WIFEXITED(e) and os.WEXITSTATUS(e)==1 print yabs.place(), e, text return commands, yabs.add_rule( test36, root=yabs.caller, phony=True) def test37( target, state): ''' Check that `always' rules work. ''' if target=='test37.0': return 'echo > test37.a', if target=='test37.a': return 'touch test37.a; echo hello >> test37.a', if target=='test37.b': return '', [ 'test37.a'], if target=='test37.c': return '', [ 'test37.a'], if target=='test37.d': return '', [ 'test37.0', 'test37.b', 'test37.c'], if target=='test37': def commands( context): text = file( 'test37.a').read() print text assert text=='\n' + 2 * 'hello\n' return commands, [ 'test37.d'], yabs.add_rule( test37, root=yabs.caller, phony=True, always=True) def test38( target, state): if target=='test38.1': return '', 'test38.2', if target=='test38.2': assert 0 elif target=='test38': def commands( context): e, text = yabs._subprocess_pty( './make.py -ks -j 4 test38.2', echo=True, prefix='test38: ', endtime=time.time()+5) assert not os.WIFSIGNALED( e) print yabs.place(), 'test38 passed' return commands, yabs.add_rule( test38, root=yabs.caller, phony=True, always=True) def test39( target, state): if target=='test39': def commands( context): yabs3._transform_test() return commands, yabs.add_rule( test39, root=yabs.caller, phony=True, always=True) # a rule that deliberately generates an error. useful for # manually testing the error messages when -s and -e flags # are used. yabs2.add_patternrule_phony( 'error', '', 'g++ --- ij') yabs2.add_patternrule_phony( 'error2', 'error', 'echo ok') yabs2.add_patternrule_phony( 'hang', '', 'sleep 1000') #yabs2.add_patternrule_phony( 'segv', yabs3.default_target( 'segv.c.exe'), '$<') yabs2.add_patternrule( 'error3', '', 'touch error4', phony=True, always=True) yabs2.add_patternrule( 'error4', '', 'touch -t 200001010101 error4', always=True) if os.name=='nt': # some targets don't work on windows - use gcc etc. yabs2.add_patternrule_phony( 'test', 'test6 test7 test12', '') else: tests = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39 ] if os.uname()[0]=='Linux': tests.remove( 14) # following rule allows targets like `test3-6' - will depend on all # tests between 3 and 6 (both inclusive) that are in the global list # `tests'. Also, can do 'test-5', 'test6-' and 'test'. testrange_fn_regex = re.compile( '^tests(?P[0-9]*)-(?P[0-9]*)$') def testrange_fn( target, state): if target=='tests': begin = 0 end = None else: match = testrange_fn_regex.match( target) #print 'testrange_fn_regex: match=', match if not match: return None begin = match.group('begin') if begin=='': begin = 0 else: begin = int( begin) end = match.group('end') if end=='': end = 99999 else: end = int( end) #print 'testrange_fn: ', begin, end prereq = [] for i in tests: if i>=begin and ( end is None or i<=end): prereq.append( 'test' + str( i)) return 'echo succeeded: ' + target, prereq yabs.add_rule( testrange_fn, phony=True, root=yabs.caller) yabs2.add_patternrule_phony( 'run-%.exe', '%1.exe', '%1.exe') yabs2.add_patternrule( 'tags', '', 'ctags --c-kinds=+px *.c *.h *.S *.py', autocmds='.cmds') # things below here are for making releases of Yabs. version = '22' version_string = version release_file = 'yabs-' + version + '.tar.gz' website_dir = 'yabs-' + version + '-website' def processreadme( target, state): ''' insert correct version numbers in readme.html, and check version number at end of history.html. ''' if target != 'checkdocs': return None def process( state): date = time.strftime( '%Y %B %d') f = open( 'readme.html', 'r') out = '' for line in f.readlines(): line = re.sub( '^

Version: .*

$', '

Version: ' + version_string + '.

', line) line = re.sub( '^

Date: .*

$', '

Date: ' + date + '.

', line) line = re.sub( 'yabs-[0-9]*.tar.gz', lambda t: release_file, line) out += line f.close() # replace the options section with the actual output from # yabs2's -h: p = out.find( '
Yabs options are:\n')
        assert p>=0
        end = '
' q = out.find( end, p) assert q>=0 helptext = os.popen( './make.py -h').read() helptext = helptext.replace( '<', '<') helptext = helptext.replace( '>', '>') out = out[:p] + '
' + helptext + out[q:]
        
        f = open( 'readme.html-', 'w')
        f.write( out)
        f.close()
        os.system( 'diff readme.html readme.html-')
        shutil.copyfile( 'readme.html-', 'readme.html')
        os.unlink( 'readme.html-')
        
        f = open( 'history.html')
        h = f.read()
        end_text =(
            '\n'
            '

' + date + ': version ' + version + '.

\n' '
\n' '\n' '\n' '\n' '\n' ) if not h.endswith( end_text): print 'History file doesn\'t end with correct text: ' print '[' + end_text + ']' print 'history file ends with:' print '[' + h[-len(end_text):] + ']' raise Exception( 'History file doesn\'t end with correct text') return '' return process, yabs.add_rule( processreadme, True, yabs.get_caller_directory(1)) filelist = ''' __init__.py readme.html history.html yabs.py yabs2.py yabs3.py yabserrors.py yabs.html yabs2.html yabs3.html yabserrors.html make.py autodeps.c example-1/make.py example-1/foo.cpp example-1/main.cpp example-1/main.h ''' filelist = string.replace( filelist, '\n', ' ') yabs2.add_patternrule( release_file, 'always ' + filelist, '-~/htmlcontents/_yabs/foo\,debug\,gcc2.95.3\,os\=OpenBSD\,cpu\=i386\,osv\=3.6.exe -s readme.html\n' '-rm -r yabs-' + str( version) + '\n' 'mkdir -p yabs-' + str( version) + '/example-1\n' 'for i in ' + filelist + '; do cp -p $i yabs-' + str( version) + '/$i; done\n' 'tar -czf $@ yabs-' + str( version) + '\n' '-rm -r yabs-' + str( version) + '\n' ) yabs2.add_patternrule( website_dir, 'always checkdocs ' + release_file, '-rm -r $@\n' 'tar -xzf ' + release_file + '\n' 'mv yabs-' + str( version) + ' ' + website_dir + '\n' 'cp -p ' + release_file + ' ' + website_dir + '\n' ) pydoc = 'pydoc' yabs2.add_patternrule( 'yabs%.html', 'yabs%1.py', pydoc + ' -w yabs%1') yabs2.add_patternrule( 'make.html', 'make.py', pydoc + ' -w make') # we are careful to make this rule not apply to readme.html, otherwise # it would be attempted and fail because there is no file (or rule that # can generate) readme.py, and this would give a warning from yabs if # debugging is on. yabs2.add_patternrule_phony( 'release', release_file, '') yabs2.add_patternrule_phony( 'website', website_dir, '') yabs2.add_patternrule_phony( 'always', '', '') if 1: #yabs2.add_rules( '^test.*$', phony=True) #yabs2.appmakeexit() yabs2.appmakeexit( default_params='-d') else: import hotshot, hotshot.stats prof = hotshot.Profile( 'make.prof') e = prof.runcall( yabs2.appmake) stats = hotshot.stats.load( 'make.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats() sys.exit( e)