"""
TestSCons.py:  a testing framework for the SCons software construction
tool.

A TestSCons environment object is created via the usual invocation:

    test = TestSCons()

TestScons is a subclass of TestCmd, and hence has available all of its
methods and attributes, as well as any overridden or additional methods
or attributes defined in this subclass.
"""

# Copyright 2001, 2002 Steven Knight

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import os.path
import string
import sys

import TestCmd

class TestFailed(Exception):
    def __init__(self, args=None):
        self.args = args

class TestNoResult(Exception):
    def __init__(self, args=None):
        self.args = args

if os.name == 'posix':
    def _failed(self, status = 0):
        if self.status is None:
            return None
        if os.WIFSIGNALED(status):
            return None
        return _status(self) != status
    def _status(self):
        if os.WIFEXITED(self.status):
            return os.WEXITSTATUS(self.status)
        else:
            return None
elif os.name == 'nt':
    def _failed(self, status = 0):
        return not self.status is None and self.status != status
    def _status(self):
        return self.status

class TestSCons(TestCmd.TestCmd):
    """Class for testing SCons.

    This provides a common place for initializing SCons tests,
    eliminating the need to begin every test with the same repeated
    initializations.
    """

    def __init__(self, **kw):
	"""Initialize an SCons testing object.

	If they're not overridden by keyword arguments, this
	initializes the object with the following default values:

		program = 'scons' if it exists,
			  else 'scons.py'
		interpreter = 'python'
		match = TestCmd.match_exact
		workdir = ''

        The workdir value means that, by default, a temporary workspace
        directory is created for a TestSCons environment.  In addition,
	this method changes directory (chdir) to the workspace directory,
	so an explicit "chdir = '.'" on all of the run() method calls
	is not necessary.
	"""
	if not kw.has_key('program'):
            kw['program'] = os.environ.get('SCONS')
            if not kw['program']:
                if os.path.exists('scons'):
                    kw['program'] = 'scons'
                else:
                    kw['program'] = 'scons.py'
	if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'):
	    kw['interpreter'] = sys.executable
	if not kw.has_key('match'):
	    kw['match'] = TestCmd.match_exact
	if not kw.has_key('workdir'):
	    kw['workdir'] = ''
	apply(TestCmd.TestCmd.__init__, [self], kw)
	os.chdir(self.workdir)

    def run(self, stdout = None, stderr = '', status = 0, **kw):
	"""Runs SCons.

        This is the same as the base TestCmd.run() method, with
        the addition of:

		stdout	The expected standard output from
			the command.  A value of None means
			don't test standard output.

		stderr	The expected error output from
			the command.  A value of None means
			don't test error output.

                status  The expected exit status from the 
                        command. 

        By default, this does not test standard output (stdout = None),
        and expects that error output is empty (stderr = "").
	"""
	try:
	    apply(TestCmd.TestCmd.run, [self], kw)
	except:
	    print "STDOUT ============"
	    print self.stdout()
	    print "STDERR ============"
	    print self.stderr()
	    raise
	if _failed(self, status):
            expect = ''
            if status != 0:
                expect = " (expected %d)" % status
            print "%s returned %d%s" % (self.program, _status(self), expect)
            print "STDOUT ============"
            print self.stdout()
	    print "STDERR ============"
	    print self.stderr()
	    raise TestFailed
	if not stdout is None and not self.match(self.stdout(), stdout):
	    print "Expected STDOUT =========="
	    print stdout
	    print "Actual STDOUT ============"
	    print self.stdout()
	    stderr = self.stderr()
	    if stderr:
		print "STDERR ==================="
		print stderr
	    raise TestFailed
	if not stderr is None and not self.match(self.stderr(), stderr):
            print "STDOUT ==================="
            print self.stdout()
	    print "Expected STDERR =========="
	    print stderr
	    print "Actual STDERR ============"
	    print self.stderr()
	    raise TestFailed

    def up_to_date(self, arguments = None, **kw):
	    kw['arguments'] = arguments
	    s = ""
	    for arg in string.split(arguments):
		s = s + 'scons: "%s" is up to date.\n' % arg
	    kw['stdout'] = s
	    apply(self.run, [], kw)

@@ -2670,16 +2670,68 @@
 un(self,
+ options = None, arguments = None,%0A                 
  stdout 
@@ -3332,16 +3332,122 @@
 ).%0A%09%22%22%22%0A
+        if options:%0A            arguments = options + %22 %22 + arguments%0A        kw%5B'arguments'%5D = arguments%0A
 %09try:%0A%09 
@@ -4601,24 +4601,40 @@
 o_date(self,
+ options = None,
  arguments =
@@ -4651,41 +4651,8 @@
 w):%0A
-%09    kw%5B'arguments'%5D = arguments%0A
 %09   
@@ -4747,16 +4747,135 @@
 ' %25 arg%0A
+                if options:%0A                    arguments = options + %22 %22 + arguments%0A%09    kw%5B'arguments'%5D = arguments%0A
 %09    kw%5B

Add a separate options argument for running SCons via the test infrastructure.