Esempio n. 1
0
        private static void BeginRun()
        {
            // Find all test classes.
            var entryAssembly = Assembly.GetEntryAssembly();

            if (entryAssembly == null)
            {
                return;
            }
            Type[] testClasses = entryAssembly.GetTypes().AsParallel().Where(x => x.GetCustomAttributes(typeof(TestAttribute), true).Length > 0).ToArray();

            // Find all test functions in these classes.
            var        tests      = new List <MethodInfo>();
            MethodInfo initMethod = null;

            foreach (Type classType in testClasses)
            {
                // Check if filtering by tag.
                var    t   = (TestAttribute)classType.GetCustomAttributes(typeof(TestAttribute), true).FirstOrDefault();
                string tag = (t?.Tag ?? "").ToLower();
                if (tag == "testinit")
                {
                    initMethod = classType.GetMethods().FirstOrDefault();
                    continue;
                }

                if (!string.IsNullOrEmpty(TestTag) && tag != TestTag)
                {
#if TEST_DEBUG
                    Log.Trace($"Skipping class {classType} because it doesn't match tag filter '{TestTag}'.", CustomMSource.TestRunner);
#endif
                    continue;
                }

                if (string.IsNullOrEmpty(TestTag) && (t?.TagOnly ?? false))
                {
#if TEST_DEBUG
                    Log.Trace($"Skipping class {classType} because it will only run with the tag '{t.Tag}'.", CustomMSource.TestRunner);
#endif
                    continue;
                }

                // Find all test functions in this class.
                tests.AddRange(classType.GetMethods().AsParallel().Where(x => x.GetCustomAttributes(typeof(TestAttribute), true).Length > 0).ToArray());
            }

            if (initMethod != null)
            {
                Engine.Log.Info("Executing test init method...", TestRunnerLogger.TestRunnerSrc);
                Type initClassType = initMethod.DeclaringType;
                Debug.Assert(initClassType != null);
                object initClassInstance = Activator.CreateInstance(initClassType);
                initMethod.Invoke(initClassInstance, new object[] { });
                Engine.Log.Info("Tests initialized.", TestRunnerLogger.TestRunnerSrc);
            }

            Type   currentClass         = null;
            object currentClassInstance = null;
            float  classTimer           = 0;

            var timeTracker = new Stopwatch();
            var failedTests = 0;

            foreach (MethodInfo func in tests)
            {
                // Create an instance of the test class.
                if (currentClass != func.DeclaringType)
                {
                    if (currentClass != null)
                    {
                        Engine.Log.Info($"Test class {currentClass} completed in {classTimer}ms!", TestRunnerLogger.TestRunnerSrc);
                    }
                    currentClass = func.DeclaringType;
                    if (currentClass == null)
                    {
                        throw new Exception($"Declaring type of function {func.Name} is missing.");
                    }
                    currentClassInstance = Activator.CreateInstance(currentClass);
                    classTimer           = 0;
                    Engine.Log.Info($"Running test class {currentClass}...", TestRunnerLogger.TestRunnerSrc);
                }

                // Run test.
                Engine.Log.Info($"  Running test {func.Name}...", TestRunnerLogger.TestRunnerSrc);
                timeTracker.Restart();
#if !THROW_EXCEPTIONS
                try
                {
#endif
                func.Invoke(currentClassInstance, new object[] { });

                // Check if errored in the loop.
                if (_loopException != null)
                {
                    var wrapped = new Exception("Exception in test engine loop.", _loopException);
                    _loopException = null;
                    throw wrapped;
                }
#if !THROW_EXCEPTIONS
            }
            catch (ImageDerivationException)
            {
                failedTests++;
            }
            catch (Exception ex)
            {
                failedTests++;
                if (ex.InnerException is ImageDerivationException)
                {
                    Engine.Log.Error($"{ex.InnerException.Message}", TestRunnerLogger.TestRunnerSrc);
                    continue;
                }

                Engine.Log.Error($" Test {func.Name} failed - {ex}", TestRunnerLogger.TestRunnerSrc);
                Debug.Assert(false);
            }
#endif

                Engine.Log.Info($"  Test {func.Name} completed in {timeTracker.ElapsedMilliseconds}ms!", TestRunnerLogger.TestRunnerSrc);
                classTimer += timeTracker.ElapsedMilliseconds;
            }

            Engine.Log.Info($"Test completed: {tests.Count - failedTests}/{tests.Count}!", TestRunnerLogger.TestRunnerSrc);

            // If not the master - then nothing else to do.
            if (TestRunId != RunnerId.ToString())
            {
                return;
            }

            var results = new List <string> {
                $"Master: Test completed: {tests.Count - failedTests}/{tests.Count}!"
            };
            int totalTests = tests.Count;
            var error      = false;

            // Wait for linked runners to exit.
            foreach (LinkedRunner linked in _linkedRunners)
            {
                Engine.Log.Info("----------------------------------------------------------------------", TestRunnerLogger.TestRunnerSrc);
                Engine.Log.Info($"Waiting for LR{linked.Id} - ({linked.Args})", TestRunnerLogger.TestRunnerSrc);
                int exitCode = linked.WaitForFinish(out string output, out string errorOutput);
                output      = output.Trim();
                errorOutput = errorOutput.Trim();

                // Try to find the test completed line.
                Match match  = _testCompletedRegex.Match(output);
                var   result = "";
                if (match.Success)
                {
                    try
                    {
                        int testsSuccess = int.Parse(match.Groups[1].Value);
                        int testsRun     = int.Parse(match.Groups[2].Value);

                        int failed = testsRun - testsSuccess;
                        failedTests += failed;
                        totalTests  += testsRun;

                        result = match.Groups[0].Value;
                    }
                    catch (Exception)
                    {
                        Engine.Log.Info($"Couldn't read tests completed from LR{linked.Id}.", TestRunnerLogger.TestRunnerSrc);
                    }
                }
                else
                {
                    result = "<Unknown>/<Unknown>";
                }

                var anyError = "";
                if (!string.IsNullOrEmpty(errorOutput))
                {
                    error    = true;
                    anyError = "ERR ";
                }

                results.Add($"LR{linked.Id} ({linked.Args}) {result} {anyError}{linked.TimeElapsed}ms");

                Engine.Log.Info($"LR{linked.Id} exited with code {exitCode}.", TestRunnerLogger.TestRunnerSrc);
                Engine.Log.Info($"Dumping log from LR{linked.Id}\n{output}", TestRunnerLogger.TestRunnerSrc);
                if (!string.IsNullOrEmpty(errorOutput))
                {
                    Engine.Log.Info($"[LR{linked.Id}] Error Output\n{errorOutput}", TestRunnerLogger.TestRunnerSrc);
                }
            }

            // Post final results.
            Engine.Log.Info($"Final test results: {totalTests - failedTests}/{totalTests} {(error ? "Errors found!" : "")}!", TestRunnerLogger.TestRunnerSrc);
            foreach (string r in results)
            {
                Engine.Log.Info($"     {r}", TestRunnerLogger.TestRunnerSrc);
            }

            if (error || failedTests > 0)
            {
                Environment.Exit(1);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Run tests.
        /// </summary>
        /// <param name="engineConfig">The default engine config. All configs in "otherConfigs" are modifications of this one.</param>
        /// <param name="args">The execution args passed to the Main. This is needed to coordinate linked runners.</param>
        /// <param name="otherConfigs">List of engine configurations to spawn runners with.</param>
        /// <param name="screenResultDb">Database of screenshot results to compare against when using VerifyImage</param>
        public static void RunTests(
            Configurator engineConfig,
            string[] args = null,
            Dictionary <string, Action <Configurator> > otherConfigs = null,
            Dictionary <string, byte[]> screenResultDb = null
            )
        {
            if (args == null)
            {
                args = new string[] { }
            }
            ;
            _otherConfigs   = otherConfigs ?? new Dictionary <string, Action <Configurator> >();
            _screenResultDb = screenResultDb ?? new Dictionary <string, byte[]>();

            // Check for test run id. This signifies whether the runner is linked.
            TestRunId = CommandLineParser.FindArgument(args, "testRunId=", out string testRunId) ? testRunId : RunnerId.ToString().ToLower();

            // Check if running only specific tests.
            if (CommandLineParser.FindArgument(args, "tag=", out string testTag))
            {
                TestTag = testTag;
            }

            // Check if a custom engine config is to be loaded. This check is a bit elaborate since the config params are merged with the linked params.
            string argsJoined   = string.Join(" ", args);
            string customConfig = (from possibleConfigs in _otherConfigs where argsJoined.Contains(possibleConfigs.Key) select possibleConfigs.Key).FirstOrDefault();

            CustomConfig = customConfig;

            string resultFolder = CommandLineParser.FindArgument(args, "folder=", out string folderPassed) ? folderPassed : $"{DateTime.Now:MM-dd-yyyy(HH.mm.ss)}";

            TestRunFolder = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "TestResults", resultFolder);

            RunnerReferenceImageFolder = Path.Join(TestRunFolder, RenderResultStorage, $"Runner {TestTagDisplay}");

            // Check if master runner.
            bool            linked = TestRunId != RunnerId.ToString();
            LoggingProvider log    = new TestRunnerLogger(linked, Path.Join(TestRunFolder, "Logs"));

            // Set the default engine settings for the test runner.
            Configurator config = engineConfig;

            config.DebugMode   = true;
            config.LoopFactory = TestLoop;
            config.Logger      = log;

            if (customConfig != null && _otherConfigs.ContainsKey(customConfig) && _otherConfigs[customConfig] != null)
            {
                CustomConfig = customConfig;
                Engine.Log.Info($"Loading custom engine config - {customConfig}...", TestRunnerLogger.TestRunnerSrc);
                _otherConfigs[customConfig](config);
            }

            // Perform light setup.
            Engine.LightSetup(config);

            // Run linked runners (if the master).
            if (linked)
            {
                log.Info($"I am a linked runner with arguments {string.Join(" ", args)}", TestRunnerLogger.TestRunnerSrc);
            }
            else
            {
                // Spawn linked runners
                if (!NoLinkedRunners)
                {
                    // Spawn a runner for each runtime config.
                    foreach ((string arg, Action <Configurator> _) in _otherConfigs)
                    {
                        _linkedRunners.Add(new LinkedRunner(arg));
                    }
                }
            }

            // Check if running tests without an engine instance - this shouldn't be used with a tag because most tests except an instance.
            if (CommandLineParser.FindArgument(args, "testOnly", out string _))
            {
                Task tests = Task.Run(BeginRun);
                while (!tests.IsCompleted)
                {
                    TestLoopUpdate();
                }
                Engine.Quit();
                return;
            }

            // Perform engine setup.
            Engine.Setup(config);

            if (Engine.Renderer == null)
            {
                return;
            }

            // Move the camera center in a way that its center is 0,0
            Engine.Renderer.Camera.Position += new Vector3(Engine.Renderer.Camera.WorldToScreen(Vector2.Zero), 0);
            Task.Run(() =>
            {
                // Wait for the engine to start.
                while (Engine.Status != EngineStatus.Running)
                {
                }

                // If crashed.
                if (Engine.Status == EngineStatus.Stopped)
                {
                    return;
                }

                // Name the thread.
                if (Engine.Host?.NamedThreads ?? false)
                {
                    Thread.CurrentThread.Name ??= "Runner Thread";
                }

                BeginRun();

                Engine.Quit();

                // Wait for the engine to stop.
                while (Engine.Status == EngineStatus.Running)
                {
                }
            });
            Engine.Run();
        }