1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-26 10:49:57 +08:00
Files
osu-lazer/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
T
Bartłomiej Dach 809298ddeb Turn tests green, for a short while, maybe (#37218)
## [Rewrite `BackgroundMusicManager` to not run into framework
breakage](https://github.com/ppy/osu/commit/622216d8911832c39fa4e126b2810e4e0f46cbf7)

The attempted proper fix to this was
https://github.com/ppy/osu-framework/pull/6727. Unfortunately when
presented with [the framework
bump](https://github.com/ppy/osu/pull/37217) with that change, CI says
"you're stupid" and fails on some disposal idiocy that of course is
undebuggable and irreproducible:

The active test run was aborted. Reason: Test host process crashed :
Unhandled exception. System.AggregateException: One or more errors
occurred. (Object reference not set to an instance of an object.)
---> System.NullReferenceException: Object reference not set to an
instance of an object.
at osu.Framework.Audio.Sample.SampleChannelBass.Dispose(Boolean
disposing)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext
executionContext, ContextCallback callback, Object state)
	--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext
executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&
currentTaskSlot, Thread threadPoolThread)
	   --- End of inner exception stack trace ---
	   at osu.Framework.Audio.AudioCollectionManager`1.UpdateChildren()
	   at osu.Framework.Audio.AudioCollectionManager`1.UpdateChildren()
	   at osu.Framework.Audio.AudioCollectionManager`1.UpdateChildren()
	   at osu.Framework.Audio.AudioCollectionManager`1.UpdateChildren()
	   at osu.Framework.Threading.AudioThread.OnExit()
at osu.Framework.Threading.GameThread.setExitState(GameThreadState
exitState)
	   at osu.Framework.Threading.GameThread.RunSingleFrame()
at osu.Framework.Threading.GameThread.<createThread>g__runWork|70_0()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext
executionContext, ContextCallback callback, Object state)
	--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext
executionContext, ContextCallback callback, Object state)


(https://github.com/ppy/osu/actions/runs/24019928154/job/70046733058?pr=37217#step:5:119)

I no longer have the energy for any of this shit.

@nekodex would appreciate if you could check that I actually haven't
broken anything with the bgm here. Seems okay to me in test scenes at
least.

## [Apply lowest-effort maybe-fixing changes to a bunch of flaking
tests](https://github.com/ppy/osu/commit/7bd3ca4adfcce5b90add11565a13f3fe9177ad5e)

None of the failures are reproducible locally, of course. I'm tired of
this. If anyone else wants to subject themselves to actually
investigating any of these, by all means, godspeed and good luck.
2026-04-06 17:59:39 +09:00

498 lines
19 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Screens.Ranking.Expanded.Statistics;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
using Realms;
namespace osu.Game.Tests.Visual.Ranking
{
[TestFixture]
public partial class TestSceneResultsScreen : OsuManualInputManagerTestScene
{
[Resolved]
private BeatmapManager beatmaps { get; set; }
[Resolved]
private RealmAccess realm { get; set; }
[Resolved]
private SkinManager skins { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
realm.Run(r =>
{
var beatmapInfo = r.All<BeatmapInfo>()
.Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
.FirstOrDefault();
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
});
}
[SetUp]
public void SetUp() => Schedule(() => skins.CurrentSkinInfo.SetDefault());
[Test]
public void TestScaling()
{
// scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason.
AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() =>
{
Content.Scale = new Vector2(v);
Content.Size = new Vector2(1f / v);
}));
}
[Test]
public void TestLegacySkin()
{
AddToggleStep("toggle legacy classic skin", v =>
{
if (skins != null)
skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default;
});
}
private int onlineScoreID = 1;
[TestCase(1, ScoreRank.X, 0)]
[TestCase(0.9999, ScoreRank.S, 0)]
[TestCase(0.975, ScoreRank.S, 0)]
[TestCase(0.975, ScoreRank.A, 1)]
[TestCase(0.925, ScoreRank.A, 5)]
[TestCase(0.85, ScoreRank.B, 9)]
[TestCase(0.75, ScoreRank.C, 11)]
[TestCase(0.5, ScoreRank.D, 21)]
[TestCase(0.2, ScoreRank.D, 51)]
public void TestResultsWithPlayer(double accuracy, ScoreRank rank, int missCount)
{
TestResultsScreen screen = null;
loadResultsScreen(() =>
{
var score = TestResources.CreateTestScoreInfo();
score.OnlineID = onlineScoreID++;
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
score.Accuracy = accuracy;
score.Rank = rank;
score.Statistics[HitResult.Miss] = missCount;
return screen = createResultsScreen(score);
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
}
[Test]
public void TestResultsWithoutPlayer()
{
TestResultsScreen screen = null;
OsuScreenStack stack;
AddStep("load results", () =>
{
Child = stack = new OsuScreenStack
{
RelativeSizeAxes = Axes.Both
};
var score = TestResources.CreateTestScoreInfo();
stack.Push(screen = createResultsScreen(score));
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
}
[Test]
public void TestResultsForUnranked()
{
UnrankedSoloResultsScreen screen = null;
loadResultsScreen(() => screen = createUnrankedSoloResultsScreen());
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
}
[Test]
public void TestResultsWithFailingRank()
{
TestResultsScreen screen = null;
loadResultsScreen(() =>
{
var score = TestResources.CreateTestScoreInfo();
score.OnlineID = onlineScoreID++;
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
score.Rank = ScoreRank.F;
return screen = createResultsScreen(score);
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
AddAssert("no badges displayed", () => this.ChildrenOfType<RankBadge>().All(b => !b.IsPresent));
}
[Test]
public void TestResultsWithFailingRankOnLegacySkin()
{
TestResultsScreen screen = null;
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo);
loadResultsScreen(() =>
{
var score = TestResources.CreateTestScoreInfo();
score.OnlineID = onlineScoreID++;
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
score.Rank = ScoreRank.F;
return screen = createResultsScreen(score);
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
AddAssert("no badges displayed", () => this.ChildrenOfType<RankBadge>().All(b => !b.IsPresent));
}
[Test]
public void TestShowHideStatisticsViaOutsideClick()
{
TestResultsScreen screen = null;
loadResultsScreen(() => screen = createResultsScreen());
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
AddStep("click expanded panel", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
InputManager.MoveMouseTo(expandedPanel);
InputManager.Click(MouseButton.Left);
});
AddAssert("statistics shown", () => this.ChildrenOfType<StatisticsPanel>().Single().State.Value == Visibility.Visible);
AddUntilStep("expanded panel at the left of the screen", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
return expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150;
});
AddStep("click to right of panel", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(50, 0));
InputManager.Click(MouseButton.Left);
});
AddAssert("statistics hidden", () => this.ChildrenOfType<StatisticsPanel>().Single().State.Value == Visibility.Hidden);
AddUntilStep("expanded panel in centre of screen", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1);
});
}
[Test]
public void TestShowHideStatistics()
{
TestResultsScreen screen = null;
loadResultsScreen(() => screen = createResultsScreen());
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
AddStep("click expanded panel", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
InputManager.MoveMouseTo(expandedPanel);
InputManager.Click(MouseButton.Left);
});
AddAssert("statistics shown", () => this.ChildrenOfType<StatisticsPanel>().Single().State.Value == Visibility.Visible);
AddUntilStep("expanded panel at the left of the screen", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
return expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150;
});
AddStep("click expanded panel", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
InputManager.MoveMouseTo(expandedPanel);
InputManager.Click(MouseButton.Left);
});
AddAssert("statistics hidden", () => this.ChildrenOfType<StatisticsPanel>().Single().State.Value == Visibility.Hidden);
AddUntilStep("expanded panel in centre of screen", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1);
});
}
[Test]
public void TestShowStatisticsAndClickOtherPanel()
{
TestResultsScreen screen = null;
loadResultsScreen(() => screen = createResultsScreen());
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
ScorePanel expandedPanel = null;
ScorePanel contractedPanel = null;
AddUntilStep("retrieve expanded panel",
() => expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded),
() => Is.Not.Null);
AddUntilStep("retrieve contracted panel",
() => contractedPanel = this.ChildrenOfType<ScorePanel>().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X),
() => Is.Not.Null);
AddStep("click expanded panel then contracted panel", () =>
{
InputManager.MoveMouseTo(expandedPanel);
InputManager.Click(MouseButton.Left);
InputManager.MoveMouseTo(contractedPanel);
InputManager.Click(MouseButton.Left);
});
AddAssert("statistics shown", () => this.ChildrenOfType<StatisticsPanel>().Single().State.Value == Visibility.Visible);
AddAssert("contracted panel still contracted", () => contractedPanel.State == PanelState.Contracted);
AddAssert("expanded panel still expanded", () => expandedPanel.State == PanelState.Expanded);
}
[Test]
public void TestFetchScoresAfterShowingStatistics()
{
DelayedFetchResultsScreen screen = null;
var tcs = new TaskCompletionSource<bool>();
loadResultsScreen(() => screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), tcs.Task));
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddStep("click expanded panel", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
InputManager.MoveMouseTo(expandedPanel);
InputManager.Click(MouseButton.Left);
});
AddAssert("no fetch yet", () => !screen.FetchCompleted);
AddStep("allow fetch", () => tcs.SetResult(true));
AddUntilStep("wait for fetch", () => screen.FetchCompleted);
AddAssert("expanded panel still on screen", () => this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0);
}
[Test]
public void TestDownloadButtonInitiallyDisabled()
{
TestResultsScreen screen = null;
loadResultsScreen(() => screen = createResultsScreen());
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
AddAssert("download button is disabled", () => !screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
AddStep("click contracted panel", () =>
{
var contractedPanel = this.ChildrenOfType<ScorePanel>().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X);
InputManager.MoveMouseTo(contractedPanel);
InputManager.Click(MouseButton.Left);
});
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
}
[Test]
public void TestRulesetWithNoPerformanceCalculator()
{
var ruleset = new RulesetWithNoPerformanceCalculator();
var score = TestResources.CreateTestScoreInfo(ruleset.RulesetInfo);
loadResultsScreen(() => createResultsScreen(score));
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
AddAssert("PP displayed as 0", () =>
{
var performance = this.ChildrenOfType<PerformanceStatistic>().Single();
var counter = performance.ChildrenOfType<StatisticCounter>().Single();
return counter.Current.Value == 0;
});
}
private void loadResultsScreen(Func<ResultsScreen> createResults)
{
ResultsScreen results = null;
AddStep("load results", () => Child = new TestResultsContainer(results = createResults()));
// expanded panel should be centered the moment results screen is loaded
// but can potentially be scrolled away on certain specific load scenarios.
// see: https://github.com/ppy/osu/issues/18226
AddUntilStep("expanded panel in centre of screen", () =>
{
var expandedPanel = this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, results.ScreenSpaceDrawQuad.Centre.X, 1);
});
}
private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo());
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo());
private partial class TestResultsContainer : Container
{
[Cached(typeof(Player))]
private readonly Player player = new TestPlayer();
public TestResultsContainer(IScreen screen)
{
RelativeSizeAxes = Axes.Both;
OsuScreenStack stack;
InternalChild = stack = new OsuScreenStack
{
RelativeSizeAxes = Axes.Both,
};
stack.Push(screen);
}
}
private partial class TestResultsScreen : SoloResultsScreen
{
public HotkeyRetryOverlay RetryOverlay;
public TestResultsScreen(ScoreInfo score)
: base(score)
{
AllowRetry = true;
IsLocalPlay = true;
}
protected override void LoadComplete()
{
base.LoadComplete();
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
}
protected override Task<ScoreInfo[]> FetchScores()
{
var scores = new ScoreInfo[20];
for (int i = 0; i < scores.Length; i++)
{
var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i;
score.HasOnlineReplay = true;
scores[i] = score;
}
return Task.FromResult(scores);
}
}
private partial class DelayedFetchResultsScreen : TestResultsScreen
{
private readonly Task fetchWaitTask;
public bool FetchCompleted { get; private set; }
public DelayedFetchResultsScreen(ScoreInfo score, Task fetchWaitTask = null)
: base(score)
{
this.fetchWaitTask = fetchWaitTask ?? Task.CompletedTask;
}
protected override Task<ScoreInfo[]> FetchScores()
{
return Task.Run(async () =>
{
await fetchWaitTask;
var scores = new ScoreInfo[20];
for (int i = 0; i < scores.Length; i++)
{
var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i;
scores[i] = score;
}
Schedule(() => FetchCompleted = true);
return scores;
});
}
}
private partial class UnrankedSoloResultsScreen : SoloResultsScreen
{
public HotkeyRetryOverlay RetryOverlay;
public UnrankedSoloResultsScreen(ScoreInfo score)
: base(score)
{
AllowRetry = true;
Score!.BeatmapInfo!.OnlineID = 0;
Score.BeatmapInfo.Status = BeatmapOnlineStatus.Pending;
}
protected override void LoadComplete()
{
base.LoadComplete();
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
}
}
private class RulesetWithNoPerformanceCalculator : OsuRuleset
{
public override PerformanceCalculator CreatePerformanceCalculator() => null!;
}
}
}