doc "Capable of running tests, notifying [[TestListener]]s about each test"
shared class TestRunner() {
    
    value testList = SequenceBuilder<TestUnit>();
    value testListenerList = SequenceBuilder<TestListener>();
    
    doc "The tests held by this instance"
    shared List<TestUnit> tests { 
        return testList.sequence; 
    }
    
    doc "Adds a test to be run"
    shared void addTest(String name, Anything() callable) {
        testList.append(TestUnit(name, callable));
    }
    
    doc "Adds a test listener to be notified about the execution of tests"
    shared void addTestListener(TestListener testListener) {
        testListenerList.append(testListener);
    }
    
    doc "Runs the [[tests]]"
    shared TestResult run() {
        TestRunner runner = this;
        TestResult result = TestResult(this);
        
        fire((TestListener l) => l.testRunStarted(runner));
        for(test in testList.sequence) {
            runTest(test);
        }
        fire((TestListener l) => l.testRunFinished(runner, result));
        
        return result;
    }
    
    void runTest(TestUnit test) {
        value startTime = process.milliseconds;
        test.state = running;
        fire((TestListener l) => l.testStarted(test));
        try {
            test.callable();
            test.state = success;
        } catch(AssertException e) {
            test.state = failure;
            test.exception = e;
        } catch(Exception e) {
            test.state = error;
            test.exception = e;
        } finally {
          value finishTime = process.milliseconds;
          test.elapsedTimeInMilis = finishTime - startTime;
        }
        fire((TestListener l) => l.testFinished(test));
    }
    
    void fire(void fireCallable(TestListener testListener)) {
        for(testListener in testListenerList.sequence) { 
            fireCallable(testListener);
        }
    }
    
}