///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   UnitTest.cs
//
//  Note:
//

using System;
using System.Collections;
using System.Threading;

namespace Microsoft.Singularity.UnitTest
{
    public sealed class UnitTest
    {
        public enum Result
        {
            Passed = 0,
            Failed = 1
        }

        public delegate void TestDelegate();

        internal sealed class TestCommand
        {
            private string /*!*/       description;
            private TestDelegate /*!*/ testDelegate;

            internal TestCommand(string /*!*/       description,
                                 TestDelegate /*!*/ testDelegate)
            {
                this.description  = description;
                this.testDelegate = testDelegate;
            }

            internal string Description { get { return description; } }
            internal void Execute()     { testDelegate(); }
        }

        private static ArrayList! tests    = new ArrayList();
        private static object    classLock = new object();

        private static int runThreadId;
        private static volatile int  otherThreadExceptions  = 0;
        private static volatile bool expectAssertionFailure = false;

        public static void Add(string /*!*/       description,
                               TestDelegate /*!*/ test)
        {
            tests.Add(new TestCommand(description, test));
        }

        public static void Clear()
        {
            tests.Clear();
        }

        public static void ExpectNextAssertionFails()
        {
            lock (classLock) {
                UnitTest.expectAssertionFailure = true;
            }
        }

        public static void ExpectNextAssertionPasses()
        {
            lock (classLock) {
                UnitTest.expectAssertionFailure = false;
            }
        }

        public static Result Run(bool stopOnFailure)
        {
            runThreadId = GetCurrentThreadId();

            int failures = 0;

            foreach (TestCommand! tc in tests) {
                Result r = Result.Passed;
                try {
                    Console.Write("Running test {0}...", tc.Description);
                    lock (classLock) {
                        UnitTest.otherThreadExceptions = 0;
                    }
                    tc.Execute();
                    lock (classLock) {
                        if (UnitTest.otherThreadExceptions == 0) {
                            Console.WriteLine("Passed.");
                        }
                        else {
                            Console.WriteLine("Failed.");
                            r = Result.Failed;
                        }
                    }
                }
                catch (FailedAssertionException fae) {
                    Console.WriteLine("Failed.");
                    Console.WriteLine(fae);
                    r = Result.Failed;
                }
                catch (Exception e) {
                    Console.WriteLine("Failed: Unexpected exception caught.");
                    Console.WriteLine(e);
                    r = Result.Failed;
                }
                if (r == Result.Failed) {
                    failures++;
                    if (stopOnFailure) {
                        break;
                    }
                }
            }
            return (failures == 0) ? Result.Passed : Result.Failed;
        }

        private static int GetCurrentThreadId()
        {
#if SINGULARITY
            return Thread.CurrentThread.GetThreadId();
#else
            return AppDomain.GetCurrentThreadId();
#endif
        }

        internal static void Throw(FailedAssertionException /*!*/ e)
        {
            lock (classLock) {

                if (UnitTest.expectAssertionFailure == true) {
                    UnitTest.expectAssertionFailure = false;
                    return;
                }

                int threadId = GetCurrentThreadId();
                if (threadId != runThreadId) {
                    UnitTest.otherThreadExceptions++;
                    Console.WriteLine("Tester Failure on thread {0}: {1}",
                                      threadId, e);
                }
            }
            throw e;
        }
    }
}
