public void LoadTest(Type testType = null, Action onCompletion = null, bool isDynamicLoad = false) { if (CurrentTest?.Parent != null) { testContentContainer.Remove(CurrentTest.Parent); CurrentTest.Dispose(); } var lastTest = CurrentTest; CurrentTest = null; if (testType == null && TestTypes.Count > 0) { testType = TestTypes[0]; } config.Set(TestBrowserSetting.LastTest, testType?.Name ?? string.Empty); if (testType == null) { return; } var newTest = (TestScene)Activator.CreateInstance(testType); Debug.Assert(newTest != null); const string dynamic_prefix = "dynamic"; // if we are a dynamically compiled type (via DynamicClassCompiler) we should update the dropdown accordingly. if (isDynamicLoad) { newTest.DynamicCompilationOriginal = lastTest?.DynamicCompilationOriginal ?? lastTest ?? newTest; toolbar.AddAssembly($"{dynamic_prefix} ({testType.Name})", testType.Assembly); } else { TestTypes.RemoveAll(t => { Debug.Assert(t.Assembly.FullName != null); return(t.Assembly.FullName.Contains(dynamic_prefix)); }); newTest.DynamicCompilationOriginal = newTest; } Assembly.Value = testType.Assembly; CurrentTest = newTest; CurrentTest.OnLoadComplete += _ => Schedule(() => finishLoad(newTest, onCompletion)); updateButtons(); resetRecording(); testContentContainer.Add(new ErrorCatchingDelayedLoadWrapper(CurrentTest, isDynamicLoad) { OnCaughtError = compileFailed }); }
/// <summary> /// Blocks execution until a provided <see cref="TestScene"/> runs to completion. /// </summary> /// <param name="test">The <see cref="TestScene"/> to run.</param> public void RunTestBlocking(TestScene test) { Trace.Assert(host != null, $"Ensure this runner has been loaded before calling {nameof(RunTestBlocking)}"); bool completed = false; ExceptionDispatchInfo exception = null; void complete() { // We want to remove the TestScene from the hierarchy on completion as under nUnit, it may have operations run on it from a different thread. // This is because nUnit will reuse the same class multiple times, running a different [Test] method each time, while the GameHost // is run from its own asynchronous thread. RemoveInternal(test); completed = true; } Schedule(() => { AddInternal(test); Console.WriteLine($@"{(int)Time.Current}: Running {test} visual test cases..."); // Nunit will run the tests in the TestScene with the same TestScene instance so the TestScene // needs to be removed before the host is exited, otherwise it will end up disposed test.RunAllSteps(() => { Scheduler.AddDelayed(complete, time_between_tests); }, e => { if (e is DependencyInjectionException die) { exception = die.DispatchInfo; } else { exception = ExceptionDispatchInfo.Capture(e); } complete(); }); }); while (!completed && host.ExecutionState == ExecutionState.Running) { Thread.Sleep(10); } exception?.Throw(); }
private void onChange(object sender, FileSystemEventArgs e) { lock (compileLock) { if (checkpointObject == null || isCompiling) { return; } var checkpointName = checkpointObject.GetType().Name; var reqTypes = checkpointObject.RequiredTypes.Select(t => removeGenerics(t.Name)).ToList(); // add ourselves as a required type. reqTypes.Add(removeGenerics(checkpointName)); // if we are a TestCase, add the class we are testing automatically. reqTypes.Add(TestScene.RemovePrefix(removeGenerics(checkpointName))); if (!reqTypes.Contains(Path.GetFileNameWithoutExtension(e.Name))) { return; } if (!reqTypes.SequenceEqual(requiredTypeNames)) { requiredTypeNames = reqTypes; requiredFiles.Clear(); foreach (var d in validDirectories) { requiredFiles.AddRange(Directory .EnumerateFiles(d, "*.cs", SearchOption.AllDirectories) .Where(fw => requiredTypeNames.Contains(Path.GetFileNameWithoutExtension(fw)))); } } lastTouchedFile = e.FullPath; isCompiling = true; Task.Run(recompile) .ContinueWith(_ => isCompiling = false); } }
public void RunTestBlocking(TestScene test) { Trace.Assert(host != null, $"Ensure this runner has been loaded before calling {nameof(RunTestBlocking)}"); bool completed = false; ExceptionDispatchInfo exception = null; void complete() { // We want to remove the TestScene from the hierarchy on completion as under nUnit, it may have operations run on it from a different thread. // This is because nUnit will reuse the same class multiple times, running a different [Test] method each time, while the GameHost // is run from its own asynchronous thread. RemoveInternal(test); completed = true; } Schedule(() => { AddInternal(test); Logger.Log($@"💨 Class: {test.GetType().ReadableName()}"); Logger.Log($@"🔶 Test: {TestContext.CurrentContext.Test.Name}"); // Nunit will run the tests in the TestScene with the same TestScene instance so the TestScene // needs to be removed before the host is exited, otherwise it will end up disposed test.RunAllSteps(() => { Scheduler.AddDelayed(complete, time_between_tests); }, e => { exception = ExceptionDispatchInfo.Capture(e); complete(); }); }); while (!completed && host.ExecutionState == ExecutionState.Running) { Thread.Sleep(10); } exception?.Throw(); }
private void updateList(ValueChangedEvent <Assembly> args) { leftFlowContainer.Clear(); //Add buttons for each TestCase. string namespacePrefix = TestTypes.Select(t => t.Namespace).GetCommonPrefix(); leftFlowContainer.AddRange(TestTypes.Where(t => t.Assembly == args.NewValue) .GroupBy( t => { string group = t.Namespace?.Substring(namespacePrefix.Length).TrimStart('.'); return(string.IsNullOrWhiteSpace(group) ? TestScene.RemovePrefix(t.Name) : group); }, t => t, (group, types) => new TestGroup { Name = group, TestTypes = types.ToArray() } ).OrderBy(g => g.Name) .Select(t => new TestGroupButton(type => LoadTest(type), t))); }
public void LoadTest(Type testType = null, Action onCompletion = null, bool isDynamicLoad = false) { if (CurrentTest?.Parent != null) { testContentContainer.Remove(CurrentTest.Parent); CurrentTest.Dispose(); } CurrentTest = null; if (testType == null && TestTypes.Count > 0) { testType = TestTypes[0]; } config.SetValue(TestBrowserSetting.LastTest, testType?.FullName ?? string.Empty); if (testType == null) { return; } var newTest = (TestScene)Activator.CreateInstance(testType); Debug.Assert(newTest != null); Assembly.Value = testType.Assembly; CurrentTest = newTest; CurrentTest.OnLoadComplete += _ => Schedule(() => finishLoad(newTest, onCompletion)); updateButtons(); resetRecording(); testContentContainer.Add(new ErrorCatchingDelayedLoadWrapper(CurrentTest, isDynamicLoad) { OnCaughtError = compileFailed }); }
private void finishLoad(TestScene newTest, Action onCompletion) { if (CurrentTest != newTest) { // There could have been multiple loads fired after us. In such a case we want to silently remove ourselves. testContentContainer.Remove(newTest.Parent); return; } updateButtons(); bool hadTestAttributeTest = false; foreach (var m in newTest.GetType().GetMethods()) { var name = m.Name; if (name == nameof(TestScene.TestConstructor) || m.GetCustomAttribute(typeof(IgnoreAttribute), false) != null) { continue; } if (name.StartsWith("Test")) { name = name.Substring(4); } int runCount = 1; if (m.GetCustomAttribute(typeof(RepeatAttribute), false) != null) { runCount += (int)m.GetCustomAttributesData().Single(a => a.AttributeType == typeof(RepeatAttribute)).ConstructorArguments.Single().Value; } for (int i = 0; i < runCount; i++) { string repeatSuffix = i > 0 ? $" ({i + 1})" : string.Empty; if (m.GetCustomAttribute(typeof(TestAttribute), false) != null) { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}{repeatSuffix}"); handleTestMethod(m); } foreach (var tc in m.GetCustomAttributes(typeof(TestCaseAttribute), false).OfType <TestCaseAttribute>()) { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}({string.Join(", ", tc.Arguments)}){repeatSuffix}"); handleTestMethod(m, tc.Arguments); } } } // even if no [Test] or [TestCase] methods were found, [SetUp] steps should be added. if (!hadTestAttributeTest) { addSetUpSteps(); } backgroundCompiler?.SetRecompilationTarget(CurrentTest); runTests(onCompletion); updateButtons(); void addSetUpSteps() { var setUpMethods = Reflect.GetMethodsWithAttribute(newTest.GetType(), typeof(SetUpAttribute), true) .Where(m => m.Name != nameof(TestScene.SetUpTestForNUnit)); if (setUpMethods.Any()) { CurrentTest.AddStep(new SetUpStepButton { Action = () => setUpMethods.ForEach(s => s.Invoke(CurrentTest, null)) }); } CurrentTest.RunSetUpSteps(); } void handleTestMethod(MethodInfo methodInfo, object[] arguments = null) { addSetUpSteps(); methodInfo.Invoke(CurrentTest, arguments); CurrentTest.RunTearDownSteps(); } }
/// <summary> /// Blocks execution until a provided <see cref="TestScene"/> runs to completion. /// </summary> /// <param name="test">The <see cref="TestScene"/> to run.</param> public virtual void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test);
private void finishLoad(TestScene newTest, Action onCompletion) { if (CurrentTest != newTest) { // There could have been multiple loads fired after us. In such a case we want to silently remove ourselves. testContentContainer.Remove(newTest.Parent); return; } updateButtons(); bool hadTestAttributeTest = false; foreach (var m in newTest.GetType().GetMethods()) { var name = m.Name; if (name == nameof(TestScene.TestConstructor) || m.GetCustomAttribute(typeof(IgnoreAttribute), false) != null) { continue; } if (name.StartsWith("Test", StringComparison.Ordinal)) { name = name.Substring(4); } int runCount = 1; if (m.GetCustomAttribute(typeof(RepeatAttribute), false) != null) { var count = m.GetCustomAttributesData().Single(a => a.AttributeType == typeof(RepeatAttribute)).ConstructorArguments.Single().Value; Debug.Assert(count != null); runCount += (int)count; } for (int i = 0; i < runCount; i++) { string repeatSuffix = i > 0 ? $" ({i + 1})" : string.Empty; var methodWrapper = new MethodWrapper(m.GetType(), m); if (methodWrapper.GetCustomAttributes <TestAttribute>(false).SingleOrDefault() != null) { var parameters = m.GetParameters(); if (parameters.Length > 0) { var valueMatrix = new List <List <object> >(); foreach (var p in methodWrapper.GetParameters()) { var valueAttrib = p.GetCustomAttributes <ValuesAttribute>(false).SingleOrDefault(); if (valueAttrib == null) { throw new ArgumentException($"Parameter is present on a {nameof(TestAttribute)} method without values specification.", p.ParameterInfo.Name); } List <object> choices = new List <object>(); foreach (var choice in valueAttrib.GetData(p)) { choices.Add(choice); } valueMatrix.Add(choices); } foreach (var combination in valueMatrix.CartesianProduct()) { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}({string.Join(", ", combination)}){repeatSuffix}"); handleTestMethod(m, combination.ToArray()); } } else { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}{repeatSuffix}"); handleTestMethod(m); } } foreach (var tc in m.GetCustomAttributes(typeof(TestCaseAttribute), false).OfType <TestCaseAttribute>()) { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}({string.Join(", ", tc.Arguments)}){repeatSuffix}"); handleTestMethod(m, tc.Arguments); } } } // even if no [Test] or [TestCase] methods were found, [SetUp] steps should be added. if (!hadTestAttributeTest) { addSetUpSteps(); } backgroundCompiler?.SetRecompilationTarget(CurrentTest); runTests(onCompletion); updateButtons(); void addSetUpSteps() { var setUpMethods = Reflect.GetMethodsWithAttribute(newTest.GetType(), typeof(SetUpAttribute), true) .Where(m => m.Name != nameof(TestScene.SetUpTestForNUnit)); if (setUpMethods.Any()) { CurrentTest.AddStep(new SingleStepButton(true) { Text = "[SetUp]", LightColour = Color4.Teal, Action = () => setUpMethods.ForEach(s => s.Invoke(CurrentTest, null)) }); } CurrentTest.RunSetUpSteps(); } void handleTestMethod(MethodInfo methodInfo, object[] arguments = null) { addSetUpSteps(); methodInfo.Invoke(CurrentTest, arguments); CurrentTest.RunTearDownSteps(); } }