From: stevenknight Date: Fri, 21 Sep 2001 11:39:19 +0000 (+0000) Subject: rebuild in response to changed .c file X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=b29574231166f19fdaf137f68c32443f4405d204;p=scons.git rebuild in response to changed .c file git-svn-id: http://scons.tigris.org/svn/scons/trunk@65 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 53e4bcc5..0d1d2745 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -10,6 +10,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import SCons.Node.FS +import string import types @@ -41,7 +42,21 @@ class Builder: node = self.node_factory(target) node.builder_set(self) node.env_set(self) - node.sources = source # XXX REACHING INTO ANOTHER OBJECT + + # XXX REACHING INTO ANOTHER OBJECT (this is only temporary): + assert type(source) is type("") + node.sources = source + node.derived = 1 + sources = string.split(source, " ") + sources = filter(lambda x: x, sources) + source_nodes = [] + for source in sources: + source_node = self.node_factory(source) + source_node.derived = 0 + source_node.source_nodes = [] + source_nodes.append(source_node) + node.source_nodes = source_nodes + return node def execute(self, **kw): diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 9cd0607d..fc7b042c 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -257,6 +257,14 @@ class File(Node): def root(self): return self.parent.root() + def get_contents(self): + return open(self.path, "r").read() + + def get_timestamp(self): + return os.path.getmtime(self.path) + + def exists(self): + return os.path.exists(self.path) default_fs = FS() diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 6da15b6a..8e5aab1a 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -20,3 +20,13 @@ class Node: def env_set(self, env): self.env = env + + def has_signature(self): + return hasattr(self, "signature") + + def set_signature(self, signature): + self.signature = signature + + def get_signature(self): + return self.signature + diff --git a/src/engine/SCons/Sig/MD5.py b/src/engine/SCons/Sig/MD5.py index 10927e0c..4c94b1ac 100644 --- a/src/engine/SCons/Sig/MD5.py +++ b/src/engine/SCons/Sig/MD5.py @@ -10,7 +10,11 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import md5 import string - +def current(obj, sig): + """Return whether a given object is up-to-date with the + specified signature. + """ + return obj.get_signature() == sig def hexdigest(s): """Return a signature as a string of hex characters. @@ -25,46 +29,28 @@ def hexdigest(s): r = r + h[(i >> 4) & 0xF] + h[i & 0xF] return r - - -def _init(): - pass # XXX - -def _end(): - pass # XXX - -def current(obj, sig): - """Return whether a given object is up-to-date with the - specified signature. +def collect(signatures): """ - return obj.signature() == sig - -def set(): - pass # XXX - -def invalidate(): - pass # XXX + Collect a list of signatures into an aggregate signature. -def collect(*objects): - """Collect signatures from a list of objects, returning the - aggregate signature of the list. + signatures - a list of signatures + returns - the aggregate signature """ - if len(objects) == 1: - sig = objects[0].signature() + if len(signatures) == 1: + return signatures[0] else: - contents = string.join(map(lambda o: o.signature(), objects), ', ') - sig = signature(contents) -# if debug: -# pass - return sig + contents = string.join(signatures, ', ') + return hexdigest(md5.new(contents).digest()) -def signature(contents): - """Generate a signature for a byte string. +def signature(obj): + """Generate a signature for an object """ - return hexdigest(md5.new(contents).digest()) + return hexdigest(md5.new(obj.get_contents()).digest()) -def cmdsig(): - pass # XXX +def to_string(signature): + """Convert a signature to a string""" + return signature -def srcsig(): - pass # XXX +def from_string(string): + """Convert a string to a signature""" + return string diff --git a/src/engine/SCons/Sig/MD5Tests.py b/src/engine/SCons/Sig/MD5Tests.py index 63134753..6a872117 100644 --- a/src/engine/SCons/Sig/MD5Tests.py +++ b/src/engine/SCons/Sig/MD5Tests.py @@ -3,7 +3,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import SCons.Sig.MD5 +from SCons.Sig.MD5 import current, collect, signature, to_string, from_string + @@ -14,61 +15,48 @@ class my_obj: def __init__(self, value = ""): self.value = value - self.sig = None - - def signature(self): - if not self.sig: - self.sig = SCons.Sig.MD5.signature(self.value) + + def get_signature(self): + if not hasattr(self, "sig"): + self.sig = signature(self) return self.sig - def current(self, sig): - return SCons.Sig.MD5.current(self, sig) + def get_contents(self): + return self.value class MD5TestCase(unittest.TestCase): - def test__init(self): - pass # XXX - - def test__end(self): - pass # XXX - def test_current(self): """Test deciding if an object is up-to-date Simple comparison of different "signature" values. """ - o111 = my_obj(value = '111') - assert not o111.current(SCons.Sig.MD5.signature('110')) - assert o111.current(SCons.Sig.MD5.signature('111')) - assert not o111.current(SCons.Sig.MD5.signature('112')) - - def test_set(self): - pass # XXX - - def test_invalidate(self): - pass # XXX + obj = my_obj('111') + assert not current(obj, signature(my_obj('110'))) + assert current(obj, signature(my_obj('111'))) + assert not current(obj, signature(my_obj('112'))) def test_collect(self): """Test collecting a list of signatures into a new signature value """ - o1 = my_obj(value = '111') - o2 = my_obj(value = '222') - o3 = my_obj(value = '333') - assert '698d51a19d8a121ce581499d7b701668' == SCons.Sig.MD5.collect(o1) - assert '8980c988edc2c78cc43ccb718c06efd5' == SCons.Sig.MD5.collect(o1, o2) - assert '53fd88c84ff8a285eb6e0a687e55b8c7' == SCons.Sig.MD5.collect(o1, o2, o3) + s = map(signature, map(my_obj, ('111', '222', '333'))) + + assert '698d51a19d8a121ce581499d7b701668' == collect(s[0:1]) + assert '8980c988edc2c78cc43ccb718c06efd5' == collect(s[0:2]) + assert '53fd88c84ff8a285eb6e0a687e55b8c7' == collect(s) def test_signature(self): - pass # XXX - - def test_cmdsig(self): - pass # XXX + """Test generating a signature""" + o1 = my_obj(value = '111') + assert '698d51a19d8a121ce581499d7b701668' == signature(o1) - def test_srcsig(self): - pass # XXX + def test_to_string(self): + assert '698d51a19d8a121ce581499d7b701668' == to_string('698d51a19d8a121ce581499d7b701668') + def test_from_string(self): + assert '698d51a19d8a121ce581499d7b701668' == from_string('698d51a19d8a121ce581499d7b701668') if __name__ == "__main__": suite = unittest.makeSuite(MD5TestCase, 'test_') diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py new file mode 100644 index 00000000..7fae807d --- /dev/null +++ b/src/engine/SCons/Sig/SigTests.py @@ -0,0 +1,204 @@ +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import unittest +import TestCmd +import SCons.Sig +import SCons.Sig.MD5 +import SCons.Sig.TimeStamp +import sys + + +class DummyFile: + """A class that simulates a file for testing purposes""" + def __init__(self, path, contents, timestamp, derived): + self.path = path + self.contents = contents + self.timestamp = timestamp + self.derived = derived + + def modify(self, contents, timestamp): + self.contents = contents + self.timestamp = timestamp + +class DummyNode: + """A node workalike for testing purposes""" + + def __init__(self, file): + self.file = file + self.path = file.path + self.derived = file.derived + + def get_contents(self): + # a file that doesn't exist has no contents: + assert self.exists() + + return self.file.contents + + def get_timestamp(self): + # a file that doesn't exist has no timestamp: + assert self.exists() + + return self.file.timestamp + + def exists(self): + return not self.file.contents is None + + def has_signature(self): + return hasattr(self, "sig") + + def set_signature(self, sig): + self.sig = sig + + def get_signature(self): + return self.sig + + +def create_files(test): + args = [(test.workpath('f1.c'), 'blah blah', 111, 0), #0 + (test.workpath('f1'), None, 0, 1), #1 + (test.workpath('d1/f1.c'), 'blah blah', 111, 0), #2 + (test.workpath('d1/f1.h'), 'blah blah', 111, 0), #3 + (test.workpath('d1/f2.c'), 'blah blah', 111, 0), #4 + (test.workpath('d1/f3.h'), 'blah blah', 111, 0), #5 + (test.workpath('d1/f4.h'), 'blah blah', 111, 0), #6 + (test.workpath('d1/f1'), None, 0, 1), #7 + (test.workpath('d1/test.c'), 'blah blah', 111, 0),#8 + (test.workpath('d1/test.o'), None, 0, 1), #9 + (test.workpath('d1/test'), None, 0, 1)] #10 + + files = map(lambda x: apply(DummyFile, x), args) + + return files + +def create_nodes(files): + nodes = map(DummyNode, files) + + nodes[0].source_nodes = [] + nodes[1].source_nodes = [nodes[0]] + nodes[2].source_nodes = [nodes[3]] + nodes[3].source_nodes = [] + nodes[4].source_nodes = [nodes[5]] + nodes[5].source_nodes = [nodes[6]] + nodes[6].source_nodes = [nodes[5]] + nodes[7].source_nodes = [nodes[2], nodes[4]] + nodes[8].source_nodes = [] + nodes[9].source_nodes = [nodes[8]] + nodes[10].source_nodes = [nodes[9]] + + return nodes + + +class SigTestBase: + + def runTest(self): + + test = TestCmd.TestCmd(workdir = '') + test.subdir('d1') + + self.files = create_files(test) + self.test_initial() + self.test_built() + self.test_modify() + self.test_delete() + + def test_initial(self): + + nodes = create_nodes(self.files) + calc = SCons.Sig.Calculator(self.module) + + for node in nodes: + self.failUnless(not calc.current(node), "none of the nodes should be current") + + # simulate a build: + self.files[1].modify('built', 222) + self.files[7].modify('built', 222) + self.files[9].modify('built', 222) + self.files[10].modify('built', 222) + + calc.write(nodes) + + def test_built(self): + + nodes = create_nodes(self.files) + + calc = SCons.Sig.Calculator(self.module) + + for node in nodes: + self.failUnless(calc.current(node), "all of the nodes should be current") + + calc.write(nodes) + + def test_modify(self): + + nodes = create_nodes(self.files) + + #simulate a modification of some files + self.files[0].modify('blah blah blah', 333) + self.files[3].modify('blah blah blah', 333) + self.files[6].modify('blah blah blah', 333) + self.files[8].modify('blah blah blah', 333) + + calc = SCons.Sig.Calculator(self.module) + + self.failUnless(not calc.current(nodes[0]), "modified directly") + self.failUnless(not calc.current(nodes[1]), "direct source modified") + self.failUnless(calc.current(nodes[2])) + self.failUnless(not calc.current(nodes[3]), "modified directly") + self.failUnless(calc.current(nodes[4])) + self.failUnless(calc.current(nodes[5])) + self.failUnless(not calc.current(nodes[6]), "modified directly") + self.failUnless(not calc.current(nodes[7]), "indirect source modified") + self.failUnless(not calc.current(nodes[8]), "modified directory") + self.failUnless(not calc.current(nodes[9]), "direct source modified") + self.failUnless(not calc.current(nodes[10]), "indirect source modified") + + calc.write(nodes) + + def test_delete(self): + + nodes = create_nodes(self.files) + + #simulate the deletion of some files + self.files[1].modify(None, 0) + self.files[7].modify(None, 0) + self.files[9].modify(None, 0) + + calc = SCons.Sig.Calculator(self.module) + + self.failUnless(calc.current(nodes[0])) + self.failUnless(not calc.current(nodes[1]), "deleted") + self.failUnless(calc.current(nodes[2])) + self.failUnless(calc.current(nodes[3])) + self.failUnless(calc.current(nodes[4])) + self.failUnless(calc.current(nodes[5])) + self.failUnless(calc.current(nodes[6])) + self.failUnless(not calc.current(nodes[7]), "deleted") + self.failUnless(calc.current(nodes[8])) + self.failUnless(not calc.current(nodes[9]), "deleted") + self.failUnless(calc.current(nodes[10]), + "current even though it's source was deleted") + + calc.write(nodes) + +class MD5TestCase(unittest.TestCase, SigTestBase): + """Test MD5 signatures""" + + module = SCons.Sig.MD5 + +class TimeStampTestCase(unittest.TestCase, SigTestBase): + """Test timestamp signatures""" + + module = SCons.Sig.TimeStamp + +def suite(): + suite = unittest.TestSuite() + suite.addTest(MD5TestCase()) + suite.addTest(TimeStampTestCase()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + diff --git a/src/engine/SCons/Sig/TimeStamp.py b/src/engine/SCons/Sig/TimeStamp.py index f66810d5..cb2c2023 100644 --- a/src/engine/SCons/Sig/TimeStamp.py +++ b/src/engine/SCons/Sig/TimeStamp.py @@ -7,43 +7,38 @@ utility. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -def _init(): - pass # XXX - -def _end(): - pass # XXX - def current(obj, sig): - """Return whether the object's timestamp is up-to-date. + """Return whether the objects timestamp is up-to-date. """ - return obj.signature() >= sig + return obj.get_signature() >= sig -def set(): - pass # XXX - -def invalidate(): - pass # XXX +def collect(signatures): + """ + Collect a list of timestamps, returning + the most-recent timestamp from the list -def collect(*objects): - """Collect timestamps from a list of objects, returning - the most-recent timestamp from the list. + signatures - a list of timestamps + returns - the most recent timestamp """ - r = 0 - for obj in objects: - s = obj.signature() - if s > r: - r = s - return r - -def signature(contents): + + if len(signatures) == 0: + return 0 + elif len(signatures) == 1: + return signatures[0] + else: + return max(signatures) + +def signature(obj): """Generate a timestamp. """ - pass # XXX -# return md5.new(contents).hexdigest() # 2.0 - return hexdigest(md5.new(contents).digest()) + return obj.get_timestamp() + +def to_string(signature): + """Convert a timestamp to a string""" + return str(signature) + +def from_string(string): + """Convert a string to a timestamp""" + return int(string) -def cmdsig(): - pass # XXX -def srcsig(): - pass # XXX diff --git a/src/engine/SCons/Sig/TimeStampTests.py b/src/engine/SCons/Sig/TimeStampTests.py index 2dddff8c..0c652ab2 100644 --- a/src/engine/SCons/Sig/TimeStampTests.py +++ b/src/engine/SCons/Sig/TimeStampTests.py @@ -3,7 +3,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest -import SCons.Sig.TimeStamp +from SCons.Sig.TimeStamp import current, collect, signature, to_string, from_string @@ -12,60 +12,47 @@ class my_obj: requirements of the TimeStamp class. """ - def __init__(self, value = ""): + def __init__(self, value = 0): self.value = value - def signature(self): + def get_signature(self): return self.value + def get_timestamp(self): + return self.value class TimeStampTestCase(unittest.TestCase): - def test__init(self): - pass # XXX - - def test__init(self): - pass # XXX - - def test__end(self): - pass # XXX - def test_current(self): """Test deciding if an object is up-to-date Simple comparison of different timestamp values. """ o1 = my_obj(value = 111) - assert SCons.Sig.TimeStamp.current(o1, 110) - assert SCons.Sig.TimeStamp.current(o1, 111) - assert not SCons.Sig.TimeStamp.current(o1, 112) - - def test_set(self): - pass # XXX - - def test_invalidate(self): - pass # XXX + assert current(o1, 110) + assert current(o1, 111) + assert not current(o1, 112) def test_collect(self): """Test collecting a list of signatures into a new signature value into a new timestamp value. """ - o1 = my_obj(value = 111) - o2 = my_obj(value = 222) - o3 = my_obj(value = 333) - assert 111 == SCons.Sig.TimeStamp.collect(o1) - assert 222 == SCons.Sig.TimeStamp.collect(o1, o2) - assert 333 == SCons.Sig.TimeStamp.collect(o1, o2, o3) + + assert 111 == collect((111,)) + assert 222 == collect((111, 222)) + assert 333 == collect((333, 222, 111)) def test_signature(self): - pass # XXX + """Test generating a signature""" + o1 = my_obj(value = 111) + assert 111 == signature(o1) - def test_cmdsig(self): - pass # XXX + def test_to_string(self): + assert '111' == to_string(111) - def test_srcsig(self): - pass # XXX + def test_from_string(self): + assert 111 == from_string('111') if __name__ == "__main__": diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index ba485d88..ea1820e3 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -1,7 +1,193 @@ """SCons.Sig -The Signature package for the SCons software construction utility. +The Signature package for the scons software construction utility. """ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os.path +import string + +class SConsignFile: + """ + Encapsulates reading and writing a .sconsign file. + """ + + def __init__(self, dir, module): + """ + dir - the directory for the file + module - the signature module being used + """ + + self.path = os.path.join(dir, '.sconsign') + self.entries = {} + + try: + file = open(self.path, 'rt') + except: + pass + else: + for line in file.readlines(): + filename, rest = map(string.strip, string.split(line, ":")) + time, signature = map(string.strip, string.split(rest, " ")) + self.entries[filename] = (int(time), module.from_string(signature)) + + def get(self, filename): + """ + Get the signature for a file + + filename - the filename whose signature will be returned + returns - (timestamp, signature) + """ + + try: + return self.entries[filename] + except KeyError: + return (0, None) + + def set(self, filename, timestamp, signature, module): + """ + Set the signature for a file + + filename - the filename whose signature will be set + timestamp - the timestamp + signature - the signature + module - the signature module being used + """ + self.entries[filename] = (timestamp, module.to_string(signature)) + + def write(self): + """ + Write the .sconsign file to disk. + """ + + file = open(self.path, 'wt') + for item in self.entries.items(): + file.write("%s: %d %s\n" % (item[0], item[1][0], item[1][1])) + + +class Calculator: + """ + Encapsulates signature calculations and .sconsign file generating + for the build engine. + """ + + def __init__(self, module): + """ + Initialize the calculator. + + module - the signature module to use for signature calculations + """ + self.module = module + self.sig_files = {} + + + def collect(self, node, signatures): + """ + Collect the signatures of the node's sources. + + node - the node whose sources will be collected + signatures - the dictionary that the signatures will be + gathered into. + """ + for source_node in node.source_nodes: + if not signatures.has_key(source_node): + signature = self.signature(source_node) + signatures[source_node] = signature + self.collect(source_node, signatures) + + def get_sig_file(self, dir): + """ + Get a sconsign file from the cache, or add it to the cache. + + dir - the dir for the sconsign file + returns - the sconsign file + """ + if self.sig_files.has_key(dir): + return self.sig_files[dir] + else: + self.sig_files[dir] = SConsignFile(dir, self.module) + return self.sig_files[dir] + + def signature(self, node): + """ + Get the signature for a node. + + node - the node + returns - the signature or None if the signature could not + be computed. + + This method also stores the signature in the node and + in the .sconsign file. + """ + + if node.has_signature(): + sig = node.get_signature() + elif node.derived: + signatures = {} + self.collect(node, signatures) + signatures = filter(lambda x: not x is None, signatures.values()) + sig = self.module.collect(signatures) + else: + if not node.exists(): + return None + + # XXX handle nodes that are not under the source root + sig = self.module.signature(node) + + node.set_signature(sig) + + dir, filename = os.path.split(node.path) + if node.exists(): + timestamp = node.get_timestamp() + else: + timestamp = 0 + + self.get_sig_file(dir).set(filename, + timestamp, + sig, + self.module) + + return sig + + def current(self, node): + """ + Check if a node is up to date. + + node - the node whose signature will be checked + + returns - 0 if the signature has changed since the last invocation, + and 1 if it hasn't + """ + + if not node.exists(): + return 0 + + dir, filename = os.path.split(node.path) + oldtime, oldsig = self.get_sig_file(dir).get(filename) + + newtime = node.get_timestamp() + + if not node.derived and newtime == oldtime: + newsig = oldsig + else: + newsig = self.signature(node) + + return newsig == oldsig + + def write(self, nodes): + """ + Write out all of the signature files. + + nodes - the nodes whose signatures may have changed durring + the build + """ + + # make sure all the signatures have been calculated: + for node in nodes: + self.signature(node) + + for sig_file in self.sig_files.values(): + sig_file.write() + diff --git a/src/script/scons.py b/src/script/scons.py index 51593eb9..a6a7f81a 100644 --- a/src/script/scons.py +++ b/src/script/scons.py @@ -14,6 +14,8 @@ import traceback import SCons.Node.FS import SCons.Job from SCons.Errors import * +import SCons.Sig +import SCons.Sig.MD5 # # Modules and classes that we don't use directly in this script, but @@ -545,14 +547,18 @@ def main(): if not targets: targets = default_targets - taskmaster = Taskmaster(map( - lambda x: SCons.Node.FS.default_fs.File(x), - targets)) + calc = SCons.Sig.Calculator(SCons.Sig.MD5) + nodes = map(lambda x: SCons.Node.FS.default_fs.File(x), targets) + nodes = filter(lambda x, calc=calc: not calc.current(x), nodes) + + taskmaster = Taskmaster(nodes) jobs = SCons.Job.Jobs(num_jobs, taskmaster) jobs.start() jobs.wait() + calc.write(nodes) + if __name__ == "__main__": try: main() diff --git a/test/Program.py b/test/Program.py index d5178089..4189c466 100644 --- a/test/Program.py +++ b/test/Program.py @@ -3,6 +3,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import TestSCons +import os.path +import time #XXX Future: be able to interpolate @@ -121,4 +123,12 @@ test.run(program = test.workpath('foo2'), stdout = "f2a.c\nf2b.c\nf2c.c\n") #XXXtest.up_to_date(arguments = '.') +# make sure the programs don't get rebuilt, because nothing changed: +oldtime1 = os.path.getmtime(test.workpath('foo1')) +oldtime2 = os.path.getmtime(test.workpath('foo2')) +time.sleep(1) # introduce a small delay, to make the test valid +test.run(arguments = 'foo1 foo2') +test.fail_test(not (oldtime1 == os.path.getmtime(test.workpath('foo1')))) +test.fail_test(not (oldtime2 == os.path.getmtime(test.workpath('foo2')))) + test.pass_test() diff --git a/test/option-j.py b/test/option-j.py index 90d036d5..cc8efa42 100644 --- a/test/option-j.py +++ b/test/option-j.py @@ -36,9 +36,10 @@ env.MyBuild(target = 'f1', source = 'f1.in') env.MyBuild(target = 'f2', source = 'f2.in') """) -def RunTest(args): - test.write('f1.in', 'f1.in') - test.write('f2.in', 'f2.in') +def RunTest(args, extra): + """extra is used to make scons rebuild the output file""" + test.write('f1.in', 'f1.in'+extra) + test.write('f2.in', 'f2.in'+extra) test.run(arguments = args) @@ -50,13 +51,21 @@ def RunTest(args): return start2, finish1 -start2, finish1 = RunTest('-j 2 f1 f2') +start2, finish1 = RunTest('-j 2 f1 f2', "first") # fail if the second file was not started # before the first one was finished test.fail_test(not (start2 < finish1)) -start2, finish1 = RunTest('f1 f2') +s2, f1 = RunTest('-j 2 f1 f2', "first") + +# re-run the test with the same input, fail if we don't +# get back the same times, which would indicate that +# SCons rebuilt the files even though nothing changed +test.fail_test(start2 != s2) +test.fail_test(finish1 != f1) + +start2, finish1 = RunTest('f1 f2', "second") # fail if the second file was started # before the first one was finished