/*
 * Test2.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Private;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Commands
{
    [ObjectId("a382feba-f2ff-4596-b4c7-81dfa628733b")]
    [CommandFlags(CommandFlags.Safe | CommandFlags.NonStandard
#if NATIVE && WINDOWS
        //
        // NOTE: Uses native code indirectly for profiling with the "-time"
        //       option (on Windows only).
        //
        | CommandFlags.NativeCode
#endif
        )]
    [ObjectGroup("test")]
    internal sealed class Test2 : Core
    {
        public Test2(
            ICommandData commandData
            )
            : base(commandData)
        {
            // do nothing.
        }

        #region IExecute Members
        public override ReturnCode Execute(
            Interpreter interpreter,
            IClientData clientData,
            ArgumentList arguments,
            ref Result result
            )
        {
            ReturnCode code = ReturnCode.Ok;

            if (interpreter != null)
            {
                if (arguments != null)
                {
                    if (arguments.Count >= 3)
                    {
                        ///////////////////////////////////////////////////////////////////////////////////////////////
                        //
                        // test name description
                        // ?-constraints keywordList|expression?
                        // ?-setup setupScript?
                        // ?-body testScript?
                        // ?-cleanup cleanupScript?
                        // ?-repeatCount integer?
                        // ?-result expectedAnswer?
                        // ?-output expectedOutput?
                        // ?-errorOutput expectedError?
                        // ?-returnCodes codeList?
                        // ?-exitCode exitCode?
                        // ?-match mode?
                        // ?-noCase boolean?
                        // ?-regExOptions regExOptions?
                        // ?-exitCode code?
                        // ?-debug boolean?
                        // ?-trace boolean?
                        // ?-time boolean?
                        // ?-noCancel boolean?
                        // ?-noData boolean?
                        // ?-noExit boolean?
                        // ?-noHalt boolean?
                        // ?-noStatistics boolean?
                        // ?-noStart boolean?
                        // ?-once boolean?
                        // ?-isolationLevel isolationLevel?
                        // ?-autoPath pathList?
                        // ?-argv argList?
                        // ?-text script?
                        // ?-libraryPath path?
                        // ?-packagePath path?
                        //
                        ///////////////////////////////////////////////////////////////////////////////////////////////

                        //
                        // COMPAT: Tcl, tcltest requires all test options to have values.
                        //
                        OptionDictionary options = new OptionDictionary(
                            new Option[] {
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-constraints", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-setup", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-body", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-cleanup", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-result", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-output", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-errorOutput", null),
                            new Option(null, OptionFlags.MustHaveReturnCodeListValue, Index.Invalid, Index.Invalid, "-returnCodes", null),
                            new Option(typeof(ExitCode), OptionFlags.MustHaveEnumValue, Index.Invalid, Index.Invalid, "-exitCode", null),
                            new Option(null, OptionFlags.MustHaveMatchModeValue, Index.Invalid, Index.Invalid, "-match",
                                new Variant(StringOps.DefaultResultMatchMode)),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noCase", null),
                            new Option(typeof(RegexOptions), OptionFlags.NoCase | OptionFlags.MustHaveEnumValue, Index.Invalid, Index.Invalid, "-regExOptions",
                                new Variant(TestOps.RegExOptions)),
                            new Option(null, OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-debug", null),
                            new Option(null, OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-trace", null),
                            new Option(null, OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-time", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveIntegerValue, Index.Invalid, Index.Invalid, "-repeatCount", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noCancel", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noData", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noEvent", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noExit", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noHalt", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noPass", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noStatistics", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-noStart", null),
                            new Option(null, OptionFlags.MustHaveBooleanValue, Index.Invalid, Index.Invalid, "-once", null),
                            new Option(null, OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-text", null),
                            new Option(null, OptionFlags.MustHaveListValue, Index.Invalid, Index.Invalid, "-argv", null),
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-libraryPath", null),
#if ISOLATED_INTERPRETERS
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveValue, Index.Invalid, Index.Invalid, "-packagePath", null),
#else
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveValue | OptionFlags.Unsupported, Index.Invalid, Index.Invalid,
                                "-packagePath", null),
#endif
                            new Option(null, OptionFlags.NoCase | OptionFlags.MustHaveListValue, Index.Invalid, Index.Invalid, "-autoPath", null),
                            new Option(typeof(IsolationLevel), OptionFlags.NoCase | OptionFlags.MustHaveEnumValue, Index.Invalid, Index.Invalid, "-isolationLevel",
                                new Variant(IsolationLevel.Default)),
                            new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, Option.EndOfOptions, null)
                        });

                        int argumentIndex = Index.Invalid;

                        if (arguments.Count > 3)
                            code = interpreter.GetOptions(options, arguments, 0, 3, Index.Invalid, true, ref argumentIndex, ref result);
                        else
                            code = ReturnCode.Ok;

                        if (code == ReturnCode.Ok)
                        {
                            //
                            // NOTE: Support the new syntax only.
                            //
                            if (argumentIndex == Index.Invalid)
                            {
                                string name = arguments[1];

#if DEBUGGER
                                if (DebuggerOps.CanHitBreakpoints(EngineFlags.None, interpreter))
                                    code = interpreter.CheckBreakpoints(
                                        code, BreakpointType.Test, name, null, null,
                                        this, null, clientData, arguments, ref result);

                                if (code == ReturnCode.Ok)
#endif
                                {
                                    string description = arguments[2];

                                    Variant value = null;
                                    int repeatCount = TestOps.DefaultRepeatCount;

                                    if (options.IsPresent("-repeatCount", ref value))
                                        repeatCount = (int)value.Value;

                                    int valueIndex = Index.Invalid;
                                    string constraints = null;

                                    if (options.IsPresent("-constraints", ref value))
                                        constraints = value.ToString();

                                    string setup = null;
                                    IScriptLocation setupLocation = null;

                                    if (options.IsPresent("-setup", ref value, ref valueIndex))
                                    {
                                        setup = value.ToString();
                                        setupLocation = arguments[valueIndex + 1];
                                    }

                                    string body = null;
                                    IScriptLocation bodyLocation = null;

                                    if (options.IsPresent("-body", ref value, ref valueIndex))
                                    {
                                        body = value.ToString();
                                        bodyLocation = arguments[valueIndex + 1];
                                    }

                                    string cleanup = null;
                                    IScriptLocation cleanupLocation = null;

                                    if (options.IsPresent("-cleanup", ref value, ref valueIndex))
                                    {
                                        cleanup = value.ToString();
                                        cleanupLocation = arguments[valueIndex + 1];
                                    }

                                    Result expectedResult = null;

                                    if (options.IsPresent("-result", ref value))
                                        expectedResult = value.ToString();

                                    string expectedOutput = null;

                                    if (options.IsPresent("-output", ref value))
                                        expectedOutput = value.ToString();

                                    string expectedError = null;

                                    if (options.IsPresent("-errorOutput", ref value))
                                        expectedError = value.ToString();

                                    ReturnCodeList returnCodes = new ReturnCodeList(new ReturnCode[] {
                                        ReturnCode.Ok, ReturnCode.Return
                                    });

                                    if (options.IsPresent("-returnCodes", ref value))
                                        returnCodes = (ReturnCodeList)value.Value;

                                    ExitCode? exitCode = null;

                                    if (options.IsPresent("-exitCode", ref value))
                                        exitCode = (ExitCode)value.Value;

                                    MatchMode mode = StringOps.DefaultResultMatchMode;

                                    if (options.IsPresent("-match", ref value))
                                        mode = (MatchMode)value.Value;

                                    bool noCase = false;

                                    if (options.IsPresent("-noCase", true, ref value))
                                        noCase = (bool)value.Value;

                                    RegexOptions regExOptions = TestOps.RegExOptions;

                                    if (options.IsPresent("-regExOptions", true, ref value))
                                        regExOptions = (RegexOptions)value.Value;

                                    bool debug = false;

                                    if (options.IsPresent("-debug", ref value))
                                        debug = (bool)value.Value;

                                    bool trace = false;

                                    if (options.IsPresent("-trace", ref value))
                                        trace = (bool)value.Value;

                                    bool time = false;

                                    if (options.IsPresent("-time", ref value))
                                        time = (bool)value.Value;

                                    bool noCancel = false;

                                    if (options.IsPresent("-noCancel", true, ref value))
                                        noCancel = (bool)value.Value;

                                    bool noData = false;

                                    if (options.IsPresent("-noData", true, ref value))
                                        noData = (bool)value.Value;

                                    bool noEvent = false;

                                    if (options.IsPresent("-noEvent", true, ref value))
                                        noEvent = (bool)value.Value;

                                    bool noExit = false;

                                    if (options.IsPresent("-noExit", true, ref value))
                                        noExit = (bool)value.Value;

                                    bool noHalt = false;

                                    if (options.IsPresent("-noHalt", true, ref value))
                                        noHalt = (bool)value.Value;

                                    bool noStatistics = false;

                                    if (options.IsPresent("-noStatistics", true, ref value))
                                        noStatistics = (bool)value.Value;

                                    bool noStart = false;

                                    if (options.IsPresent("-noStart", true, ref value))
                                        noStart = (bool)value.Value;

                                    bool noPass = false;

                                    if (options.IsPresent("-noPass", true, ref value))
                                        noPass = (bool)value.Value;

                                    IsolationLevel isolationLevel = IsolationLevel.Default;

                                    if (options.IsPresent("-isolationLevel", true, ref value))
                                        isolationLevel = (IsolationLevel)value.Value;

                                    bool once = false;

                                    if (options.IsPresent("-once", ref value))
                                        once = (bool)value.Value;

                                    StringList argv = null;

                                    if (options.IsPresent("-argv", ref value))
                                        argv = (StringList)value.Value;

                                    string text = null;

                                    if (options.IsPresent("-text", ref value))
                                        text = value.ToString();

                                    string libraryPath = null;

                                    if (options.IsPresent("-libraryPath", ref value))
                                        libraryPath = value.ToString();

#if ISOLATED_INTERPRETERS
                                    string packagePath = null;

                                    if (options.IsPresent("-packagePath", ref value))
                                        packagePath = value.ToString();
#endif

                                    StringList autoPathList = null;

                                    if (options.IsPresent("-autoPath", ref value))
                                        autoPathList = (StringList)value.Value;

                                    ///////////////////////////////////////////////////////////////////////////////////////////////

                                    int testLevels = interpreter.EnterTestLevel();

                                    try
                                    {
                                        if (!noStatistics && (testLevels == 1))
                                        {
                                            Interlocked.Increment(
                                                ref interpreter.TestStatistics[
                                                    (int)TestInformationType.Total]);
                                        }

                                        //
                                        // NOTE: Create a place to put all the output of the this command.
                                        //
                                        StringBuilder testData = null;

                                        if (!noData)
                                            testData = StringOps.NewStringBuilder();

                                        //
                                        // NOTE: Are we going to skip this test?
                                        //
                                        bool skip = false;
                                        bool fail = true;

                                        code = TestOps.CheckConstraints(
                                            interpreter, testLevels, name, constraints,
                                            once, noStatistics, testData, ref skip,
                                            ref fail, ref result);

                                        //
                                        // NOTE: Track the fact that we handled this test.
                                        //
                                        if ((code == ReturnCode.Ok) && !skip)
                                            code = TestOps.RecordInformation(
                                                interpreter, TestInformationType.Counts, name,
                                                null, true, ref result);

                                        //
                                        // NOTE: Check test constraints to see if we should run the test.
                                        //
                                        if ((code == ReturnCode.Ok) && !skip)
                                        {
#if ISOLATED_INTERPRETERS
                                            AppDomain testAppDomain = null;
                                            InterpreterHelper testInterpreterHelper = null;
#endif
                                            Interpreter testInterpreter = null;
                                            Result isolationResult = null;

                                            try
                                            {
                                                switch (isolationLevel)
                                                {
                                                    case IsolationLevel.None:
                                                        {
                                                            testInterpreter = interpreter;
                                                            break;
                                                        }
                                                    case IsolationLevel.Interpreter:
                                                        {
                                                            CreateFlags testCreateFlags = CreateFlags.TestUse |
                                                                interpreter.FilterCreateFlags(true, true, true);

                                                            //
                                                            // HACK: For now, just clone the script flags of the
                                                            //       "parent" interpreter.
                                                            //
                                                            ScriptFlags testScriptFlags = interpreter.ScriptFlags;

                                                            testInterpreter = Interpreter.Create(
                                                                argv, testCreateFlags, testScriptFlags, text,
                                                                libraryPath, autoPathList, ref isolationResult);

                                                            break;
                                                        }
                                                    case IsolationLevel.AppDomain:
                                                    case IsolationLevel.AppDomainOrInterpreter:
                                                        {
#if ISOLATED_INTERPRETERS
                                                            if (AppDomainOps.CreateForTest(
                                                                    interpreter, name, packagePath, clientData,
                                                                    ref testAppDomain, ref isolationResult) == ReturnCode.Ok)
                                                            {
                                                                CreateFlags testCreateFlags = CreateFlags.TestUse |
                                                                    interpreter.FilterCreateFlags(true, true, true);

                                                                //
                                                                // HACK: For now, just clone the script flags of the
                                                                //       "parent" interpreter.
                                                                //
                                                                ScriptFlags testScriptFlags = interpreter.ScriptFlags;

                                                                testInterpreterHelper = InterpreterHelper.Create(
                                                                    testAppDomain, argv, testCreateFlags, testScriptFlags,
                                                                    text, libraryPath, autoPathList, ref isolationResult);

                                                                if (testInterpreterHelper != null)
                                                                    testInterpreter = testInterpreterHelper.Interpreter;
                                                            }
#else
                                                            if (isolationLevel == IsolationLevel.AppDomainOrInterpreter)
                                                            {
                                                                CreateFlags testCreateFlags = CreateFlags.TestUse |
                                                                    interpreter.FilterCreateFlags(true, true, true);

                                                                //
                                                                // HACK: For now, just clone the script flags of the
                                                                //       "parent" interpreter.
                                                                //
                                                                ScriptFlags testScriptFlags = interpreter.ScriptFlags;

                                                                testInterpreter = Interpreter.Create(
                                                                    argv, testCreateFlags, testScriptFlags, text,
                                                                    libraryPath, autoPathList, ref isolationResult);
                                                            }
                                                            else
                                                            {
                                                                isolationResult = "not implemented";
                                                            }
#endif
                                                            break;
                                                        }
                                                    case IsolationLevel.Process:
                                                    case IsolationLevel.Session:
                                                    case IsolationLevel.Machine:
                                                        {
                                                            isolationResult = "not implemented";
                                                            break;
                                                        }
                                                    default:
                                                        {
                                                            isolationResult = String.Format(
                                                                "unsupported isolation level \"{0}\"",
                                                                isolationLevel);

                                                            break;
                                                        }
                                                }

                                                //
                                                // NOTE: Did we succeed at creating the test interpreter,
                                                //       if necessary?
                                                //
                                                if (testInterpreter != null)
                                                {
                                                    //
                                                    // NOTE: Less than zero means use the default number of test
                                                    //       iterations.
                                                    //
                                                    if (repeatCount < 0)
                                                        repeatCount = TestOps.DefaultRepeatCount;

                                                    int iterationCount = 0;

                                                    while (iterationCount < repeatCount)
                                                    {
                                                        //
                                                        // NOTE: These variables are used to measure performance if the
                                                        //       -time option is enabled.
                                                        //
                                                        double microseconds = 0;
                                                        long startCount = 0;
                                                        long stopCount = 0;

                                                        //
                                                        // NOTE: Used to hold the result of the -setup script, if any.
                                                        //
                                                        ReturnCode setupCode;
                                                        Result setupResult = null;

                                                        //
                                                        // BUGFIX: Mark the test as starting now, before the setup script is
                                                        //         evaluated.
                                                        //
                                                        if (!noStart && (testData != null) &&
                                                            TestOps.ShouldShowTestStart(interpreter))
                                                        {
                                                            testData.AppendFormat(
                                                                "---- {0} start{1}",
                                                                name, TestOps.GetRepeatSuffix(
                                                                    iterationCount + 1, repeatCount));
                                                            testData.AppendLine();
                                                        }

                                                        //
                                                        // NOTE: Run the setup script, if any.
                                                        //
                                                        if (setup != null)
                                                        {
                                                            if (trace)
                                                                TraceOps.DebugTrace(String.Format(
                                                                    "{0}: Execute: starting test {1} setup...{2}",
                                                                    GlobalState.GetCurrentThreadId(),
                                                                    FormatOps.WrapOrNull(name), Environment.NewLine),
                                                                    typeof(Test2).Name);

                                                            if (time)
                                                                ProfileOps.Start(ref startCount, ref microseconds);

                                                            int savedPreviousLevels = interpreter.BeginNestedExecution();

                                                            try
                                                            {
                                                                ICallFrame frame = interpreter.NewTrackingCallFrame(
                                                                    StringList.MakeList(this.Name, "setup", name),
                                                                    CallFrameFlags.Test);

                                                                interpreter.PushAutomaticCallFrame(frame);

                                                                try
                                                                {
                                                                    setupCode = testInterpreter.EvaluateScript(
                                                                        setup, setupLocation, ref setupResult);

                                                                    if (setupCode == ReturnCode.Error)
                                                                        /* IGNORED */
                                                                        testInterpreter.CopyErrorInformation(
                                                                            VariableFlags.None, ref setupResult);
                                                                }
                                                                finally
                                                                {
                                                                    if (noCancel)
                                                                        /* IGNORED */
                                                                        Engine.ResetCancel(testInterpreter, true);

                                                                    if (noHalt)
                                                                        /* IGNORED */
                                                                        Engine.ResetHalt(testInterpreter, true);

                                                                    if (noEvent)
                                                                        /* IGNORED */
                                                                        testInterpreter.ClearEvents();

                                                                    //
                                                                    // NOTE: If requested, prevent the interactive loop from
                                                                    //       actually exiting and reset the exit code to be
                                                                    //       "success".
                                                                    //
                                                                    if (noExit && testInterpreter.Exit)
                                                                    {
                                                                        testInterpreter.ExitCode = ResultOps.SuccessExitCode();
                                                                        testInterpreter.Exit = false;
                                                                    }

                                                                    //
                                                                    // NOTE: Pop the original call frame that we pushed above
                                                                    //       and any intervening scope call frames that may be
                                                                    //       leftover (i.e. they were not explicitly closed).
                                                                    //
                                                                    /* IGNORED */
                                                                    interpreter.PopCallFrame(CallFrameFlags.Scope, true);
                                                                }
                                                            }
                                                            catch (Exception e)
                                                            {
                                                                setupResult = e;
                                                                setupCode = ReturnCode.Error;
                                                            }
                                                            finally
                                                            {
                                                                interpreter.EndNestedExecution(savedPreviousLevels);

                                                                if (time)
                                                                {
                                                                    ProfileOps.Stop(
                                                                        startCount, ref stopCount, ref microseconds);

                                                                    if (testData != null)
                                                                    {
                                                                        testData.AppendFormat(
                                                                            "---- {0} setup completed in {1}",
                                                                            name, FormatOps.Performance(microseconds));

                                                                        testData.AppendLine();
                                                                    }
                                                                }
                                                            }

                                                            if (trace)
                                                                TraceOps.DebugTrace(String.Format(
                                                                    "{0}: Execute: test {1} setup complete{2}",
                                                                    GlobalState.GetCurrentThreadId(),
                                                                    FormatOps.WrapOrNull(name), Environment.NewLine),
                                                                    typeof(Test2).Name);
                                                        }
                                                        else
                                                        {
                                                            setupCode = ReturnCode.Ok;
                                                        }

                                                        //
                                                        // NOTE: Used to hold the result of the -body script, if any.
                                                        //
                                                        ReturnCode bodyCode = ReturnCode.Ok;
                                                        Result bodyResult = null;
                                                        ExitCode bodyExitCode = ResultOps.SuccessExitCode();

                                                        //
                                                        // NOTE: Only run the test body if the setup is successful.
                                                        //
                                                        if ((setupCode == ReturnCode.Ok) && (body != null))
                                                        {
                                                            if (trace)
                                                                TraceOps.DebugTrace(String.Format(
                                                                    "{0}: Execute: starting test {1} body...{2}",
                                                                    GlobalState.GetCurrentThreadId(),
                                                                    FormatOps.WrapOrNull(name), Environment.NewLine),
                                                                    typeof(Test2).Name);

                                                            if (time)
                                                                ProfileOps.Start(ref startCount, ref microseconds);

                                                            int savedPreviousLevels = interpreter.BeginNestedExecution();

                                                            try
                                                            {
                                                                ICallFrame frame = interpreter.NewTrackingCallFrame(
                                                                    StringList.MakeList(this.Name, "body", name),
                                                                    CallFrameFlags.Test);

                                                                interpreter.PushAutomaticCallFrame(frame);

                                                                try
                                                                {
                                                                    bodyCode = testInterpreter.EvaluateScript(
                                                                        body, bodyLocation, ref bodyResult);

                                                                    bodyExitCode = testInterpreter.ExitCode;

                                                                    if (bodyCode == ReturnCode.Error)
                                                                        /* IGNORED */
                                                                        testInterpreter.CopyErrorInformation(
                                                                            VariableFlags.None, ref bodyResult);
                                                                }
                                                                finally
                                                                {
                                                                    if (noCancel)
                                                                        /* IGNORED */
                                                                        Engine.ResetCancel(testInterpreter, true);

                                                                    if (noHalt)
                                                                        /* IGNORED */
                                                                        Engine.ResetHalt(testInterpreter, true);

                                                                    if (noEvent)
                                                                        /* IGNORED */
                                                                        testInterpreter.ClearEvents();

                                                                    //
                                                                    // NOTE: If requested, prevent the interactive loop from
                                                                    //       actually exiting and reset the exit code to be
                                                                    //       "success".
                                                                    //
                                                                    if (noExit && testInterpreter.Exit)
                                                                    {
                                                                        testInterpreter.ExitCode = ResultOps.SuccessExitCode();
                                                                        testInterpreter.Exit = false;
                                                                    }

                                                                    //
                                                                    // NOTE: Pop the original call frame that we pushed above
                                                                    //       and any intervening scope call frames that may be
                                                                    //       leftover (i.e. they were not explicitly closed).
                                                                    //
                                                                    /* IGNORED */
                                                                    interpreter.PopCallFrame(CallFrameFlags.Scope, true);
                                                                }
                                                            }
                                                            catch (Exception e)
                                                            {
                                                                bodyResult = e;
                                                                bodyCode = ReturnCode.Error;
                                                            }
                                                            finally
                                                            {
                                                                interpreter.EndNestedExecution(savedPreviousLevels);

                                                                if (time)
                                                                {
                                                                    ProfileOps.Stop(
                                                                        startCount, ref stopCount, ref microseconds);

                                                                    if (testData != null)
                                                                    {
                                                                        testData.AppendFormat(
                                                                            "---- {0} body completed in {1}",
                                                                            name, FormatOps.Performance(microseconds));

                                                                        testData.AppendLine();
                                                                    }
                                                                }
                                                            }

                                                            if (trace)
                                                                TraceOps.DebugTrace(String.Format(
                                                                    "{0}: Execute: test {1} body complete{2}",
                                                                    GlobalState.GetCurrentThreadId(),
                                                                    FormatOps.WrapOrNull(name), Environment.NewLine),
                                                                    typeof(Test2).Name);
                                                        }

                                                        //
                                                        // NOTE: Used to hold the result of the -cleanup script, if any.
                                                        //
                                                        ReturnCode cleanupCode;
                                                        Result cleanupResult = null;

                                                        //
                                                        // NOTE: Always run the cleanup script, if any.
                                                        //
                                                        if (cleanup != null)
                                                        {
                                                            if (trace)
                                                                TraceOps.DebugTrace(String.Format(
                                                                    "{0}: Execute: starting test {1} cleanup...{2}",
                                                                    GlobalState.GetCurrentThreadId(),
                                                                    FormatOps.WrapOrNull(name), Environment.NewLine),
                                                                    typeof(Test2).Name);

                                                            if (time)
                                                                ProfileOps.Start(ref startCount, ref microseconds);

                                                            int savedPreviousLevels = interpreter.BeginNestedExecution();

                                                            try
                                                            {
                                                                ICallFrame frame = interpreter.NewTrackingCallFrame(
                                                                    StringList.MakeList(this.Name, "cleanup", name),
                                                                    CallFrameFlags.Test);

                                                                interpreter.PushAutomaticCallFrame(frame);

                                                                try
                                                                {
                                                                    cleanupCode = testInterpreter.EvaluateScript(
                                                                        cleanup, cleanupLocation, ref cleanupResult);

                                                                    if (cleanupCode == ReturnCode.Error)
                                                                        /* IGNORED */
                                                                        testInterpreter.CopyErrorInformation(
                                                                            VariableFlags.None, ref cleanupResult);
                                                                }
                                                                finally
                                                                {
                                                                    if (noCancel)
                                                                        /* IGNORED */
                                                                        Engine.ResetCancel(testInterpreter, true);

                                                                    if (noHalt)
                                                                        /* IGNORED */
                                                                        Engine.ResetHalt(testInterpreter, true);

                                                                    if (noEvent)
                                                                        /* IGNORED */
                                                                        testInterpreter.ClearEvents();

                                                                    //
                                                                    // NOTE: If requested, prevent the interactive loop from
                                                                    //       actually exiting and reset the exit code to be
                                                                    //       "success".
                                                                    //
                                                                    if (noExit && testInterpreter.Exit)
                                                                    {
                                                                        testInterpreter.ExitCode = ResultOps.SuccessExitCode();
                                                                        testInterpreter.Exit = false;
                                                                    }

                                                                    //
                                                                    // NOTE: Pop the original call frame that we pushed above
                                                                    //       and any intervening scope call frames that may be
                                                                    //       leftover (i.e. they were not explicitly closed).
                                                                    //
                                                                    /* IGNORED */
                                                                    interpreter.PopCallFrame(CallFrameFlags.Scope, true);
                                                                }
                                                            }
                                                            catch (Exception e)
                                                            {
                                                                cleanupResult = e;
                                                                cleanupCode = ReturnCode.Error;
                                                            }
                                                            finally
                                                            {
                                                                interpreter.EndNestedExecution(savedPreviousLevels);

                                                                if (time)
                                                                {
                                                                    ProfileOps.Stop(
                                                                        startCount, ref stopCount, ref microseconds);

                                                                    if (testData != null)
                                                                    {
                                                                        testData.AppendFormat(
                                                                            "---- {0} cleanup completed in {1}",
                                                                            name, FormatOps.Performance(microseconds));

                                                                        testData.AppendLine();
                                                                    }
                                                                }
                                                            }

                                                            if (trace)
                                                                TraceOps.DebugTrace(String.Format(
                                                                    "{0}: Execute: test {1} cleanup complete{2}",
                                                                    GlobalState.GetCurrentThreadId(),
                                                                    FormatOps.WrapOrNull(name), Environment.NewLine),
                                                                    typeof(Test2).Name);
                                                        }
                                                        else
                                                        {
                                                            cleanupCode = ReturnCode.Ok;
                                                        }

                                                        //
                                                        // NOTE: Did the setup script fail?
                                                        //
                                                        bool setupFailure = (setupCode != ReturnCode.Ok);

                                                        //
                                                        // NOTE: Did the cleanup script fail?
                                                        //
                                                        bool cleanupFailure = (cleanupCode != ReturnCode.Ok);

                                                        //
                                                        // NOTE: Did we fail to match the return code?
                                                        //
                                                        bool codeFailure = !setupFailure && !returnCodes.Contains(bodyCode);

                                                        //
                                                        // NOTE: Did we match exit code, if provided?
                                                        //
                                                        bool exitFailure = (exitCode != null) && (bodyExitCode != exitCode);

                                                        //
                                                        // NOTE: The virtual output from the StdOut channel.
                                                        //
                                                        StringBuilder outputData = null;

                                                        //
                                                        // NOTE: The virtual output from the StdErr channel.
                                                        //
                                                        StringBuilder errorData = null;

                                                        //
                                                        // NOTE: Did we match the StdOut output, if provided?  If not,
                                                        //       was there some sort of error?
                                                        //
                                                        bool outputFailure = false;
                                                        ReturnCode outputCode = ReturnCode.Ok;
                                                        Result outputResult = null;

                                                        //
                                                        // NOTE: Did we match the StdErr output, if provided?  If not,
                                                        //       was there some sort of error?
                                                        //
                                                        bool errorFailure = false;
                                                        ReturnCode errorCode = ReturnCode.Ok;
                                                        Result errorResult = null;

                                                        if (!codeFailure)
                                                        {
                                                            if (expectedOutput != null)
                                                            {
                                                                outputData = testInterpreter.GetChannelVirtualOutput(Channel.StdOut);

                                                                if (outputData != null)
                                                                {
                                                                    //
                                                                    // NOTE: Did we match the StdOut output, if provided?
                                                                    //
                                                                    outputCode = TestOps.Match(
                                                                        testInterpreter, mode, outputData.ToString(),
                                                                        expectedOutput, noCase, null, regExOptions,
                                                                        debug, ref outputFailure, ref outputResult);

                                                                    if (outputCode == ReturnCode.Ok)
                                                                        outputFailure = !outputFailure;
                                                                    else
                                                                        outputFailure = true;
                                                                }
                                                                else
                                                                {
                                                                    //
                                                                    // NOTE: There was no StdOut output and we expected some.
                                                                    //       This is a failure.
                                                                    //
                                                                    outputFailure = true;
                                                                }
                                                            }

                                                            if (expectedError != null)
                                                            {
                                                                errorData = testInterpreter.GetChannelVirtualOutput(Channel.StdErr);

                                                                if (errorData != null)
                                                                {
                                                                    //
                                                                    // NOTE: Did we match the StdErr output, if provided?
                                                                    //
                                                                    errorCode = TestOps.Match(
                                                                        testInterpreter, mode, errorData.ToString(),
                                                                        expectedError, noCase, null, regExOptions,
                                                                        debug, ref errorFailure, ref errorResult);

                                                                    if (errorCode == ReturnCode.Ok)
                                                                        errorFailure = !errorFailure;
                                                                    else
                                                                        errorFailure = true;
                                                                }
                                                                else
                                                                {
                                                                    //
                                                                    // NOTE: There was no StdErr output and we expected some.
                                                                    //       This is a failure.
                                                                    //
                                                                    errorFailure = true;
                                                                }
                                                            }
                                                        }

                                                        //
                                                        // NOTE: Does the actual result match the expected result?
                                                        //
                                                        bool scriptFailure = false;
                                                        ReturnCode scriptCode = ReturnCode.Ok;
                                                        Result scriptResult = null;

                                                        if (!setupFailure && !codeFailure)
                                                        {
                                                            if (expectedResult != null)
                                                            {
                                                                scriptCode = TestOps.Match(
                                                                    testInterpreter, mode, bodyResult,
                                                                    expectedResult, noCase, null, regExOptions,
                                                                    debug, ref scriptFailure, ref scriptResult);

                                                                if (scriptCode == ReturnCode.Ok)
                                                                    scriptFailure = !scriptFailure;
                                                                else
                                                                    scriptFailure = true;
                                                            }
                                                        }

                                                        //
                                                        // NOTE: If any of the important things failed, the test fails.
                                                        //
                                                        if (!(setupFailure || cleanupFailure || outputFailure ||
                                                                errorFailure || codeFailure || scriptFailure ||
                                                                exitFailure))
                                                        {
                                                            //
                                                            // PASS: Test ran with no errors and the results match
                                                            //       what we expected.
                                                            //
                                                            if (!noStatistics && (testLevels == 1))
                                                            {
                                                                Interlocked.Increment(
                                                                    ref interpreter.TestStatistics[
                                                                        (int)TestInformationType.Passed]);
                                                            }

                                                            if (!noPass && (testData != null) &&
                                                                TestOps.ShouldShowTestPass(interpreter))
                                                            {
                                                                testData.AppendFormat(
                                                                    "++++ {0} PASSED{1}",
                                                                    name, TestOps.GetRepeatSuffix(
                                                                        iterationCount + 1, repeatCount));
                                                                testData.AppendLine();
                                                            }
                                                        }
                                                        else
                                                        {
                                                            //
                                                            // FAIL: Test ran with errors or the result does not match
                                                            //       what we expected.
                                                            //
                                                            if (!noStatistics && (testLevels == 1))
                                                            {
                                                                if (fail)
                                                                {
                                                                    Interlocked.Increment(
                                                                        ref interpreter.TestStatistics[
                                                                            (int)TestInformationType.Failed]);
                                                                }
                                                                else
                                                                {
                                                                    Interlocked.Decrement(
                                                                        ref interpreter.TestStatistics[
                                                                            (int)TestInformationType.Total]);
                                                                }
                                                            }

                                                            if (testData != null)
                                                            {
                                                                testData.AppendLine();
                                                                testData.AppendFormat(
                                                                    "==== {0} {1} FAILED{2}",
                                                                    name, description.Trim(),
                                                                    TestOps.GetRepeatSuffix(
                                                                        iterationCount + 1, repeatCount));
                                                                testData.AppendLine();

                                                                if (body != null)
                                                                {
                                                                    testData.AppendLine("==== Contents of test case:");
                                                                    testData.AppendLine(body);
                                                                }

                                                                if (setupFailure)
                                                                {
                                                                    testData.AppendLine("---- Test setup failed:");
                                                                    testData.AppendLine(setupResult);

                                                                    if ((setupResult != null) &&
                                                                        (setupResult.ErrorInfo != null))
                                                                    {
                                                                        testData.Append("---- errorInfo(setup): ");
                                                                        testData.AppendLine(setupResult.ErrorInfo);
                                                                        testData.Append("---- errorCode(setup): ");
                                                                        testData.AppendLine(setupResult.ErrorCode);
                                                                    }
                                                                }

                                                                if (scriptFailure)
                                                                {
                                                                    if (scriptCode == ReturnCode.Ok)
                                                                    {
                                                                        testData.AppendLine("---- Result was:");
                                                                        testData.AppendLine(bodyResult);
                                                                        testData.AppendFormat(
                                                                            "---- Result should have been ({0} matching):",
                                                                            mode);
                                                                        testData.AppendLine();
                                                                        testData.AppendLine(expectedResult);
                                                                    }
                                                                    else
                                                                    {
                                                                        testData.Append("---- Error testing result: ");
                                                                        testData.AppendLine(scriptResult);
                                                                    }
                                                                }

                                                                if (codeFailure)
                                                                {
                                                                    ReturnCodeDictionary returnCodeMessages =
                                                                        interpreter.TestReturnCodeMessages;

                                                                    string codeMessage;

                                                                    if ((returnCodeMessages == null) ||
                                                                        (!returnCodeMessages.TryGetValue(
                                                                            bodyCode, out codeMessage) &&
                                                                        !returnCodeMessages.TryGetValue(
                                                                            ReturnCode.Invalid, out codeMessage)))
                                                                    {
                                                                        codeMessage = "Unknown";
                                                                    }

                                                                    testData.AppendFormat(
                                                                        "---- {0}; Return code was: {1}",
                                                                        codeMessage, bodyCode);
                                                                    testData.AppendLine();
                                                                    testData.Append("---- Return code should have been one of: ");
                                                                    testData.AppendLine(returnCodes.ToString());

                                                                    if ((bodyResult != null) &&
                                                                        (bodyResult.ErrorInfo != null) &&
                                                                        !returnCodes.Contains(ReturnCode.Error))
                                                                    {
                                                                        testData.Append("---- errorInfo: ");
                                                                        testData.AppendLine(bodyResult.ErrorInfo);
                                                                        testData.Append("---- errorCode: ");
                                                                        testData.AppendLine(bodyResult.ErrorCode);
                                                                    }
                                                                }

                                                                if (outputFailure)
                                                                {
                                                                    if (outputCode == ReturnCode.Ok)
                                                                    {
                                                                        testData.AppendLine("---- Output was:");
                                                                        testData.Append(outputData);
                                                                        testData.AppendLine();
                                                                        testData.AppendFormat(
                                                                            "---- Output should have been ({0} matching):",
                                                                            mode);
                                                                        testData.AppendLine();
                                                                        testData.AppendLine(expectedOutput);
                                                                    }
                                                                    else
                                                                    {
                                                                        testData.Append("---- Error testing output: ");
                                                                        testData.AppendLine(outputResult);
                                                                    }
                                                                }

                                                                if (errorFailure)
                                                                {
                                                                    if (errorCode == ReturnCode.Ok)
                                                                    {
                                                                        testData.AppendLine("---- Error output was:");
                                                                        testData.Append(errorData);
                                                                        testData.AppendLine();
                                                                        testData.AppendFormat(
                                                                            "---- Error output should have been ({0} matching):",
                                                                            mode);
                                                                        testData.AppendLine();
                                                                        testData.AppendLine(expectedError);
                                                                    }
                                                                    else
                                                                    {
                                                                        testData.Append("---- Error testing errorOutput: ");
                                                                        testData.AppendLine(errorResult);
                                                                    }
                                                                }

                                                                if (cleanupFailure)
                                                                {
                                                                    testData.AppendLine("---- Test cleanup failed:");
                                                                    testData.AppendLine(cleanupResult);

                                                                    if ((cleanupResult != null) &&
                                                                        (cleanupResult.ErrorInfo != null))
                                                                    {
                                                                        testData.Append("---- errorInfo(cleanup): ");
                                                                        testData.AppendLine(cleanupResult.ErrorInfo);
                                                                        testData.Append("---- errorCode(cleanup): ");
                                                                        testData.AppendLine(cleanupResult.ErrorCode);
                                                                    }
                                                                }

                                                                if (exitFailure)
                                                                {
                                                                    testData.AppendFormat(
                                                                        "---- Exit code was: {1}",
                                                                        bodyExitCode);
                                                                    testData.AppendLine();

                                                                    if (exitCode != null)
                                                                    {
                                                                        testData.Append("---- Exit code should have been: ");
                                                                        testData.AppendLine(exitCode.ToString());
                                                                    }
                                                                }

                                                                testData.AppendFormat(
                                                                    "==== {0} FAILED{1}",
                                                                    name, TestOps.GetRepeatSuffix(
                                                                        iterationCount + 1, repeatCount));
                                                                testData.AppendLine();
                                                            }

                                                            //
                                                            // NOTE: Keep track of each test that fails.
                                                            //
                                                            if (!noStatistics && (testLevels == 1))
                                                                interpreter.TestFailures.Add(name);
                                                        }

                                                        iterationCount++;
                                                    }
                                                }
                                                else
                                                {
                                                    //
                                                    // FAIL: Test did not run because we failed to setup the
                                                    //       isolated test interpreter.
                                                    //
                                                    if (!noStatistics && (testLevels == 1))
                                                    {
                                                        if (fail)
                                                        {
                                                            Interlocked.Increment(
                                                                ref interpreter.TestStatistics[
                                                                    (int)TestInformationType.Failed]);
                                                        }
                                                        else
                                                        {
                                                            Interlocked.Decrement(
                                                                ref interpreter.TestStatistics[
                                                                    (int)TestInformationType.Total]);
                                                        }
                                                    }

                                                    if (testData != null)
                                                    {
                                                        testData.AppendLine();
                                                        testData.AppendFormat(
                                                            "==== {0} {1} FAILED",
                                                            name, description.Trim());
                                                        testData.AppendLine();

                                                        testData.AppendFormat(
                                                            "---- Test isolation failed ({0} level):",
                                                            isolationLevel);
                                                        testData.AppendLine();
                                                        testData.AppendLine(isolationResult);

                                                        if ((isolationResult != null) &&
                                                            (isolationResult.ErrorInfo != null))
                                                        {
                                                            testData.Append("---- errorInfo(isolation): ");
                                                            testData.AppendLine(isolationResult.ErrorInfo);
                                                            testData.Append("---- errorCode(isolation): ");
                                                            testData.AppendLine(isolationResult.ErrorCode);
                                                        }
                                                    }

                                                    //
                                                    // NOTE: Keep track of each test that fails.
                                                    //
                                                    if (!noStatistics && (testLevels == 1))
                                                        interpreter.TestFailures.Add(name);
                                                }
                                            }
                                            finally
                                            {
                                                //
                                                // NOTE: If a new interpreter was used, dispose it now.
                                                //
                                                if ((testInterpreter != null) &&
                                                    !Object.ReferenceEquals(testInterpreter, interpreter))
                                                {
                                                    ReturnCode disposeCode;
                                                    Result disposeError = null;

                                                    disposeCode = ObjectOps.TryDispose(
                                                        testInterpreter, ref disposeError);

                                                    if (disposeCode != ReturnCode.Ok)
                                                        DebugOps.Complain(interpreter, disposeCode, disposeError);
                                                }

#if ISOLATED_INTERPRETERS
                                                if (testInterpreterHelper != null)
                                                {
                                                    ReturnCode disposeCode;
                                                    Result disposeError = null;

                                                    disposeCode = ObjectOps.TryDispose(
                                                        testInterpreterHelper, ref disposeError);

                                                    if (disposeCode != ReturnCode.Ok)
                                                        DebugOps.Complain(interpreter, disposeCode, disposeError);
                                                }

                                                if (testAppDomain != null)
                                                {
                                                    ReturnCode unloadCode;
                                                    Result unloadError = null;

                                                    unloadCode = AppDomainOps.Unload(
                                                        testAppDomain, clientData, ref unloadError);

                                                    if (unloadCode != ReturnCode.Ok)
                                                        DebugOps.Complain(interpreter, unloadCode, unloadError);
                                                }
#endif
                                            }
                                        }

                                        //
                                        // NOTE: Did the above code succeed?
                                        //
                                        if (code == ReturnCode.Ok)
                                        {
                                            //
                                            // NOTE: The result is the complete output produced by the
                                            //       entire test.
                                            //
                                            if (testData != null)
                                                result = testData;
                                            else
                                                result = String.Empty;
                                        }
                                    }
                                    catch (Exception e)
                                    {
                                        Engine.SetExceptionErrorCode(interpreter, e);

                                        result = e;
                                        code = ReturnCode.Error;
                                    }
                                    finally
                                    {
                                        interpreter.ExitTestLevel();
                                    }
                                }
                            }
                            else
                            {
                                result = String.Format(
                                    "wrong # args: should be \"{0} name description ?options?\"",
                                    this.Name);

                                code = ReturnCode.Error;
                            }
                        }
                    }
                    else
                    {
                        result = String.Format(
                            "wrong # args: should be \"{0} name description ?options?\"",
                            this.Name);

                        code = ReturnCode.Error;
                    }
                }
                else
                {
                    result = "invalid argument list";
                    code = ReturnCode.Error;
                }
            }
            else
            {
                result = "invalid interpreter";
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion
    }
}
