/// <summary>
        /// Pulls tests from a queue and runs them.
        /// </summary>
        /// <param name="queue"> The queue to retrieve tests from. </param>
        private void RunTests(BlockingQueue <TestExecutionState> queue)
        {
            // Loop as long as there are tests in the queue.
            while (true)
            {
                // Retrieve a test from the queue.
                TestExecutionState executionState;
                if (queue.TryDequeue(out executionState) == false)
                {
                    break;
                }

                // Restore the ScriptEngine state.
                var scriptEngine = new ScriptEngine();
                foreach (var propertyNameAndValue in includeProperties)
                {
                    scriptEngine.Global[propertyNameAndValue.Key] = propertyNameAndValue.Value;
                }

                // Set strict mode.
                scriptEngine.ForceStrictMode = executionState.RunInStrictMode;

                // Run the test.
                RunTest(script =>
                {
                    try
                    {
                        scriptEngine.Execute(script);
                        return(null);
                    }
                    catch (Exception ex)
                    {
                        return(ex);
                    }
                }, executionState);
            }
        }
        /// <summary>
        /// Pulls tests from a queue and runs them inside a sandbox.
        /// </summary>
        /// <param name="queue"> The queue to retrieve tests from. </param>
        private void RunTestsInSandbox(BlockingQueue <TestExecutionState> queue)
        {
            // Set the DeserializationEnvironment so any JavaScriptExceptions can be serialized
            // accross the AppDomain boundary.
            ScriptEngine.DeserializationEnvironment = new ScriptEngine();

            int       testCounter = 0;
            AppDomain appDomain   = null;

            // Loop as long as there are tests in the queue.
            while (true)
            {
                if (testCounter == 0)
                {
                    // Unload the old AppDomain.
                    if (appDomain != null)
                    {
                        AppDomain.Unload(appDomain);
                    }

                    // Create an AppDomain with internet sandbox permissions.
                    var e = new System.Security.Policy.Evidence();
                    e.AddHostEvidence(new System.Security.Policy.Zone(System.Security.SecurityZone.Internet));
                    appDomain = AppDomain.CreateDomain(
                        "Jurassic sandbox",
                        null,
                        new AppDomainSetup()
                    {
                        ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
                    },
                        System.Security.SecurityManager.GetStandardSandbox(e));
                }

                // Retrieve a test from the queue.
                TestExecutionState executionState;
                if (queue.TryDequeue(out executionState) == false)
                {
                    break;
                }

                // Restore the ScriptEngine state.
                var scriptEngine = new ScriptEngine();
                foreach (var propertyNameAndValue in includeProperties)
                {
                    scriptEngine.Global[propertyNameAndValue.Key] = propertyNameAndValue.Value;
                }

                // Set strict mode.
                scriptEngine.ForceStrictMode = executionState.RunInStrictMode;

                // Create a new ScriptEngine instance inside the AppDomain.
                var engineHandle = Activator.CreateInstanceFrom(
                    appDomain,                                          // The AppDomain to create the type within.
                    typeof(ScriptEngineWrapper).Assembly.CodeBase,      // The file name of the assembly containing the type.
                    typeof(ScriptEngineWrapper).FullName,               // The name of the type to create.
                    false,                                              // Ignore case: no.
                    (System.Reflection.BindingFlags) 0,                 // Binding flags.
                    null,                                               // Binder.
                    new object[] { scriptEngine },                      // Parameters passed to the constructor.
                    null,                                               // Culture.
                    null);                                              // Activation attributes.
                var scriptEngineProxy = (ScriptEngineWrapper)engineHandle.Unwrap();
                if (System.Runtime.Remoting.RemotingServices.IsTransparentProxy(scriptEngineProxy) == false)
                {
                    throw new InvalidOperationException("Script engine not operating within the sandbox.");
                }

                // Run the test.
                RunTest(scriptEngineProxy.Execute, executionState);

                // Reset the test count every 200 tests so the AppDomain gets recreated.
                // This saves us from an unavoidable memory leak in low privilege mode.
                testCounter++;
                if (testCounter == 200)
                {
                    testCounter = 0;
                }
            }
        }
        /// <summary>
        /// Starts executing the test suite.
        /// </summary>
        public void Start()
        {
            // Create a ScriptEngine and freeze its state.
            SaveScriptEngineSnapshot();

            // Create a queue to hold the tests.
            var queue = new BlockingQueue <TestExecutionState>(100);

            // Create a thread per processor.
            var threads = new List <Thread>();

            for (int i = 0; i < GetThreadCount(); i++)
            {
                var thread = new Thread(ThreadStart);
                thread.Start(queue);
                threads.Add(thread);
            }

            for (int i = 0; i < this.zipFile.Count; i++)
            {
                var zipEntry = this.zipFile[i];
                if (zipEntry.IsFile && zipEntry.Name.EndsWith(".js"))
                {
                    // This is a test file.

                    // Read out the contents (assume UTF-8).
                    string fileContents;
                    using (var entryStream = this.zipFile.GetInputStream(zipEntry))
                        using (var reader = new StreamReader(entryStream))
                        {
                            fileContents = reader.ReadToEnd();
                        }

                    // Parse out the test metadata.
                    var test = new Test(this, zipEntry.Name, fileContents);

                    // Check if the test should be skipped.
                    if (this.skippedTestNames.Contains(Path.GetFileNameWithoutExtension(zipEntry.Name)))
                    {
                        this.skippedTestCount++;
                        TestFinished(this, new TestEventArgs(TestRunStatus.Skipped, test, false));
                        continue;
                    }
                    if (this.IncludedTests.Count > 0 && this.IncludedTests.Contains(Path.GetFileNameWithoutExtension(zipEntry.Name)) == false)
                    {
                        this.skippedTestCount++;
                        TestFinished(this, new TestEventArgs(TestRunStatus.Skipped, test, false));
                        continue;
                    }


                    // Queue the test.
                    if (test.RunInNonStrictMode)
                    {
                        queue.Enqueue(new TestExecutionState(test, runInStrictMode: false));
                    }
                    if (test.RunInStrictMode)
                    {
                        queue.Enqueue(new TestExecutionState(test, runInStrictMode: true));
                    }
                }
            }

            // Signal the threads that no more tests will be provided.
            queue.Close();

            // Wait for all threads to exit.
            foreach (var thread in threads)
            {
                thread.Join();
            }
        }