A game framework written with osu! in mind.
at master 4.2 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Diagnostics; 6using System.Runtime.ExceptionServices; 7using System.Threading; 8using osu.Framework.Allocation; 9using osu.Framework.Bindables; 10using osu.Framework.Configuration; 11using osu.Framework.Graphics; 12using osu.Framework.Graphics.Containers; 13using osu.Framework.Platform; 14 15namespace osu.Framework.Testing 16{ 17 public class TestSceneTestRunner : Game, ITestSceneTestRunner 18 { 19 private readonly TestRunner runner; 20 21 public TestSceneTestRunner() 22 { 23 Add(runner = new TestRunner()); 24 } 25 26 /// <summary> 27 /// Blocks execution until a provided <see cref="TestScene"/> runs to completion. 28 /// </summary> 29 /// <param name="test">The <see cref="TestScene"/> to run.</param> 30 public virtual void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); 31 32 public class TestRunner : CompositeDrawable 33 { 34 private const double time_between_tests = 200; 35 36 private Bindable<double> volume; 37 private double volumeAtStartup; 38 39 [Resolved] 40 private GameHost host { get; set; } 41 42 public TestRunner() 43 { 44 RelativeSizeAxes = Axes.Both; 45 } 46 47 [BackgroundDependencyLoader] 48 private void load(FrameworkConfigManager config) 49 { 50 volume = config.GetBindable<double>(FrameworkSetting.VolumeUniversal); 51 volumeAtStartup = volume.Value; 52 volume.Value = 0; 53 } 54 55 internal override void UnbindAllBindables() 56 { 57 base.UnbindAllBindables(); 58 if (volume != null) volume.Value = volumeAtStartup; 59 } 60 61 /// <summary> 62 /// Blocks execution until a provided <see cref="TestScene"/> runs to completion. 63 /// </summary> 64 /// <param name="test">The <see cref="TestScene"/> to run.</param> 65 public void RunTestBlocking(TestScene test) 66 { 67 Trace.Assert(host != null, $"Ensure this runner has been loaded before calling {nameof(RunTestBlocking)}"); 68 69 bool completed = false; 70 ExceptionDispatchInfo exception = null; 71 72 void complete() 73 { 74 // 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. 75 // This is because nUnit will reuse the same class multiple times, running a different [Test] method each time, while the GameHost 76 // is run from its own asynchronous thread. 77 RemoveInternal(test); 78 completed = true; 79 } 80 81 Schedule(() => 82 { 83 AddInternal(test); 84 85 Console.WriteLine($@"{(int)Time.Current}: Running {test} visual test cases..."); 86 87 // Nunit will run the tests in the TestScene with the same TestScene instance so the TestScene 88 // needs to be removed before the host is exited, otherwise it will end up disposed 89 90 test.RunAllSteps(() => 91 { 92 Scheduler.AddDelayed(complete, time_between_tests); 93 }, e => 94 { 95 exception = ExceptionDispatchInfo.Capture(e); 96 complete(); 97 }); 98 }); 99 100 while (!completed && host.ExecutionState == ExecutionState.Running) 101 Thread.Sleep(10); 102 103 exception?.Throw(); 104 } 105 } 106 } 107 108 public interface ITestSceneTestRunner 109 { 110 /// <summary> 111 /// Blocks execution until a provided <see cref="TestScene"/> runs to completion. 112 /// </summary> 113 /// <param name="test">The <see cref="TestScene"/> to run.</param> 114 void RunTestBlocking(TestScene test); 115 } 116}