From 652d80b36bf9e80989336bf7a25b54c176ff626c Mon Sep 17 00:00:00 2001 From: Gary Oberbrunner Date: Wed, 29 Oct 2008 03:01:00 +0000 Subject: Applied Benoit Belley's patch in ticket 1957 improve the robustness of GetBuildFailures(). New function convert_to_buildError, and use it in several places so all build failures now go through it and are returned as BuildError exceptions. Had a small effect on output formatting in many tests but no significant change to behavior. I reworked the patch a little to keep SCons exit status values the same as before; this patch could make it simpler to change them in some cases, e.g. exit with the errno of the failed action if desired. One nice side effect of this patch is that more scons errors print the node that caused the error now. --- QMTest/TestCommon.py | 13 +-- src/engine/SCons/Action.py | 53 ++++++------ src/engine/SCons/CacheDirTests.py | 3 +- src/engine/SCons/Errors.py | 147 ++++++++++++++++++++++++++++++--- src/engine/SCons/ErrorsTests.py | 51 +++++++++--- src/engine/SCons/Executor.py | 6 +- src/engine/SCons/Node/FSTests.py | 2 +- src/engine/SCons/Script/Main.py | 67 ++++++++------- src/engine/SCons/Taskmaster.py | 8 +- src/engine/SCons/TaskmasterTests.py | 4 +- src/script/scons.py | 2 + test/Alias/errors.py | 2 +- test/Configure/Action-error.py | 2 +- test/Errors/Exception.py | 2 +- test/GetBuildFailures/serial.py | 160 ++++++++++++++++++++++++++++++++---- test/Libs/SharedLibrary.py | 4 +- test/Parallel/multiple-parents.py | 2 +- test/RCS/diskcheck.py | 2 +- test/Scanner/exception.py | 2 +- test/builderrors.py | 26 ++++++ test/exceptions.py | 4 +- test/nonexistent.py | 16 ++-- test/option/debug-stacktrace.py | 4 +- test/symlink/dangling-include.py | 5 +- test/symlink/dangling-source.py | 2 +- 25 files changed, 457 insertions(+), 132 deletions(-) diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py index 8a3ad37..167f84d 100644 --- a/QMTest/TestCommon.py +++ b/QMTest/TestCommon.py @@ -220,12 +220,13 @@ if os.name == 'posix': return None return _status(self) != status def _status(self): - if os.WIFEXITED(self.status): - return os.WEXITSTATUS(self.status) - elif os.WIFSIGNALED(self.status): - return os.WTERMSIG(self.status) - else: - return None + return self.status # p.wait() has already retrieved the OS status from the status val; don't do it again here! +# if os.WIFEXITED(self.status): +# return os.WEXITSTATUS(self.status) +# elif os.WIFSIGNALED(self.status): +# return os.WTERMSIG(self.status) +# else: +# return None elif os.name == 'nt': def _failed(self, status = 0): return not (self.status is None or status is None) and \ diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index c24d0be..cfd4a50 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -946,31 +946,38 @@ class FunctionAction(_ActionAction): return "%s(target, source, env)" % name def execute(self, target, source, env): - rsources = map(rfile, source) + exc_info = (None,None,None) try: - result = self.execfunction(target=target, source=rsources, env=env) - except EnvironmentError, e: - # If an IOError/OSError happens, raise a BuildError. - # Report the name of the file or directory that caused the - # error, which might be different from the target being built - # (for example, failure to create the directory in which the - # target file will appear). - try: filename = e.filename - except AttributeError: filename = None - result = SCons.Errors.BuildError(node=target, - errstr=e.strerror, - status=1, - filename=filename, - action=self, - command=self.strfunction(target, source, env)) - else: + rsources = map(rfile, source) + try: + result = self.execfunction(target=target, source=rsources, env=env) + except Exception, e: + result = e + exc_info = sys.exc_info() + if result: - msg = "Error %s" % result - result = SCons.Errors.BuildError(errstr=msg, - status=result, - action=self, - command=self.strfunction(target, source, env)) - return result + result = SCons.Errors.convert_to_BuildError(result, exc_info) + result.node=target + result.action=self + result.command=self.strfunction(target, source, env) + + # FIXME: This maintains backward compatibility with respect to + # which type of exceptions were returned by raising an + # exception and which ones were returned by value. It would + # probably be best to always return them by value here, but + # some codes do not check the return value of Actions and I do + # not have the time to modify them at this point. + if (exc_info[1] and + not isinstance(exc_info[1],EnvironmentError)): + raise result + + return result + finally: + # Break the cycle between the traceback object and this + # function stack frame. See the sys.exc_info() doc info for + # more information about this issue. + del exc_info + def get_presig(self, target, source, env): """Return the signature contents of this callable action.""" diff --git a/src/engine/SCons/CacheDirTests.py b/src/engine/SCons/CacheDirTests.py index e31f67b..3715bf1 100644 --- a/src/engine/SCons/CacheDirTests.py +++ b/src/engine/SCons/CacheDirTests.py @@ -241,7 +241,8 @@ class FileTestCase(BaseTestCase): warn_caught = 0 try: f7.built() - except SCons.Warnings.CacheWriteErrorWarning: + except SCons.Errors.BuildError, e: + assert e.exc_info[0] == SCons.Warnings.CacheWriteErrorWarning warn_caught = 1 assert warn_caught finally: diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py index fc55cf4..e663fb4 100644 --- a/src/engine/SCons/Errors.py +++ b/src/engine/SCons/Errors.py @@ -30,20 +30,88 @@ and user errors in SCons. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import SCons.Util +import exceptions class BuildError(Exception): - def __init__(self, node=None, errstr="Unknown error", status=0, - filename=None, executor=None, action=None, command=None, - *args): - self.node = node + """ Errors occuring while building. + + BuildError have the following attributes: + + Information about the cause of the build error: + ----------------------------------------------- + + errstr : a description of the error message + + status : the return code of the action that caused the build + error. Must be set to a non-zero value even if the + build error is not due to an action returning a + non-zero returned code. + + exitstatus : SCons exit status due to this build error. + Must be nonzero unless due to an explicit Exit() + call. Not always the same as status, since + actions return a status code that should be + respected, but SCons typically exits with 2 + irrespective of the return value of the failed + action. + + filename : The name of the file or directory that caused the + build error. Set to None if no files are associated with + this error. This might be different from the target + being built. For example, failure to create the + directory in which the target file will appear. It + can be None if the error is not due to a particular + filename. + + exc_info : Info about exception that caused the build + error. Set to (None, None, None) if this build + error is not due to an exception. + + + Information about the cause of the location of the error: + --------------------------------------------------------- + + node : the error occured while building this target node(s) + + executor : the executor that caused the build to fail (might + be None if the build failures is not due to the + executor failing) + + action : the action that caused the build to fail (might be + None if the build failures is not due to the an + action failure) + + command : the command line for the action that caused the + build to fail (might be None if the build failures + is not due to the an action failure) + """ + + def __init__(self, + node=None, errstr="Unknown error", status=2, exitstatus=2, + filename=None, executor=None, action=None, command=None, + exc_info=(None, None, None)): + self.errstr = errstr self.status = status + self.exitstatus = exitstatus self.filename = filename + self.exc_info = exc_info + + self.node = node self.executor = executor self.action = action self.command = command - apply(Exception.__init__, (self,) + args) + + Exception.__init__(self, node, errstr, status, exitstatus, filename, + executor, action, command, exc_info) + + def __str__(self): + if self.filename: + return self.filename + ': ' + self.errstr + else: + return self.errstr class InternalError(Exception): pass @@ -61,11 +129,68 @@ class ExplicitExit(Exception): def __init__(self, node=None, status=None, *args): self.node = node self.status = status + self.exitstatus = status apply(Exception.__init__, (self,) + args) -class TaskmasterException(Exception): - def __init__(self, node=None, exc_info=(None, None, None), *args): - self.node = node - self.errstr = "Exception" - self.exc_info = exc_info - apply(Exception.__init__, (self,) + args) +def convert_to_BuildError(status, exc_info=None): + """ + Convert any return code a BuildError Exception. + + `status' can either be a return code or an Exception. + The buildError.status we set here will normally be + used as the exit status of the "scons" process. + """ + if not exc_info and isinstance(status, Exception): + exc_info = (status.__class__, status, None) + + if isinstance(status, BuildError): + buildError = status + buildError.exitstatus = 2 # always exit with 2 on build errors + elif isinstance(status, ExplicitExit): + status = status.status + errstr = 'Explicit exit, status %s' % status + buildError = BuildError( + errstr=errstr, + status=status, # might be 0, OK here + exitstatus=status, # might be 0, OK here + exc_info=exc_info) + elif isinstance(status, (StopError, UserError)): + buildError = BuildError( + errstr=str(status), + status=2, + exitstatus=2, + exc_info=exc_info) + elif isinstance(status, exceptions.EnvironmentError): + # If an IOError/OSError happens, raise a BuildError. + # Report the name of the file or directory that caused the + # error, which might be different from the target being built + # (for example, failure to create the directory in which the + # target file will appear). + try: filename = status.filename + except AttributeError: filename = None + buildError = BuildError( + errstr=status.strerror, + status=status.errno, + exitstatus=2, + filename=filename, + exc_info=exc_info) + elif isinstance(status, Exception): + buildError = BuildError( + errstr='%s : %s' % (status.__class__.__name__, status), + status=2, + exitstatus=2, + exc_info=exc_info) + elif SCons.Util.is_String(status): + buildError = BuildError( + errstr=status, + status=2, + exitstatus=2) + else: + buildError = BuildError( + errstr="Error %s" % status, + status=status, + exitstatus=2) + + #import sys + #sys.stderr.write("convert_to_BuildError: status %s => (errstr %s, status %s)"%(status,buildError.errstr, buildError.status)) + return buildError diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py index 93d516c..893d284 100644 --- a/src/engine/SCons/ErrorsTests.py +++ b/src/engine/SCons/ErrorsTests.py @@ -32,10 +32,49 @@ class ErrorsTestCase(unittest.TestCase): def test_BuildError(self): """Test the BuildError exception.""" try: - raise SCons.Errors.BuildError(node = "n", errstr = "foo") + raise SCons.Errors.BuildError( + errstr = "foo", status=57, filename="file", exc_info=(1,2,3), + node = "n", executor="e", action="a", command="c") except SCons.Errors.BuildError, e: - assert e.node == "n" assert e.errstr == "foo" + assert e.status == 57 + assert e.exitstatus == 2, e.exitstatus + assert e.filename == "file" + assert e.exc_info == (1,2,3) + + assert e.node == "n" + assert e.executor == "e" + assert e.action == "a" + assert e.command == "c" + + try: + raise SCons.Errors.BuildError("n", "foo", 57, 3, "file", + "e", "a", "c", (1,2,3)) + except SCons.Errors.BuildError, e: + assert e.errstr == "foo", e.errstr + assert e.status == 57, e.status + assert e.exitstatus == 3, e.exitstatus + assert e.filename == "file", e.filename + assert e.exc_info == (1,2,3), e.exc_info + + assert e.node == "n" + assert e.executor == "e" + assert e.action == "a" + assert e.command == "c" + + try: + raise SCons.Errors.BuildError() + except SCons.Errors.BuildError, e: + assert e.errstr == "Unknown error" + assert e.status == 2 + assert e.exitstatus == 2 + assert e.filename == None + assert e.exc_info == (None, None, None) + + assert e.node == None + assert e.executor == None + assert e.action == None + assert e.command == None def test_InternalError(self): """Test the InternalError exception.""" @@ -58,14 +97,6 @@ class ErrorsTestCase(unittest.TestCase): except SCons.Errors.ExplicitExit, e: assert e.node == "node" - def test_TaskmasterException(self): - """Test the TaskmasterException exception.""" - try: - raise SCons.Errors.TaskmasterException("tm exception", (1, 2, 3)) - except SCons.Errors.TaskmasterException, e: - assert e.node == "tm exception" - assert e.exc_info == (1, 2, 3) - if __name__ == "__main__": suite = unittest.makeSuite(ErrorsTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index fe3cb62..6b9ab0f 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -134,7 +134,11 @@ class Executor: raise status elif status: msg = "Error %s" % status - raise SCons.Errors.BuildError(errstr=msg, executor=self, action=act) + raise SCons.Errors.BuildError( + errstr=msg, + node=self.targets, + executor=self, + action=act) return status # use extra indirection because with new-style objects (Python 2.2 diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 4081a7a..3d1afdc 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -361,7 +361,7 @@ class VariantDirTestCase(unittest.TestCase): save_Link = SCons.Node.FS.Link def Link_IOError(target, source, env): - raise IOError, "Link_IOError" + raise IOError, (17, "Link_IOError") SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None) test.write(['work', 'src', 'IOError'], "work/src/IOError\n") diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 7b6b52b..62a52d6 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -231,54 +231,51 @@ class BuildTask(SCons.Taskmaster.Task): # Handle the failure of a build task. The primary purpose here # is to display the various types of Errors and Exceptions # appropriately. - status = 2 exc_info = self.exc_info() try: t, e, tb = exc_info except ValueError: t, e = exc_info tb = None + if t is None: # The Taskmaster didn't record an exception for this Task; # see if the sys module has one. - t, e = sys.exc_info()[:2] - - def nodestring(n): - if not SCons.Util.is_List(n): - n = [ n ] - return string.join(map(str, n), ', ') + try: + t, e, tb = sys.exc_info()[:] + except ValueError: + t, e = exc_info + tb = None + + # Deprecated string exceptions will have their string stored + # in the first entry of the tuple. + if e is None: + e = t + + buildError = SCons.Errors.convert_to_BuildError(e) + if not buildError.node: + buildError.node = self.node + + node = buildError.node + if not SCons.Util.is_List(node): + node = [ node ] + nodename = string.join(map(str, node), ', ') errfmt = "scons: *** [%s] %s\n" + sys.stderr.write(errfmt % (nodename, buildError)) - if t == SCons.Errors.BuildError: - tname = nodestring(e.node) - errstr = e.errstr - if e.filename: - errstr = e.filename + ': ' + errstr - sys.stderr.write(errfmt % (tname, errstr)) - elif t == SCons.Errors.TaskmasterException: - tname = nodestring(e.node) - sys.stderr.write(errfmt % (tname, e.errstr)) - type, value, trace = e.exc_info + if (buildError.exc_info[2] and buildError.exc_info[1] and + not isinstance( + buildError.exc_info[1], + (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))): + type, value, trace = buildError.exc_info traceback.print_exception(type, value, trace) - elif t == SCons.Errors.ExplicitExit: - status = e.status - tname = nodestring(e.node) - errstr = 'Explicit exit, status %s' % status - sys.stderr.write(errfmt % (tname, errstr)) - else: - if e is None: - e = t - s = str(e) - if t == SCons.Errors.StopError and not self.options.keep_going: - s = s + ' Stop.' - sys.stderr.write("scons: *** %s\n" % s) - - if tb and print_stacktrace: - sys.stderr.write("scons: internal stack trace:\n") - traceback.print_tb(tb, file=sys.stderr) + elif tb and print_stacktrace: + sys.stderr.write("scons: internal stack trace:\n") + traceback.print_tb(tb, file=sys.stderr) - self.do_failed(status) + self.exception = (e, buildError, tb) # type, value, traceback + self.do_failed(buildError.exitstatus) self.exc_clear() @@ -1273,6 +1270,8 @@ def main(): except SConsPrintHelpException: parser.print_help() exit_status = 0 + except SCons.Errors.BuildError, e: + exit_status = e.exitstatus except: # An exception here is likely a builtin Python exception Python # code in an SConscript file. Show them precisely what the diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index f8c494d..979b9e9 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -225,9 +225,11 @@ class Task: raise except SCons.Errors.BuildError: raise - except: - raise SCons.Errors.TaskmasterException(self.targets[0], - sys.exc_info()) + except Exception, e: + buildError = SCons.Errors.convert_to_BuildError(e) + buildError.node = self.targets[0] + buildError.exc_info = sys.exc_info() + raise buildError def executed_without_callbacks(self): """ diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index 9324581..0735cc1 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -876,9 +876,9 @@ class TaskmasterTestCase(unittest.TestCase): t = tm.next_task() try: t.execute() - except SCons.Errors.TaskmasterException, e: + except SCons.Errors.BuildError, e: assert e.node == n4, e.node - assert e.errstr == "Exception", e.errstr + assert e.errstr == "OtherError : ", e.errstr assert len(e.exc_info) == 3, e.exc_info exc_traceback = sys.exc_info()[2] assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2] diff --git a/src/script/scons.py b/src/script/scons.py index d8ad7b2..698c9b4 100644 --- a/src/script/scons.py +++ b/src/script/scons.py @@ -160,4 +160,6 @@ sys.path = libs + sys.path if __name__ == "__main__": import SCons.Script + # this does all the work, and calls sys.exit + # with the proper exit status when done. SCons.Script.main() diff --git a/test/Alias/errors.py b/test/Alias/errors.py index 1205b29..36876c4 100644 --- a/test/Alias/errors.py +++ b/test/Alias/errors.py @@ -40,7 +40,7 @@ env.Alias('A', 'B') """) test.run(arguments='A', - stderr="scons: *** Source `D' not found, needed by target `C'. Stop.\n", + stderr="scons: *** [C] Source `D' not found, needed by target `C'.\n", status=2) test.pass_test() diff --git a/test/Configure/Action-error.py b/test/Configure/Action-error.py index 0abcc7c..22ea7c7 100644 --- a/test/Configure/Action-error.py +++ b/test/Configure/Action-error.py @@ -41,7 +41,7 @@ env = Environment(BUILDERS = {'MyAction' : env.MyAction('target', []) """) -expect = "scons: *** Calling Configure from Builders is not supported.\n" +expect = "scons: *** [target] Calling Configure from Builders is not supported.\n" test.run(status=2, stderr=expect) diff --git a/test/Errors/Exception.py b/test/Errors/Exception.py index 710c819..8485ce5 100644 --- a/test/Errors/Exception.py +++ b/test/Errors/Exception.py @@ -53,7 +53,7 @@ test.write('exit.in', 'exit\n') # no longer exists or that line in the source file no longer exists, # so make sure the proper variations are supported in the following # regexp. -expect = """scons: \*\*\* \[exit.out\] Exception +expect = """scons: \*\*\* \[exit.out\] Exception : exit Traceback \((most recent call|innermost) last\): ( File ".+", line \d+, in \S+ [^\n]+ diff --git a/test/GetBuildFailures/serial.py b/test/GetBuildFailures/serial.py index c8205ed..b5d8e44 100644 --- a/test/GetBuildFailures/serial.py +++ b/test/GetBuildFailures/serial.py @@ -31,6 +31,7 @@ attributes we expect to be most commonly used. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import TestSCons +import re _python_ = TestSCons._python_ @@ -59,19 +60,43 @@ test.write('mypass.py', contents) test.write('myfail.py', contents) test.write('SConstruct', """\ -Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE') -Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE') -Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE') -Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE') +Command('f03', 'f03.in', r'@%(_python_)s mypass.py - f03 $TARGET $SOURCE') +Command('f04', 'f04.in', r'@%(_python_)s myfail.py f03 f04 $TARGET $SOURCE') +Command('f05', 'f05.in', r'@%(_python_)s myfail.py f04 f05 $TARGET $SOURCE') +Command('f06', 'f06.in', r'@%(_python_)s mypass.py f05 - $TARGET $SOURCE') +Command('f07', 'f07.in', r'@%(_python_)s mypass.py f07 - $TARGET $SOURCE') + +import SCons.Errors +def raiseExcAction(exc): + def action(env, target, source): + raise exc + return action +def returnExcAction(exc): + def action(env, target, source): + return exc + return action +class MyBuildError(SCons.Errors.BuildError): + pass + +Command('f08', 'f08.in', raiseExcAction(SCons.Errors.UserError("My User Error"))) +Command('f09', 'f09.in', returnExcAction(SCons.Errors.UserError("My User Error"))) +Command('f10', 'f10.in', raiseExcAction(MyBuildError(errstr="My Build Error", status=7))) +Command('f11', 'f11.in', returnExcAction(MyBuildError(errstr="My Build Error", status=7))) +Command('f12', 'f12.in', raiseExcAction(EnvironmentError(123, "My EnvironmentError", "f12"))) +Command('f13', 'f13.in', returnExcAction(EnvironmentError(123, "My EnvironmentError", "f13"))) +Command('f14', 'f14.in', raiseExcAction(SCons.Errors.InternalError("My InternalError"))) +Command('f15', 'f15.in', returnExcAction(SCons.Errors.InternalError("My InternalError"))) def print_build_failures(): from SCons.Script import GetBuildFailures import string bf_list = GetBuildFailures() - bf_list.sort(lambda a,b: cmp(a.filename, b.filename)) + bf_list.sort(lambda a,b: cmp(str(a.node), str(b.node))) for bf in bf_list: - print "%%s failed (%%s): %%s" %% (bf.node, bf.status, bf.errstr) - print " %%s" %% string.join(bf.command) + assert( isinstance(bf, SCons.Errors.BuildError) ) + print "BF: %%s failed (%%s): %%s" %% (bf.node, bf.status, bf.errstr) + if bf.command: + print "BF: %%s" %% string.join(Flatten(bf.command)) try: import atexit @@ -82,22 +107,31 @@ else: atexit.register(print_build_failures) """ % locals()) -test.write('f3.in', "f3.in\n") -test.write('f4.in', "f4.in\n") -test.write('f5.in', "f5.in\n") -test.write('f6.in', "f6.in\n") +test.write('f03.in', "f03.in\n") +test.write('f04.in', "f04.in\n") +test.write('f05.in', "f05.in\n") +test.write('f06.in', "f06.in\n") +# f07.in is intentionally missing... +test.write('f08.in', "f08.in\n") +test.write('f09.in', "f09.in\n") +test.write('f10.in', "f10.in\n") +test.write('f11.in', "f11.in\n") +test.write('f12.in', "f12.in\n") +test.write('f13.in', "f13.in\n") +test.write('f14.in', "f14.in\n") +test.write('f15.in', "f15.in\n") expect_stdout = """\ scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... scons: building terminated because of errors. -f4 failed (1): Error 1 - %(_python_)s myfail.py f3 f4 "f4" "f4.in" +BF: f04 failed (1): Error 1 +BF: %(_python_)s myfail.py f03 f04 "f04" "f04.in" """ % locals() expect_stderr = """\ -scons: *** [f4] Error 1 +scons: *** [f04] Error 1 """ test.run(arguments = '.', @@ -105,11 +139,101 @@ test.run(arguments = '.', stdout = expect_stdout, stderr = expect_stderr) -test.must_match(test.workpath('f3'), 'f3.in\n') -test.must_not_exist(test.workpath('f4')) -test.must_not_exist(test.workpath('f5')) -test.must_not_exist(test.workpath('f6')) +test.must_match(test.workpath('f03'), 'f03.in\n') +test.must_not_exist(test.workpath('f04')) +test.must_not_exist(test.workpath('f05')) +test.must_not_exist(test.workpath('f06')) +test.must_not_exist(test.workpath('f07')) +test.must_not_exist(test.workpath('f08')) +test.must_not_exist(test.workpath('f09')) +test.must_not_exist(test.workpath('f10')) +test.must_not_exist(test.workpath('f11')) +test.must_not_exist(test.workpath('f12')) +test.must_not_exist(test.workpath('f13')) +test.must_not_exist(test.workpath('f14')) +test.must_not_exist(test.workpath('f15')) + + +expect_stdout = re.escape("""\ +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +action(["f08"], ["f08.in"]) +action(["f09"], ["f09.in"]) +action(["f10"], ["f10.in"]) +action(["f11"], ["f11.in"]) +action(["f12"], ["f12.in"]) +action(["f13"], ["f13.in"]) +action(["f14"], ["f14.in"]) +action(["f15"], ["f15.in"]) +scons: done building targets (errors occurred during build). +BF: f04 failed (1): Error 1 +BF: %(_python_)s myfail.py f03 f04 "f04" "f04.in" +BF: f05 failed (1): Error 1 +BF: %(_python_)s myfail.py f04 f05 "f05" "f05.in" +BF: f07 failed (2): Source `f07.in' not found, needed by target `f07'. +BF: f08 failed (2): My User Error +BF: action(["f08"], ["f08.in"]) +BF: f09 failed (2): My User Error +BF: action(["f09"], ["f09.in"]) +BF: f10 failed (7): My Build Error +BF: action(["f10"], ["f10.in"]) +BF: f11 failed (7): My Build Error +BF: action(["f11"], ["f11.in"]) +BF: f12 failed (123): My EnvironmentError +BF: action(["f12"], ["f12.in"]) +BF: f13 failed (123): My EnvironmentError +BF: action(["f13"], ["f13.in"]) +BF: f14 failed (2): InternalError : My InternalError +BF: action(["f14"], ["f14.in"]) +BF: f15 failed (2): InternalError : My InternalError +BF: action(["f15"], ["f15.in"]) +""" % locals()) +expect_stderr = re.escape("""\ +scons: *** [f04] Error 1 +scons: *** [f05] Error 1 +scons: *** [f07] Source `f07.in' not found, needed by target `f07'. +scons: *** [f08] My User Error +scons: *** [f09] My User Error +scons: *** [f10] My Build Error +scons: *** [f11] My Build Error +scons: *** [f12] f12: My EnvironmentError +scons: *** [f13] f13: My EnvironmentError +scons: *** [f14] InternalError : My InternalError +""") + \ +"""\ +Traceback \((most recent call|innermost) last\): +( File ".+", line \d+, in \S+ + [^\n]+ +)*( File ".+", line \d+, in \S+ +)*( File ".+", line \d+, in \S+ + [^\n]+ +)*\S.+ +""" + \ +re.escape("""\ +scons: *** [f15] InternalError : My InternalError +""") + +test.run(arguments = '-k .', + status = 2, + stdout = expect_stdout, + stderr = expect_stderr, + match = TestSCons.match_re_dotall) + +test.must_match(test.workpath('f03'), 'f03.in\n') +test.must_not_exist(test.workpath('f04')) +test.must_not_exist(test.workpath('f05')) +test.must_match(test.workpath('f06'), 'f06.in\n') +test.must_not_exist(test.workpath('f07')) +test.must_not_exist(test.workpath('f08')) +test.must_not_exist(test.workpath('f09')) +test.must_not_exist(test.workpath('f10')) +test.must_not_exist(test.workpath('f11')) +test.must_not_exist(test.workpath('f12')) +test.must_not_exist(test.workpath('f13')) +test.must_not_exist(test.workpath('f14')) +test.must_not_exist(test.workpath('f15')) test.pass_test() diff --git a/test/Libs/SharedLibrary.py b/test/Libs/SharedLibrary.py index f8447ca..554f8df 100644 --- a/test/Libs/SharedLibrary.py +++ b/test/Libs/SharedLibrary.py @@ -213,13 +213,13 @@ if sys.platform == 'win32' or string.find(sys.platform, 'irix') != -1: test.run(arguments = '-f SConstructFoo') else: test.run(arguments = '-f SConstructFoo', status=2, stderr='''\ -scons: \*\*\* Source file: foo\..* is static and is not compatible with shared target: .* +scons: \*\*\* \[.*\] Source file: foo\..* is static and is not compatible with shared target: .* ''', match=TestSCons.match_re_dotall) # Run it again to make sure that we still get the error # even though the static objects already exist. test.run(arguments = '-f SConstructFoo', status=2, stderr='''\ -scons: \*\*\* Source file: foo\..* is static and is not compatible with shared target: .* +scons: \*\*\* \[.*\] Source file: foo\..* is static and is not compatible with shared target: .* ''', match=TestSCons.match_re_dotall) diff --git a/test/Parallel/multiple-parents.py b/test/Parallel/multiple-parents.py index d9c414f..cfcad73 100644 --- a/test/Parallel/multiple-parents.py +++ b/test/Parallel/multiple-parents.py @@ -143,7 +143,7 @@ Default(all) re_error = """\ (scons: \\*\\*\\* \\[failed\\d+] Error 2\\n)|\ -(scons: \\*\\*\\* Source `MissingSrc' not found, needed by target `missing\\d+'\\.( Stop\\.)?\\n)|\ +(scons: \\*\\*\\* \\[missing\\d+] Source `MissingSrc' not found, needed by target `missing\\d+'\\.( Stop\\.)?\\n)|\ (scons: \\*\\*\\* \\[\\w+] Build interrupted\.\\n)\ """ diff --git a/test/RCS/diskcheck.py b/test/RCS/diskcheck.py index 07cb170..99ba7ed 100644 --- a/test/RCS/diskcheck.py +++ b/test/RCS/diskcheck.py @@ -122,7 +122,7 @@ expect = """\ scons: warning: Ignoring missing SConscript '%(sub_SConscript)s' %(SConstruct_file_line)s -scons: *** Source `aaa.in' not found, needed by target `aaa.out'. Stop. +scons: *** [aaa.out] Source `aaa.in' not found, needed by target `aaa.out'. """ % locals() test.run(status=2, stderr=expect) diff --git a/test/Scanner/exception.py b/test/Scanner/exception.py index d8204db..5d10fd7 100644 --- a/test/Scanner/exception.py +++ b/test/Scanner/exception.py @@ -109,7 +109,7 @@ test.write('zzz', "zzz 1\n") test.run(arguments = '.', status = 2, stderr = """\ -scons: *** kfile_scan error: yyy 1 +scons: *** [foo] Exception : kfile_scan error: yyy 1 """) test.pass_test() diff --git a/test/builderrors.py b/test/builderrors.py index 28c9a0a..deb52a0 100644 --- a/test/builderrors.py +++ b/test/builderrors.py @@ -181,5 +181,31 @@ test.fail_test(string.find(err, 'Exception') != -1 or \ string.find(err, 'Traceback') != -1) +# Test SConscript with errors and an atexit function. +# Should not give traceback; the task error should get converted +# to a BuildError. +test.write('SConstruct', """ +import atexit + +env = Environment() +env2 = env.Clone() + +env.Install("target", "dir1/myFile") +env2.Install("target", "dir2/myFile") + +def print_build_failures(): + from SCons.Script import GetBuildFailures + for bf in GetBuildFailures(): + print bf.action + +atexit.register(print_build_failures) +""") + +test.run(status=2, stderr=None) +err = test.stderr() +test.fail_test(string.find(err, 'Exception') != -1 or \ + string.find(err, 'Traceback') != -1) + + # No tests failed; OK. test.pass_test() diff --git a/test/exceptions.py b/test/exceptions.py index b0939c0..98e3e83 100644 --- a/test/exceptions.py +++ b/test/exceptions.py @@ -47,7 +47,7 @@ env.B(target = 'foo.out', source = 'foo.in') test.write('foo.in', "foo.in\n") -expected_stderr = """scons: \*\*\* \[foo.out\] Exception +expected_stderr = """scons: \*\*\* \[foo.out\] Exception : func exception Traceback \((most recent call|innermost) last\): ( File ".+", line \d+, in \S+ [^\n]+ @@ -109,7 +109,7 @@ test.run(arguments = '.', status = 2, stderr = expected_stderr) expected_stderr_list = [ "scons: *** [out.f1] Error 1\n", - "scons: *** Source `in.f2' not found, needed by target `out.f2'.\n", + "scons: *** [out.f2] Source `in.f2' not found, needed by target `out.f2'.\n", "scons: *** [out.f3] Error 1\n", ] diff --git a/test/nonexistent.py b/test/nonexistent.py index 3e47cbe..b130a1a 100644 --- a/test/nonexistent.py +++ b/test/nonexistent.py @@ -46,20 +46,21 @@ Dir('ddd') """) test.run(arguments = 'foo', - stderr = "scons: *** Do not know how to make target `foo'. Stop.\n", - status = 2) + stderr = "scons: \\*\\*\\* Do not know how to make target `foo'.( *Stop.)?\n", + status = 2, + match=TestSCons.match_re_dotall) test.run(arguments = '-k foo/bar foo', stderr = "scons: *** Do not know how to make target `%s'.\n" % foo_bar, status = 2) test.run(arguments = "aaa.out", - stderr = "scons: *** Source `aaa.in' not found, needed by target `aaa.out'. Stop.\n", + stderr = "scons: *** [aaa.out] Source `aaa.in' not found, needed by target `aaa.out'.\n", status = 2) test.run(arguments = "-k bbb.out aaa.out", - stderr = """scons: *** Source `bbb.in' not found, needed by target `bbb.out'. -scons: *** Source `aaa.in' not found, needed by target `aaa.out'. + stderr = """scons: *** [bbb.out] Source `bbb.in' not found, needed by target `bbb.out'. +scons: *** [aaa.out] Source `aaa.in' not found, needed by target `aaa.out'. """, status = 2) @@ -71,8 +72,9 @@ scons: *** Do not know how to make target `bbb.in'. test.run(arguments = 'xxx', - stderr = "scons: *** Do not know how to make target `xxx'. Stop.\n", - status = 2) + stderr = "scons: \\*\\*\\* Do not know how to make target `xxx'.( *Stop.)?\n", + status = 2, + match=TestSCons.match_re_dotall) test.run(arguments = 'ddd') diff --git a/test/option/debug-stacktrace.py b/test/option/debug-stacktrace.py index cf7b81d..93cb206 100644 --- a/test/option/debug-stacktrace.py +++ b/test/option/debug-stacktrace.py @@ -65,14 +65,14 @@ env.Command('foo', 'foo.k', Copy('$TARGET', '$SOURCE')) test.write('foo.k', "foo.k\n") -test.run(status = 2, stderr = "scons: *** kfile_scan error\n") +test.run(status = 2, stderr = "scons: *** [foo] Exception : kfile_scan error\n") test.run(arguments = "--debug=stacktrace", status = 2, stderr = None) lines = [ - "scons: *** kfile_scan error", + "scons: *** [foo] Exception : kfile_scan error", "scons: internal stack trace:", 'raise Exception, "kfile_scan error"', ] diff --git a/test/symlink/dangling-include.py b/test/symlink/dangling-include.py index 61ef07d..9c9d93a 100644 --- a/test/symlink/dangling-include.py +++ b/test/symlink/dangling-include.py @@ -51,9 +51,10 @@ test.write('foo.c', """\ test.symlink('nonexistent', 'foo.h') expect = """\ -scons: *** Implicit dependency `foo.h' not found, needed by target `%s'. Stop. +scons: \\*\\*\\* \\[foo.o(bj)?\\] Implicit dependency `foo.h' not found, needed by target `%s'.( Stop.)? """% foo_obj -test.run(arguments = '.', status = 2, stderr = expect) +test.run(arguments = '.', status = 2, stderr = expect, + match=TestSCons.match_re_dotall) test.pass_test() diff --git a/test/symlink/dangling-source.py b/test/symlink/dangling-source.py index e242e61..366b742 100644 --- a/test/symlink/dangling-source.py +++ b/test/symlink/dangling-source.py @@ -45,7 +45,7 @@ Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE')) test.symlink('nonexistent', 'file.in') expect = """\ -scons: *** Source `file.in' not found, needed by target `file.out'. Stop. +scons: *** [file.out] Source `file.in' not found, needed by target `file.out'. """ test.run(arguments = '.', status = 2, stderr = expect) -- cgit v0.12