diff --git a/osu.Framework.Tests/Visual/Testing/TestSceneStepButton.cs b/osu.Framework.Tests/Visual/Testing/TestSceneStepButton.cs index 613421f7c7..602709ffc2 100644 --- a/osu.Framework.Tests/Visual/Testing/TestSceneStepButton.cs +++ b/osu.Framework.Tests/Visual/Testing/TestSceneStepButton.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing.Drawables.Steps; @@ -22,12 +21,43 @@ public TestSceneStepButton() Spacing = new Vector2(5), Children = new Drawable[] { - new LabelStep { Text = nameof(LabelStep) }, - new AssertButton { Text = nameof(AssertButton), Assertion = () => true }, - new SingleStepButton { Text = nameof(SingleStepButton) }, - new RepeatStepButton(null) { Text = nameof(RepeatStepButton) }, - new ToggleStepButton(null) { Text = nameof(ToggleStepButton) }, - new UntilStepButton(() => true) { Text = nameof(UntilStepButton) }, + new LabelStep + { + Text = nameof(LabelStep), + IsSetupStep = false, + Action = _ => { }, + }, + new AssertButton + { + Text = nameof(AssertButton), + IsSetupStep = false, + Assertion = () => true, + CallStack = new StackTrace() + }, + new SingleStepButton + { + Text = nameof(SingleStepButton), + IsSetupStep = false, + Action = () => { } + }, + new RepeatStepButton + { + Text = nameof(RepeatStepButton), + IsSetupStep = false + }, + new ToggleStepButton + { + Text = nameof(ToggleStepButton), + IsSetupStep = false, + Action = _ => { } + }, + new UntilStepButton + { + Text = nameof(UntilStepButton), + IsSetupStep = false, + Assertion = () => true, + CallStack = new StackTrace() + }, new StepSlider(nameof(StepSlider), 0, 10, 5), } }; diff --git a/osu.Framework.Tests/Visual/Testing/TestSceneTest.cs b/osu.Framework.Tests/Visual/Testing/TestSceneTest.cs index c93a996bc7..26ba31bf95 100644 --- a/osu.Framework.Tests/Visual/Testing/TestSceneTest.cs +++ b/osu.Framework.Tests/Visual/Testing/TestSceneTest.cs @@ -38,9 +38,10 @@ public virtual void SetUpSteps() if (DebugUtils.IsNUnitRunning && TestContext.CurrentContext.Test.MethodName == nameof(TestConstructor)) return; - AddStep(new SingleStepButton(true) + AddStep(new SingleStepButton { - Name = "set up dummy", + Text = "set up dummy", + IsSetupStep = true, Action = () => setupStepsDummyRun++ }); diff --git a/osu.Framework/Testing/Drawables/Steps/AssertButton.cs b/osu.Framework/Testing/Drawables/Steps/AssertButton.cs index e75e05fb03..d910acb964 100644 --- a/osu.Framework/Testing/Drawables/Steps/AssertButton.cs +++ b/osu.Framework/Testing/Drawables/Steps/AssertButton.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; +using System.Runtime.ExceptionServices; using System.Text; using NUnit.Framework; using osuTK.Graphics; @@ -13,15 +12,14 @@ namespace osu.Framework.Testing.Drawables.Steps { public partial class AssertButton : StepButton { - public Func Assertion; - public string ExtendedDescription; - public StackTrace CallStack; - private readonly Func getFailureMessage; + public required StackTrace CallStack { get; init; } + public required Func Assertion { get; init; } + public Func? GetFailureMessage { get; init; } + + public string? ExtendedDescription { get; init; } - public AssertButton(bool isSetupStep = false, Func getFailureMessage = null) - : base(isSetupStep) + public AssertButton() { - this.getFailureMessage = getFailureMessage; Action += checkAssert; LightColour = Color4.OrangeRed; } @@ -39,26 +37,13 @@ private void checkAssert() if (!string.IsNullOrEmpty(ExtendedDescription)) builder.Append($" {ExtendedDescription}"); - if (getFailureMessage != null) - builder.Append($": {getFailureMessage()}"); + if (GetFailureMessage != null) + builder.Append($": {GetFailureMessage()}"); - throw new TracedException(builder.ToString(), CallStack); + throw ExceptionDispatchInfo.SetRemoteStackTrace(new AssertionException(builder.ToString()), CallStack.ToString()); } } public override string ToString() => "Assert: " + base.ToString(); - - private class TracedException : AssertionException - { - private readonly StackTrace trace; - - public TracedException(string description, StackTrace trace) - : base(description) - { - this.trace = trace; - } - - public override string StackTrace => trace.ToString(); - } } } diff --git a/osu.Framework/Testing/Drawables/Steps/LabelStep.cs b/osu.Framework/Testing/Drawables/Steps/LabelStep.cs index c6fcd61616..5f1271a63a 100644 --- a/osu.Framework/Testing/Drawables/Steps/LabelStep.cs +++ b/osu.Framework/Testing/Drawables/Steps/LabelStep.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osuTK.Graphics; namespace osu.Framework.Testing.Drawables.Steps { public partial class LabelStep : StepButton { + public new required Action Action { get; init; } + protected override Color4 IdleColour => new Color4(77, 77, 77, 255); protected override Color4 RunningColour => new Color4(128, 128, 128, 255); @@ -15,6 +18,9 @@ public LabelStep() { Light.Hide(); Height = 30; + base.Action = clickAction; } + + private void clickAction() => Action(this); } } diff --git a/osu.Framework/Testing/Drawables/Steps/RepeatStepButton.cs b/osu.Framework/Testing/Drawables/Steps/RepeatStepButton.cs index 5a386b60af..20e7aa4e1f 100644 --- a/osu.Framework/Testing/Drawables/Steps/RepeatStepButton.cs +++ b/osu.Framework/Testing/Drawables/Steps/RepeatStepButton.cs @@ -1,45 +1,39 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; namespace osu.Framework.Testing.Drawables.Steps { public partial class RepeatStepButton : StepButton { - private readonly int count; - private int invocations; + public int Count { get; init; } = 1; - public override int RequiredRepetitions => count; + public override int RequiredRepetitions => Count; - private string text; + private readonly string text = string.Empty; + private int invocations; - public new string Text + public RepeatStepButton() { - get => text; - set => base.Text = text = value; + updateText(); } - public RepeatStepButton(Action action, int count = 1, bool isSetupStep = false) - : base(isSetupStep) + public new string Text { - this.count = count; - Action = action; - - updateText(); + get => text; + init => base.Text = text = value; } public override void PerformStep(bool userTriggered = false) { - if (invocations == count && !userTriggered) throw new InvalidOperationException("Repeat step was invoked too many times"); + if (invocations == Count && !userTriggered) throw new InvalidOperationException("Repeat step was invoked too many times"); invocations++; base.PerformStep(userTriggered); - if (invocations >= count) // Allows for manual execution beyond the invocation limit. + if (invocations >= Count) // Allows for manual execution beyond the invocation limit. Success(); updateText(); @@ -53,7 +47,7 @@ public override void Reset() updateText(); } - private void updateText() => base.Text = $@"{Text} {invocations}/{count}"; + private void updateText() => base.Text = $@"{Text} {invocations}/{Count}"; public override string ToString() => "Repeat: " + base.ToString(); } diff --git a/osu.Framework/Testing/Drawables/Steps/SingleStepButton.cs b/osu.Framework/Testing/Drawables/Steps/SingleStepButton.cs index bc8806c971..203621e217 100644 --- a/osu.Framework/Testing/Drawables/Steps/SingleStepButton.cs +++ b/osu.Framework/Testing/Drawables/Steps/SingleStepButton.cs @@ -1,24 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; namespace osu.Framework.Testing.Drawables.Steps { public partial class SingleStepButton : StepButton { - public new Action Action; + public new required Action Action { get; init; } + + public SingleStepButton() + { + base.Action = clickAction; + } - public SingleStepButton(bool isSetupStep = false) - : base(isSetupStep) + private void clickAction() { - base.Action = () => - { - Action?.Invoke(); - Success(); - }; + Action(); + Success(); } } } diff --git a/osu.Framework/Testing/Drawables/Steps/StepButton.cs b/osu.Framework/Testing/Drawables/Steps/StepButton.cs index d85b9180ef..23d90f8a2a 100644 --- a/osu.Framework/Testing/Drawables/Steps/StepButton.cs +++ b/osu.Framework/Testing/Drawables/Steps/StepButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,42 +14,20 @@ namespace osu.Framework.Testing.Drawables.Steps { public abstract partial class StepButton : CompositeDrawable { - public virtual int RequiredRepetitions => 1; - - protected Box Light; - protected Box Background; - protected SpriteText SpriteText; - - public Action Action { get; set; } - - public LocalisableString Text - { - get => SpriteText.Text; - set => SpriteText.Text = value; - } - - private Color4 lightColour = Color4.BlueViolet; - - public Color4 LightColour - { - get => lightColour; - set - { - lightColour = value; - if (IsLoaded) Reset(); - } - } + public required bool IsSetupStep { get; init; } + public Action? Action { get; set; } - public readonly bool IsSetupStep; + public virtual int RequiredRepetitions => 1; protected virtual Color4 IdleColour => new Color4(0.15f, 0.15f, 0.15f, 1); - protected virtual Color4 RunningColour => new Color4(0.5f, 0.5f, 0.5f, 1); - protected StepButton(bool isSetupStep = false) - { - IsSetupStep = isSetupStep; + protected readonly Box Light; + protected readonly Box Background; + protected readonly SpriteText SpriteText; + protected StepButton() + { InternalChildren = new Drawable[] { Background = new Box @@ -85,6 +61,24 @@ protected StepButton(bool isSetupStep = false) Masking = true; } + public LocalisableString Text + { + get => SpriteText.Text; + set => SpriteText.Text = value; + } + + private Color4 lightColour = Color4.BlueViolet; + + public Color4 LightColour + { + get => lightColour; + set + { + lightColour = value; + if (IsLoaded) Reset(); + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Framework/Testing/Drawables/Steps/ToggleStepButton.cs b/osu.Framework/Testing/Drawables/Steps/ToggleStepButton.cs index 5d2b7d8857..41341b6f92 100644 --- a/osu.Framework/Testing/Drawables/Steps/ToggleStepButton.cs +++ b/osu.Framework/Testing/Drawables/Steps/ToggleStepButton.cs @@ -9,31 +9,31 @@ namespace osu.Framework.Testing.Drawables.Steps { public partial class ToggleStepButton : StepButton { - private readonly Action? reloadCallback; private static readonly Color4 off_colour = Color4.Red; private static readonly Color4 on_colour = Color4.YellowGreen; - public bool State; + public new required Action Action { get; init; } public override int RequiredRepetitions => 2; - public ToggleStepButton(Action? reloadCallback) + private bool state; + + public ToggleStepButton() { - this.reloadCallback = reloadCallback; - Action = clickAction; + base.Action = clickAction; LightColour = off_colour; } private void clickAction() { - State = !State; - Light.FadeColour(State ? on_colour : off_colour); - reloadCallback?.Invoke(State); + state = !state; + Light.FadeColour(state ? on_colour : off_colour); + Action(state); - if (!State) + if (!state) Success(); } - public override string ToString() => $"Toggle: {base.ToString()} ({(State ? "on" : "off")})"; + public override string ToString() => $"Toggle: {base.ToString()} ({(state ? "on" : "off")})"; } } diff --git a/osu.Framework/Testing/Drawables/Steps/UntilStepButton.cs b/osu.Framework/Testing/Drawables/Steps/UntilStepButton.cs index 54656d8953..89447a9512 100644 --- a/osu.Framework/Testing/Drawables/Steps/UntilStepButton.cs +++ b/osu.Framework/Testing/Drawables/Steps/UntilStepButton.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; +using System.Runtime.ExceptionServices; using System.Text; using NUnit.Framework; using osu.Framework.Graphics; @@ -14,60 +13,59 @@ namespace osu.Framework.Testing.Drawables.Steps { public partial class UntilStepButton : StepButton { - private bool success; - - private int invocations; - private static readonly int max_attempt_milliseconds = FrameworkEnvironment.NoTestTimeout ? int.MaxValue : 10000; + public required StackTrace CallStack { get; init; } + public required Func Assertion { get; init; } + public Func? GetFailureMessage { get; init; } + public new Action? Action { get; set; } + public override int RequiredRepetitions => success ? 0 : int.MaxValue; - public new Action Action; + private readonly string text = string.Empty; + private bool success; + private int invocations; + private Stopwatch? elapsedTime; - private string text; + public UntilStepButton() + { + updateText(); + LightColour = Color4.Sienna; + base.Action = checkAssert; + } public new string Text { get => text; - set => base.Text = text = value; + init => base.Text = text = value; } - private Stopwatch elapsedTime; - - public UntilStepButton(Func waitUntilTrueDelegate, bool isSetupStep = false, Func getFailureMessage = null) - : base(isSetupStep) + private void checkAssert() { + invocations++; + elapsedTime ??= Stopwatch.StartNew(); + updateText(); - LightColour = Color4.Sienna; - base.Action = () => + if (Assertion()) { - invocations++; - - elapsedTime ??= Stopwatch.StartNew(); - - updateText(); - - if (waitUntilTrueDelegate()) - { - elapsedTime = null; - success = true; - Success(); - } - else if (!Debugger.IsAttached && elapsedTime.ElapsedMilliseconds >= max_attempt_milliseconds) - { - StringBuilder builder = new StringBuilder(); + elapsedTime = null; + success = true; + Success(); + } + else if (!Debugger.IsAttached && elapsedTime.ElapsedMilliseconds >= max_attempt_milliseconds) + { + StringBuilder builder = new StringBuilder(); - builder.Append($"\"{Text}\" timed out"); + builder.Append($"\"{Text}\" timed out"); - if (getFailureMessage != null) - builder.Append($": {getFailureMessage()}"); + if (GetFailureMessage != null) + builder.Append($": {GetFailureMessage()}"); - throw new AssertionException(builder.ToString()); - } + throw ExceptionDispatchInfo.SetRemoteStackTrace(new AssertionException(builder.ToString()), CallStack.ToString()); + } - Action?.Invoke(); - }; + Action?.Invoke(); } public override void Reset() diff --git a/osu.Framework/Testing/TestBrowser.cs b/osu.Framework/Testing/TestBrowser.cs index 3290b01e3f..7ccfca25c6 100644 --- a/osu.Framework/Testing/TestBrowser.cs +++ b/osu.Framework/Testing/TestBrowser.cs @@ -521,9 +521,10 @@ void addSetUpSteps() if (setUpMethods.Any()) { - CurrentTest.AddStep(new SingleStepButton(true) + CurrentTest.AddStep(new SingleStepButton { Text = "[SetUp]", + IsSetupStep = true, LightColour = Color4.Teal, Action = () => setUpMethods.ForEach(s => s.Invoke(CurrentTest, null)) }); diff --git a/osu.Framework/Testing/TestScene.cs b/osu.Framework/Testing/TestScene.cs index c0dca8ec2f..4cc97bf25f 100644 --- a/osu.Framework/Testing/TestScene.cs +++ b/osu.Framework/Testing/TestScene.cs @@ -271,145 +271,164 @@ private void runNextStep(Action onCompletion, Action onError, Func runNextStep(onCompletion, onError, stopCondition), TimePerAction); } - public void AddStep(StepButton step) => schedule(() => StepsContainer.Add(step)); - - private bool addStepsAsSetupSteps; - public void ChangeBackgroundColour(ColourInfo colour) => backgroundFill.FadeColour(colour, 200, Easing.OutQuint); - public StepButton AddStep(string description, Action action) + private bool addStepsAsSetupSteps; + + public void AddStep(StepButton step) { - var step = new SingleStepButton(addStepsAsSetupSteps) + schedule(() => { - Text = description, - Action = action - }; - - AddStep(step); - - return step; + StepsContainer.Add(step); + }); } - public LabelStep AddLabel(string description) + public void AddStep([NotNull] string description, [NotNull] Action action) { - var step = new LabelStep + AddStep(new SingleStepButton { Text = description, - }; + Action = action, + IsSetupStep = addStepsAsSetupSteps + }); + } - step.Action = () => + public void AddLabel([NotNull] string description) + { + AddStep(new LabelStep { - Logger.Log($@"💨 {this} {description}"); - - // kinda hacky way to avoid this doesn't get triggered by automated runs. - if (step.IsHovered) - RunAllSteps(startFromStep: step, stopCondition: s => s is LabelStep); - }; - - AddStep(step); + Text = description, + IsSetupStep = false, + Action = step => + { + Logger.Log($@"💨 {this} {description}"); - return step; + // kinda hacky way to avoid this doesn't get triggered by automated runs. + if (step.IsHovered) + RunAllSteps(startFromStep: step, stopCondition: s => s is LabelStep); + }, + }); } - protected void AddRepeatStep(string description, Action action, int invocationCount) => schedule(() => + protected void AddRepeatStep([NotNull] string description, [NotNull] Action action, int invocationCount) { - StepsContainer.Add(new RepeatStepButton(action, invocationCount, addStepsAsSetupSteps) + AddStep(new RepeatStepButton { Text = description, + IsSetupStep = addStepsAsSetupSteps, + Action = action, + Count = invocationCount }); - }); + } - protected void AddToggleStep(string description, Action action) => schedule(() => + protected void AddToggleStep([NotNull] string description, [NotNull] Action action) { - StepsContainer.Add(new ToggleStepButton(action) + AddStep(new ToggleStepButton { - Text = description + Text = description, + IsSetupStep = addStepsAsSetupSteps, + Action = action, }); - }); + } - protected void AddUntilStep(string description, Func waitUntilTrueDelegate) => schedule(() => + protected void AddUntilStep([CanBeNull] string description, [NotNull] Func waitUntilTrueDelegate) { - StepsContainer.Add(new UntilStepButton(waitUntilTrueDelegate, addStepsAsSetupSteps) + AddStep(new UntilStepButton { Text = description ?? @"Until", + IsSetupStep = addStepsAsSetupSteps, + CallStack = new StackTrace(1, true), + Assertion = waitUntilTrueDelegate, }); - }); + } - protected void AddUntilStep(string description, ActualValueDelegate actualValue, Func constraint) => schedule(() => + protected void AddUntilStep([CanBeNull] string description, [NotNull] ActualValueDelegate actualValue, [NotNull] Func constraint) { ConstraintResult lastResult = null; - StepsContainer.Add( - new UntilStepButton( - () => - { - lastResult = constraint().Resolve().ApplyTo(actualValue()); - return lastResult.IsSuccess; - }, - addStepsAsSetupSteps, - () => - { - var writer = new TextMessageWriter(string.Empty); - lastResult.WriteMessageTo(writer); - return writer.ToString().TrimStart(); - }) + AddStep(new UntilStepButton + { + Text = description ?? @"Until", + IsSetupStep = addStepsAsSetupSteps, + CallStack = new StackTrace(1, true), + Assertion = () => { - Text = description ?? @"Until", - }); - }); + lastResult = constraint().Resolve().ApplyTo(actualValue()); + return lastResult.IsSuccess; + }, + GetFailureMessage = () => + { + if (lastResult == null) + return string.Empty; + + var writer = new TextMessageWriter(string.Empty); + lastResult.WriteMessageTo(writer); + return writer.ToString().TrimStart(); + } + }); + } - protected void AddWaitStep(string description, int waitCount) => schedule(() => + protected void AddWaitStep([CanBeNull] string description, int waitCount) { - StepsContainer.Add(new RepeatStepButton(() => { }, waitCount, addStepsAsSetupSteps) + AddStep(new RepeatStepButton { Text = description ?? @"Wait", + IsSetupStep = addStepsAsSetupSteps, + Count = waitCount }); - }); + } - protected void AddSliderStep(string description, T min, T max, T start, Action valueChanged) where T : struct, INumber, IMinMaxValue => schedule(() => + protected void AddSliderStep([NotNull] string description, T min, T max, T start, [NotNull] Action valueChanged) where T : struct, INumber, IMinMaxValue { - StepsContainer.Add(new StepSlider(description, min, max, start) + schedule(() => { - ValueChanged = valueChanged, + StepsContainer.Add(new StepSlider(description, min, max, start) + { + ValueChanged = valueChanged, + }); }); - }); + } - protected void AddAssert(string description, Func assert, string extendedDescription = null) => schedule(() => + protected void AddAssert([NotNull] string description, [NotNull] Func assert, [CanBeNull] string extendedDescription = null) { - StepsContainer.Add(new AssertButton(addStepsAsSetupSteps) + AddStep(new AssertButton { Text = description, + IsSetupStep = addStepsAsSetupSteps, ExtendedDescription = extendedDescription, - CallStack = new StackTrace(1), + CallStack = new StackTrace(1, true), Assertion = assert, }); - }); + } - protected void AddAssert(string description, ActualValueDelegate actualValue, Func constraint, string extendedDescription = null) => schedule(() => + protected void AddAssert([NotNull] string description, [NotNull] ActualValueDelegate actualValue, [NotNull] Func constraint, + [CanBeNull] string extendedDescription = null) { ConstraintResult lastResult = null; - StepsContainer.Add(new AssertButton(addStepsAsSetupSteps, () => - { - if (lastResult == null) - return string.Empty; - - var writer = new TextMessageWriter(string.Empty); - lastResult.WriteMessageTo(writer); - return writer.ToString().TrimStart(); - }) + AddStep(new AssertButton { Text = description, + IsSetupStep = addStepsAsSetupSteps, ExtendedDescription = extendedDescription, - CallStack = new StackTrace(1), + CallStack = new StackTrace(1, true), Assertion = () => { lastResult = constraint().Resolve().ApplyTo(actualValue()); return lastResult.IsSuccess; + }, + GetFailureMessage = () => + { + if (lastResult == null) + return string.Empty; + + var writer = new TextMessageWriter(string.Empty); + lastResult.WriteMessageTo(writer); + return writer.ToString().TrimStart(); } }); - }); + } internal void RunSetUpSteps() {