<?php
    /**
     *	base include file for SimpleTest
     *	@package	SimpleTest
     *	@subpackage	UnitTester
     *	@version	$Id$
     */

    /**#@+
     * Includes SimpleTest files and defined the root constant
     * for dependent libraries.
     */
    require_once(dirname(__FILE__) . '/invoker.php');
    require_once(dirname(__FILE__) . '/expectation.php');

    /**
     *    Extension that traps exceptions and turns them into
     *    an error message. PHP5 only.
	 *	  @package SimpleTest
	 *	  @subpackage UnitTester
     */
    class SimpleExceptionTrappingInvoker extends SimpleInvokerDecorator {

        /**
         *    Stores the invoker to be wrapped.
         *    @param SimpleInvoker $invoker   Test method runner.
         */
        function SimpleExceptionTrappingInvoker($invoker) {
            $this->SimpleInvokerDecorator($invoker);
        }

        /**
         *    Invokes a test method whilst trapping expected
         *    exceptions. Any left over unthrown exceptions
         *    are then reported as failures.
         *    @param string $method    Test method to call.
         */
        function invoke($method) {
            $trap = SimpleTest::getContext()->get('SimpleExceptionTrap');
            $trap->clear();
            try {
                parent::invoke($method);
            } catch (Exception $exception) {
	            if (! $trap->isExpected($this->getTestCase(), $exception)) {
	                $this->getTestCase()->exception($exception);
	            }
	            $trap->clear();
            }
			if ($message = $trap->getOutstanding()) {
				$this->getTestCase()->fail($message);
			}
        }
    }

    /**
     *    Tests exceptions either by type or the exact
     *    exception. This could be improved to accept
     *    a pattern expectation to test the error
     *    message, but that will have to come later.
	 *	  @package SimpleTest
	 *	  @subpackage UnitTester
     */
    class ExceptionExpectation extends SimpleExpectation {
        private $expected;

        /**
         *    Sets up the conditions to test against.
         *    If the expected value is a string, then
         *    it will act as a test of the class name.
         *    An exception as the comparison will
         *    trigger an identical match. Writing this
         *    down now makes it look doubly dumb. I hope
         *    come up with a better scheme later.
         *    @param mixed $expected   A class name or an actual
         *                             exception to compare with.
         *    @param string $message   Message to display.
         */
        function __construct($expected, $message = '%s') {
            $this->expected = $expected;
            parent::__construct($message);
        }

        /**
         *    Carry out the test.
         *    @param Exception $compare    Value to check.
         *    @return boolean              True if matched.
         */
        function test($compare) {
            if (is_string($this->expected)) {
                return ($compare instanceof $this->expected);
            }
            if (get_class($compare) != get_class($this->expected)) {
                return false;
            }
            return $compare->getMessage() == $this->expected->getMessage();
        }

        /**
         *    Create the message to display describing the test.
         *    @param Exception $compare     Exception to match.
         *    @return string                Final message.
         */
        function testMessage($compare) {
            if (is_string($this->expected)) {
                return "Exception [" . $this->describeException($compare) .
                        "] should be type [" . $this->expected . "]";
            }
            return "Exception [" . $this->describeException($compare) .
                    "] should match [" .
                    $this->describeException($this->expected) . "]";
        }

        /**
         *    Summary of an Exception object.
         *    @param Exception $compare     Exception to describe.
         *    @return string                Text description.
         */
        function describeException($exception) {
            return get_class($exception) . ": " . $exception->getMessage();
        }
    }

    /**
     *    Stores expected exceptions for when they
     *    get thrown. Saves the irritating try...catch
     *    block.
	 *	  @package	SimpleTest
	 *	  @subpackage	UnitTester
     */
    class SimpleExceptionTrap {
        private $expected;
        private $message;

        /**
         *    Clears down the queue ready for action.
         */
        function __construct() {
            $this->clear();
        }

        /**
         *    Sets up an expectation of an exception.
         *    This has the effect of intercepting an
         *    exception that matches.
         *    @param SimpleExpectation $expected    Expected exception to match.
         *    @param string $message                Message to display.
         *    @access public
         */
        function expectException($expected = false, $message = '%s') {
            if ($expected === false) {
                $expected = new AnythingExpectation();
            }
            if (! SimpleExpectation::isExpectation($expected)) {
                $expected = new ExceptionExpectation($expected);
            }
            $this->expected = $expected;
            $this->message = $message;
        }

        /**
         *    Compares the expected exception with any
         *    in the queue. Issues a pass or fail and
         *    returns the state of the test.
         *    @param SimpleTestCase $test    Test case to send messages to.
         *    @param Exception $exception    Exception to compare.
         *    @return boolean                False on no match.
         */
        function isExpected($test, $exception) {
            if ($this->expected) {
	            return $test->assert($this->expected, $exception, $this->message);
            }
			return false;
        }

		/**
		 *    Tests for any left over exception.
		 *    @return string/false     The failure message or false if none.
		 */
		function getOutstanding() {
			return sprintf($this->message, 'Failed to trap exception');
		}

        /**
         *    Discards the contents of the error queue.
         */
        function clear() {
            $this->expected = false;
            $this->message = false;
        }
    }
?>