/*
 * TestOps.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.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    [ObjectId("c2335b96-f944-44c5-97dc-abdb6dd08525")]
    internal static class TestOps
    {
        #region Private Constants
        private static readonly string FailConstraintPrefix = "fail.";

        private static readonly MatchMode NameMatchMode = StringOps.DefaultMatchMode;
        internal static readonly RegexOptions RegExOptions = StringOps.DefaultRegExOptions;

        //
        // NOTE: These file names are skipped by the interactive "#test" command
        //       prior to any other pattern matching.
        //
        private static readonly MatchMode skipFileNameMatchMode = MatchMode.Exact;

        private static readonly StringList skipFileNames = new StringList(new string[] {
            "constraints.eagle", "epilogue.eagle", "helper.eagle", "pkgIndex.eagle",
            "prologue.eagle"
        });

        private const string suiteFileName = "all.eagle";

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

        internal const int hostWorkItemDelay = 10000; /* in milliseconds */
        internal const int gcSleepTime = 1000;    /* in milliseconds */

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

        //
        // NOTE: *TUNING* This is purposely not marked as read-only.
        //
        internal static int DefaultRepeatCount = 1;
        #endregion

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

        #region Test Result Matching Methods
        public static ReturnCode Match(
            Interpreter interpreter,
            MatchMode mode,
            string text,
            string pattern,
            bool noCase,
            IComparer<string> comparer,
            RegexOptions regExOptions,
            bool debug,
            ref bool match,
            ref Result result
            )
        {
            ReturnCode code = ReturnCode.Error;

            if (interpreter != null)
            {
                if (mode == MatchMode.Expression)
                {
                    if ((interpreter.EvaluateExpression(pattern, ref result) == ReturnCode.Ok) &&
                        (Engine.ToBoolean(result, interpreter.CultureInfo, ref match,
                            ref result) == ReturnCode.Ok))
                    {
                        code = ReturnCode.Ok;
                    }
                }
                else
                {
                    if (StringOps.Match(
                            interpreter, mode, text, pattern, noCase, comparer,
                            regExOptions, ref match, ref result) == ReturnCode.Ok)
                    {
                        code = ReturnCode.Ok;
                    }
                }
            }
            else
            {
                result = "invalid interpreter";
            }

            if (debug)
            {
                TraceOps.DebugTrace(String.Format(
                    "Match: interpreter = {0}, mode = {1}, noCase = {2}, comparer = {3}, " +
                    "regExOptions = {4}, debug = {5}, pattern = {6}, text = {7}, code = {8}, " +
                    "match = {9}, result = {10}{11}", FormatOps.WrapOrNull(interpreter), mode,
                    noCase, FormatOps.WrapOrNull(comparer), regExOptions, debug,
                    FormatOps.WrapOrNull(ArrayOps.ToHexadecimalString(pattern)),
                    FormatOps.WrapOrNull(ArrayOps.ToHexadecimalString(text)), code, match,
                    FormatOps.WrapOrNull(true, true, result), Environment.NewLine),
                    typeof(TestOps).Name);
            }

            return code;
        }
        #endregion

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

        #region Test Statistics Support Methods
        private static bool IsRepeating(
            int repeatCount
            )
        {
            return repeatCount > 1;
        }

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

        public static string GetRepeatSuffix(
            int iterationCount,
            int repeatCount
            )
        {
            if (!IsRepeating(repeatCount))
                return String.Empty;

            return String.Format(" (iteration {0}/{1})", iterationCount, repeatCount);
        }

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

        public static ReturnCode RecordInformation(
            Interpreter interpreter,
            TestInformationType type,
            string name,
            string value,
            bool add,
            ref Result error
            )
        {
            int level = 0;

            return RecordInformation(interpreter, type, name, value, add, ref level, ref error);
        }

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

        public static ReturnCode RecordInformation(
            Interpreter interpreter,
            TestInformationType type,
            string name,
            string value,
            bool add,
            ref int level,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                switch (type)
                {
                    case TestInformationType.Constraints:
                        {
                            //
                            // NOTE: *WARNING* Empty test names are allowed,
                            //       please do not change this to "!String.IsNullOrEmpty".
                            //
                            if (name != null)
                            {
                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                {
                                    if (interpreter.TestConstraints == null)
                                        interpreter.TestConstraints = new StringList();

                                    interpreter.TestConstraints.Add(name);

                                    return ReturnCode.Ok;
                                }
                            }
                            else
                            {
                                error = "invalid test name";
                            }
                            break;
                        }
                    case TestInformationType.Counts:
                        {
                            //
                            // NOTE: *WARNING* Empty test names are allowed,
                            //       please do not change this to "!String.IsNullOrEmpty".
                            //
                            if (name != null)
                            {
                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                {
                                    IntDictionary testCounts = interpreter.TestCounts;

                                    if (testCounts == null)
                                    {
                                        testCounts = new IntDictionary();
                                        interpreter.TestCounts = testCounts;
                                    }

                                    int count;

                                    /* IGNORED */
                                    testCounts.TryGetValue(name, out count);

                                    testCounts[name] = ++count;
                                    return ReturnCode.Ok;
                                }
                            }
                            else
                            {
                                error = "invalid test name";
                            }
                            break;
                        }
                    case TestInformationType.SkippedNames:
                        {
                            //
                            // NOTE: *WARNING* Empty test names are allowed,
                            //       please do not change this to "!String.IsNullOrEmpty".
                            //
                            if (name != null)
                            {
                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                {
                                    if (interpreter.TestSkipped == null)
                                        interpreter.TestSkipped = new StringList();

                                    interpreter.TestSkipped.Add(name);

                                    return ReturnCode.Ok;
                                }
                            }
                            else
                            {
                                error = "invalid test name";
                            }
                            break;
                        }
                    case TestInformationType.FailedNames:
                        {
                            //
                            // NOTE: *WARNING* Empty test names are allowed,
                            //       please do not change this to "!String.IsNullOrEmpty".
                            //
                            if (name != null)
                            {
                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                {
                                    if (interpreter.TestFailures == null)
                                        interpreter.TestFailures = new StringList();

                                    interpreter.TestFailures.Add(name);

                                    return ReturnCode.Ok;
                                }
                            }
                            else
                            {
                                error = "invalid test name";
                            }
                            break;
                        }
                    case TestInformationType.SkipNames:
                        {
                            //
                            // NOTE: *WARNING* Empty test names are allowed,
                            //       please do not change this to "!String.IsNullOrEmpty".
                            //
                            if (name != null)
                            {
                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                {
                                    if (interpreter.TestSkip == null)
                                        interpreter.TestSkip = new StringList();

                                    interpreter.TestSkip.Add(name);

                                    return ReturnCode.Ok;
                                }
                            }
                            else
                            {
                                error = "invalid test name";
                            }
                            break;
                        }
                    case TestInformationType.MatchNames:
                        {
                            //
                            // NOTE: *WARNING* Empty test names are allowed,
                            //       please do not change this to "!String.IsNullOrEmpty".
                            //
                            if (name != null)
                            {
                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                {
                                    if (interpreter.TestMatch == null)
                                        interpreter.TestMatch = new StringList();

                                    interpreter.TestMatch.Add(name);

                                    return ReturnCode.Ok;
                                }
                            }
                            else
                            {
                                error = "invalid test name";
                            }
                            break;
                        }
                    case TestInformationType.Level:
                        {
                            if (add)
                                level = interpreter.EnterTestLevel();
                            else
                                level = interpreter.ExitTestLevel();

                            return ReturnCode.Ok;
                        }
                    case TestInformationType.Total:
                    case TestInformationType.Skipped:
                    case TestInformationType.Passed:
                    case TestInformationType.Failed:
                        {
                            lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                            {
                                if (interpreter.TestStatistics == null)
                                    interpreter.TestStatistics =
                                        new int[(int)TestInformationType.SizeOf];

                                Interlocked.Increment(
                                    ref interpreter.TestStatistics[(int)type]);

                                return ReturnCode.Ok;
                            }
                        }
                    default:
                        {
                            error = "unsupported test information type";
                            break;
                        }
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Test Constraint Checking Methods
        private static ReturnCode CheckCount(
            Interpreter interpreter,
            string name,
            ref int count,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                //
                // NOTE: *WARNING* Empty test names are allowed,
                //       please do not change this to "!String.IsNullOrEmpty".
                //
                if (name != null)
                {
                    lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                    {
                        IntDictionary testCounts = interpreter.TestCounts;

                        if (testCounts != null)
                            /* IGNORED */
                            testCounts.TryGetValue(name, out count);
                        else
                            count = 0;
                    }

                    return ReturnCode.Ok;
                }
                else
                {
                    error = "invalid test name";
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

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

        private static ReturnCode CheckNames(
            Interpreter interpreter,
            string name,
            ref bool matchName,
            ref bool skipName,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                //
                // NOTE: *WARNING* Empty test names are allowed,
                //       please do not change this to "!String.IsNullOrEmpty".
                //
                if (name != null)
                {
                    lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                    {
                        if ((StringOps.MatchAnyOrAll(
                                interpreter, NameMatchMode, name,
                                interpreter.TestMatch, false, false,
                                ref matchName, ref error) == ReturnCode.Ok) &&
                            (StringOps.MatchAnyOrAll(
                                interpreter, NameMatchMode, name,
                                interpreter.TestSkip, false, false,
                                ref skipName, ref error) == ReturnCode.Ok))
                        {
                            return ReturnCode.Ok;
                        }
                    }
                }
                else
                {
                    error = "invalid test name";
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode CheckConstraints(
            Interpreter interpreter,
            int testLevels, /* NOTE: Use this instead of member variable, no need for lock. */
            string name,
            string constraints,
            bool once,
            bool noStatistics,
            StringBuilder testData,
            ref bool skip,
            ref bool fail,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                //
                // NOTE: *WARNING* Empty test names are allowed,
                //       please do not change this to "!String.IsNullOrEmpty".
                //
                if (name != null)
                {
                    bool matchName = false;
                    bool skipName = false;

                    if (CheckNames(interpreter, name, ref matchName,
                            ref skipName, ref error) == ReturnCode.Ok)
                    {
                        //
                        // NOTE: How many times has this test been run before?
                        //
                        int count = 0;

                        if (!once ||
                            (CheckCount(interpreter, name,
                                ref count, ref error) == ReturnCode.Ok))
                        {
                            //
                            // NOTE: The list of constraints for this test.
                            //
                            StringList list = null;

                            //
                            // NOTE: If no constraints were supplied, skip list parsing.
                            //
                            if ((constraints == null) || Parser.SplitList(
                                    interpreter, constraints, 0, Length.Invalid,
                                    true, ref list, ref error) == ReturnCode.Ok)
                            {
                                //
                                // HACK: Check for an proces the special "fail.false"
                                //       and "fail.true" pseudo-constraints.  They are
                                //       never added to the real list of constraints
                                //       to match.  They are only used to control the
                                //       value of the fail parameter passed in by the
                                //       caller.
                                //
                                string failFalseConstraint =
                                    FailConstraintPrefix + false.ToString();

                                string failTrueConstraint =
                                    FailConstraintPrefix + true.ToString();

                                if (list != null)
                                {
                                    foreach (string element in list)
                                    {
                                        if (String.Equals(
                                                element, failFalseConstraint,
                                                StringComparison.OrdinalIgnoreCase))
                                        {
                                            fail = false;
                                        }
                                        else if (String.Equals(
                                                element, failTrueConstraint,
                                                StringComparison.OrdinalIgnoreCase))
                                        {
                                            fail = true;
                                        }
                                    }
                                }

                                StringList matchList = new StringList();

                                //
                                // NOTE: Is the test name explicitly set to be skipped?
                                //
                                if ((matchList.Count == 0) && skipName)
                                    matchList.Add("userSpecifiedSkip");

                                //
                                // NOTE: Is the test name explicitly set to be run?
                                //
                                if ((matchList.Count == 0) && !matchName)
                                    matchList.Add("userSpecifiedNonMatch");

                                //
                                // NOTE: Check if this test is only supposed to be run once and
                                //       then disallow it from running if it has already been
                                //       run once; however, only do this if the test name itself
                                //       has not already been disallowed.
                                //
                                if ((matchList.Count == 0) && once && (count > 0))
                                {
                                    //
                                    // HACK: Add bogus test constraint to indicate that this
                                    //       test was skipped because it has already been run
                                    //       (in addition to any other constraints that may
                                    //       not have been satisfied).
                                    //
                                    matchList.Add("once");
                                }

                                //
                                // NOTE: We do not need to bother checking constraints if the
                                //       name itself has already been disallowed.
                                //
                                if (matchList.Count == 0)
                                {
                                    lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                    {
                                        //
                                        // NOTE: Were there any constraints supplied and are
                                        //       there any present in the interpreter?
                                        //
                                        if ((list != null) &&
                                            (interpreter.TestConstraints != null))
                                        {
                                            foreach (string element in list)
                                            {
                                                //
                                                // HACK: Check for an skip over the special
                                                //       pseudo-constraints "fail.false" and
                                                //       "fail.true" test constraints.  They
                                                //       are processed specially above and
                                                //       are never actually added to the list
                                                //       of real test constraints.
                                                //
                                                if (String.Equals(
                                                        element, failFalseConstraint,
                                                        StringComparison.OrdinalIgnoreCase))
                                                {
                                                    continue;
                                                }
                                                else if (String.Equals(
                                                        element, failTrueConstraint,
                                                        StringComparison.OrdinalIgnoreCase))
                                                {
                                                    continue;
                                                }

                                                //
                                                // NOTE: Null and/or empty constraints are ignored.
                                                //
                                                if (!String.IsNullOrEmpty(element))
                                                {
                                                    //
                                                    // NOTE: If a constraint starts with a "!", it must
                                                    //       be false valued (i.e. not present) for the
                                                    //       test to run; otherwise, it must be true
                                                    //       valued (i.e. present) for the test to run.
                                                    //
                                                    if (element[0] == Characters.ExclamationMark)
                                                    {
                                                        if (interpreter.TestConstraints.Contains(
                                                                element.Substring(1)))
                                                        {
                                                            matchList.Add(element);
                                                        }
                                                    }
                                                    else if (!interpreter.TestConstraints.Contains(element))
                                                    {
                                                        matchList.Add(element);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                //
                                // NOTE: Is there any reason this test should be skipped?
                                //
                                if (matchList.Count > 0)
                                {
                                    if (testLevels == 1)
                                    {
                                        if (!noStatistics)
                                        {
                                            Interlocked.Increment(
                                                ref interpreter.TestStatistics[
                                                    (int)TestInformationType.Skipped]);

                                            interpreter.TestSkipped.Add(name);
                                        }

                                        if (testData != null)
                                        {
                                            testData.AppendFormat(
                                                "++++ {0} SKIPPED: {1}",
                                                name, matchList.ToString());
                                            testData.AppendLine();
                                        }
                                    }

                                    //
                                    // NOTE: Finally, we are NOT going to run this test.
                                    //
                                    skip = true;
                                }

                                return ReturnCode.Ok;
                            }
                        }
                    }
                }
                else
                {
                    error = "invalid test name";
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Test Suite Support Methods
        public static bool ShouldShowTestStart(
            Interpreter interpreter
            )
        {
            return (interpreter != null) && !FlagOps.HasFlags(
                interpreter.InterpreterFlags, InterpreterFlags.NoTestStart, true);
        }

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

        public static bool ShouldShowTestPass(
            Interpreter interpreter
            )
        {
            return (interpreter != null) && !FlagOps.HasFlags(
                interpreter.InterpreterFlags, InterpreterFlags.NoTestPass, true);
        }

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

        public static ReturnCodeDictionary GetReturnCodeMessages()
        {
            ReturnCodeDictionary result = new ReturnCodeDictionary();

            //
            // TODO: Localize these messages as well?
            //
            result.Add(ReturnCode.Invalid,
                "Test generated exception");

            result.Add(ReturnCode.Ok,
                "Test completed normally");

            result.Add(ReturnCode.Error,
                "Test generated error");

            result.Add(ReturnCode.Return,
                "Test generated return exception");

            result.Add(ReturnCode.Break,
                "Test generated break exception");

            result.Add(ReturnCode.Continue,
                "Test generated continue exception");

            return result;
        }

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

        #region Dead Code
#if DEAD_CODE
        public static void GetTestScriptLocation(
            Interpreter interpreter,
            Argument argument,
            bool strict,
            out IScriptLocation location
            )
        {
            //
            // NOTE: For now, always fallback to the argument as a good
            //       default location.
            //
            location = argument;

            ReturnCode code;
            Result error = null;

            code = GetTestScriptLocation(
                interpreter, argument, strict, ref location, ref error);

            if (code != ReturnCode.Ok)
                DebugOps.Complain(interpreter, code, error);
        }

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

        private static ReturnCode GetTestScriptLocation(
            Interpreter interpreter,
            Argument argument,
            bool strict,
            ref IScriptLocation location,
            ref Result error
            )
        {
            ReturnCode code;
            string fileName = null;
            int currentLine = Parser.UnknownLine;

            code = ScriptOps.GetLocation(
                interpreter, true, false, ref fileName,
                ref currentLine, ref error);

            if (code == ReturnCode.Ok)
            {
                if ((fileName == null) &&
                    (currentLine == Parser.UnknownLine))
                {
                    location = argument;
                }
                else
                {
                    location = ScriptLocation.Create(
                        interpreter, fileName, currentLine,
                        false);
                }
            }
            else if (!strict)
            {
                location = argument;
            }

            return strict ? code : ReturnCode.Ok;
        }
#endif
        #endregion

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

        public static string GetPath(
            Interpreter interpreter,
            bool plugins /* run plugin specific tests? */
            )
        {
            string result = null;

            if (interpreter != null)
                result = interpreter.TestPath;

            if (String.IsNullOrEmpty(result))
            {
                result = GlobalState.GetBasePath();

                if (plugins && !String.IsNullOrEmpty(result))
                    result = PathOps.CombinePath(
                        null, result, _Path.Plugins);
            }

            return result;
        }

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

        public static ReturnCode ShellMain(
            Interpreter interpreter,
            string pattern,
            EngineFlags engineFlags,
            SubstitutionFlags substitutionFlags,
            EventFlags eventFlags,
            ExpressionFlags expressionFlags,
            bool plugins,
            bool all,
            ref Result result,
            ref int errorLine
            )
        {
            ReturnCode code = ReturnCode.Ok;

            try
            {
                //
                // NOTE: Get the location of the test suite files.
                //
                string basePath = GetPath(interpreter, plugins);

                //
                // NOTE: Save the original pattern provided by the caller,
                //       for later reporting.
                //
                string savedPattern = pattern;

                //
                // NOTE: The default is to run the entire test suite.
                //
                if (pattern == null)
                    pattern = suiteFileName;

                //
                // NOTE: The list of file names to [potentially] evaluate.
                //
                StringList fileNames;

                //
                // NOTE: If the pattern contains directory information OR a
                //       complex glob pattern (i.e. [string match]), we cannot
                //       use the GetFiles method to actually match against the
                //       file names.
                //
                if (StringOps.HasStringMatchWildcard(pattern) ||
                    PathOps.HasDirectory(pattern))
                {
                    //
                    // NOTE: Get all the files from the file system underneath
                    //       the base directory.  We will perform file name
                    //       matching in the file evaluation loop below.
                    //
                    fileNames = new StringList(Directory.GetFiles(basePath,
                        Characters.Asterisk.ToString() + FileExtension.Script,
                        SearchOption.AllDirectories));
                }
                else
                {
                    //
                    // NOTE: Only get the files from the file system that match
                    //       the specified pattern.
                    //
                    fileNames = new StringList(Directory.GetFiles(basePath,
                        pattern, SearchOption.AllDirectories));

                    //
                    // NOTE: Disable secondary file name matching in the file
                    //       evaluation loop below.
                    //
                    if ((fileNames != null) && (fileNames.Count > 0))
                        pattern = null;
                }

                //
                // NOTE: If we found it, try to evaluate it; otherwise,
                //       show an error.
                //
                if ((fileNames != null) && (fileNames.Count > 0))
                {
                    //
                    // NOTE: Make sure the file names are always evaluated
                    //       in a well-defined order.
                    //
                    IntDictionary duplicates = null;

                    fileNames.Sort(new _Comparers.StringDictionaryComparer(
                        interpreter, true, null, false, false,
                        (interpreter != null) ? interpreter.CultureInfo : null,
                        ref duplicates));

                    //
                    // NOTE: Keep track of whether or not we actually manage
                    //       to evaluate any test suite file(s).
                    //
                    int count = 0;

                    //
                    // NOTE: This loop will evaluate zero or more files from
                    //       the list of file names that were found above.
                    //
                    foreach (string fileName in fileNames)
                    {
                        if (!String.IsNullOrEmpty(fileName) &&
                            fileName.EndsWith(FileExtension.Script, PathOps.ComparisonType))
                        {
                            //
                            // NOTE: Make sure the file name is not in the list of
                            //       file names that we are purposely avoiding.
                            //
                            bool match = false;

                            if ((StringOps.MatchAnyOrAll(
                                    interpreter, skipFileNameMatchMode,
                                    Path.GetFileName(fileName), skipFileNames,
                                    false, PathOps.NoCase, ref match,
                                    ref result) == ReturnCode.Ok) && !match)
                            {
                                //
                                // NOTE: Do we need to perform any secondary pattern
                                //       matching (i.e. in case we did not really
                                //       perform any when we originally queried the
                                //       file system above)?
                                //
                                if ((pattern == null) || StringOps.Match(
                                        interpreter, StringOps.DefaultMatchMode,
                                        fileName, pattern, true))
                                {
                                    //
                                    // NOTE: Set the current test file name so that it
                                    //       can be displayed by the test prologue.
                                    //
                                    code = interpreter.SetLibraryVariableValue(
                                        VariableFlags.None, Vars.TestFile, fileName,
                                        ref result);

                                    if (code != ReturnCode.Ok)
                                        break;

                                    //
                                    // NOTE: Evaluate the file using the specified flags
                                    //       and capture all the resulting information.
                                    //
                                    code = Engine.EvaluateFile(
                                        interpreter, fileName, engineFlags,
                                        substitutionFlags, eventFlags,
                                        expressionFlags, ref result, ref errorLine);

                                    //
                                    // NOTE: If an error was raised, bail out now.
                                    //
                                    if (code != ReturnCode.Ok)
                                        break;

                                    //
                                    // NOTE: Unset the current test file name, it is no
                                    //       longer needed.
                                    //
                                    code = interpreter.UnsetLibraryVariable(
                                        VariableFlags.NoComplain, Vars.TestFile,
                                        ref result);

                                    if (code != ReturnCode.Ok)
                                        break;

                                    //
                                    // NOTE: We evaluated a[nother] file successfully.
                                    //
                                    count++;

                                    //
                                    // NOTE: If we were only supposed to evaluate one file,
                                    //       bail out now.
                                    //
                                    if (!all)
                                        break;
                                }
                            }
                        }
                    }

                    //
                    // NOTE: If we did not evaluate any test files, return
                    //       failure to our caller.
                    //
                    if ((code == ReturnCode.Ok) && (count == 0))
                    {
                        result = String.Format(
                            "test suite file(s) matching \"{0}\" (match-glob) " +
                            "not found under path \"{1}\"",
                            !String.IsNullOrEmpty(savedPattern) ?
                                savedPattern : pattern, basePath);

                        code = ReturnCode.Error;
                    }
                }
                else
                {
                    result = String.Format(
                        "test suite file(s) matching \"{0}\" (file-glob) " +
                        "not found under path \"{1}\"",
                        !String.IsNullOrEmpty(savedPattern) ?
                            savedPattern : pattern, basePath);

                    code = ReturnCode.Error;
                }
            }
            catch (Exception e)
            {
                result = e;
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion

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

        #region GC Thread Test Methods
#if SHELL && INTERACTIVE_COMMANDS
        public static bool HasGcThread(
            Interpreter interpreter
            )
        {
            if (interpreter != null)
            {
                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                {
                    return (interpreter.TestGcThread != null);
                }
            }

            return false;
        }

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

        public static ReturnCode StartGcThread(
            Interpreter interpreter,
            ref Result result
            )
        {
            if (interpreter != null)
            {
                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                {
                    Thread thread = interpreter.TestGcThread;

                    if (thread == null)
                    {
                        try
                        {
                            thread = Engine.CreateThread(
                                interpreter, GcThreadStart, 0, false, false);

                            if (thread != null)
                            {
                                thread.Name = String.Format(
                                    "testGcThread: {0}", interpreter);

                                thread.Start(null);

                                interpreter.TestGcThread = thread;
                                result = "created and started garbage collection test thread";

                                return ReturnCode.Ok;
                            }
                            else
                            {
                                result = "could not create garbage collection test thread";
                            }
                        }
                        catch (Exception e)
                        {
                            result = String.Format(
                                "failed to start garbage collection test thread, " +
                                "caught exception \"{0}\"",
                                e);
                        }
                    }
                    else
                    {
                        result = "garbage collection test thread already started";
                    }
                }
            }
            else
            {
                result = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode InterruptGcThread(
            Interpreter interpreter,
            bool strict,
            ref Result result
            )
        {
            if (interpreter != null)
            {
                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                {
                    Thread thread = interpreter.TestGcThread;

                    if (thread != null)
                    {
                        try
                        {
                            if (thread.IsAlive)
                            {
                                thread.Interrupt();

                                if (!thread.Join(ThreadOps.DefaultJoinTimeout))
                                    thread.Abort(); /* BUGBUG: Leaks? */

                                interpreter.TestGcThread = null;
                                result = "interrupted garbage collection test thread";

                                return ReturnCode.Ok;
                            }
                            else
                            {
                                result = "garbage collection test thread is not alive";
                            }
                        }
                        catch (Exception e)
                        {
                            result = String.Format(
                                "failed to interrupt garbage collection test thread, " +
                                "caught exception \"{0}\"",
                                e);
                        }
                    }
                    else
                    {
                        result = "garbage collection test thread already stopped";

                        //
                        // NOTE: This is not really always an error due to nested
                        //       interactive loops.
                        //
                        if (!strict)
                            return ReturnCode.Ok;
                    }
                }
            }
            else
            {
                result = "invalid interpreter";
            }

            return ReturnCode.Error;
        }
#endif
        #endregion

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

        #region Thread Start Callbacks
#if SHELL && INTERACTIVE_COMMANDS
        private static void GcThreadStart(
            object obj /* NOT USED */
            )
        {
            try
            {
                while (true)
                {
                    //
                    // NOTE: Force a full garbage collection now.
                    //
                    ObjectOps.CollectGarbage(); /* throw */

                    //
                    // NOTE: Wait a while before trying again.
                    //
                    HostOps.Sleep(gcSleepTime);
                }
            }
            catch (ThreadInterruptedException)
            {
                // do nothing.
            }
        }

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

        public static void HostCancelThreadStart(
            object obj
            )
        {
            try
            {
                //
                // NOTE: Delay for a while so that we can see the true
                //       effect of the standard input channel being closed
                //       from underneath a synchronous read on it.
                //
                HostOps.Sleep((int)obj);

                //
                // NOTE: Grab the active interpreter.
                //
                Interpreter interpreter = Interpreter.GetAny();

                if (interpreter != null)
                {
                    //
                    // NOTE: Grab a copy of the reference to the interpreter
                    //       host.
                    //
                    IDebugHost debugHost = interpreter.Host;

                    //
                    // NOTE: Make sure the interpreter host is currently valid.
                    //
                    if (debugHost != null)
                    {
                        ReturnCode code;
                        Result result = null;

                        try
                        {
                            //
                            // NOTE: Mark the interpreter as exited and forcibly
                            //       close the standard input channel.
                            //
                            code = debugHost.Cancel(ref result);

                            //
                            // NOTE: Did we succeed?
                            //
                            if (code == ReturnCode.Ok)
                                result = "host cancel complete";
                        }
                        catch (Exception e)
                        {
                            result = e;
                            code = ReturnCode.Error;
                        }

                        //
                        // NOTE: Always show the result, whether we succeeded or not.
                        //
                        debugHost.WriteResultLine(code, result);
                    }
                }
            }
            catch (Exception e)
            {
                //
                // NOTE: Nothing we can do here except log the failure.
                //
                TraceOps.DebugTrace(String.Format(
                    "{0}: {1}{2}",
                    GlobalState.GetCurrentThreadId(),
                    e, Environment.NewLine),
                    typeof(TestOps).Name);
            }
        }

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

        public static void HostExitThreadStart(
            object obj
            )
        {
            try
            {
                //
                // NOTE: Delay for a while so that we can see the true
                //       effect of the standard input channel being closed
                //       from underneath a synchronous read on it.
                //
                HostOps.Sleep((int)obj);

                //
                // NOTE: Grab the active interpreter.
                //
                Interpreter interpreter = Interpreter.GetAny();

                if (interpreter != null)
                {
                    //
                    // NOTE: Grab a copy of the reference to the interpreter
                    //       host.
                    //
                    IDebugHost debugHost = interpreter.Host;

                    //
                    // NOTE: Make sure the interpreter host is currently valid.
                    //
                    if (debugHost != null)
                    {
                        ReturnCode code;
                        Result result = null;

                        try
                        {
                            //
                            // NOTE: Mark the interpreter as exited and forcibly
                            //       close the standard input channel.
                            //
                            code = debugHost.Exit(ref result);

                            //
                            // NOTE: Did we succeed?
                            //
                            if (code == ReturnCode.Ok)
                                result = "host exit complete";
                        }
                        catch (Exception e)
                        {
                            result = e;
                            code = ReturnCode.Error;
                        }

                        //
                        // NOTE: Always show the result, whether we succeeded
                        //       or not.
                        //
                        debugHost.WriteResultLine(code, result);
                    }
                }
            }
            catch (Exception e)
            {
                //
                // NOTE: Nothing we can do here except log the failure.
                //
                TraceOps.DebugTrace(String.Format(
                    "{0}: {1}{2}",
                    GlobalState.GetCurrentThreadId(),
                    e, Environment.NewLine),
                    typeof(TestOps).Name);
            }
        }
#endif
        #endregion
    }
}
