1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-15 14:13:01 +08:00
Files
osu-lazer/osu.Game/Tests/Visual/ReplayStabilityTestScene.cs
T
2025-06-27 19:43:26 +09:00

121 lines
5.1 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.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
/// <summary>
/// The goal of this abstract test class is to ensure that the process of exporting and re-importing of a replay does not affect its playback.
/// Use <see cref="RunTest"/> to exercise that property.
/// </summary>
[HeadlessTest]
[TestFixture]
public abstract partial class ReplayStabilityTestScene : RateAdjustedBeatmapTestScene
{
private ReplayPlayer currentPlayer = null!;
private readonly List<JudgementResult> results = new List<JudgementResult>();
/// <summary>
/// Runs <paramref name="replay"/> against the supplied <paramref name="beatmap"/>
/// and checks that the judgement results recorded match <paramref name="expectedResults"/>.
/// Then, encodes the <paramref name="replay"/>, decodes the result of encoding, runs the result of decoding against the supplied <paramref name="beatmap"/>,
/// and checks that the judgement results recorded still match <paramref name="expectedResults"/>.
/// </summary>
protected void RunTest(IBeatmap beatmap, Replay replay, IEnumerable<HitResult> expectedResults)
{
Score originalScore = null!;
Score decodedScore = null!;
AddStep(@"create replay", () => originalScore = new Score
{
Replay = replay,
ScoreInfo = new ScoreInfo()
});
AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(beatmap));
AddStep(@"set ruleset", () => Ruleset.Value = beatmap.BeatmapInfo.Ruleset);
AddStep(@"push player", () => pushNewPlayer(originalScore));
AddUntilStep(@"wait until player is loaded", () => currentPlayer.IsCurrentScreen());
skipIntroIfPresent();
AddUntilStep(@"wait for completion", () => currentPlayer.GameplayState.HasCompleted);
AddAssert(@"judgement results before encode are correct", () => results.Select(r => r.Type), () => Is.EquivalentTo(expectedResults));
AddStep(@"exit player", () => currentPlayer.Exit());
// The incoming beatmap is ruleset-typed in every usage, so the incoming hitobjects will be used as-is rather than being converted.
// Because we'll be re-using the beatmap (thus also the hitobjects), we need to make sure the previous player has been fully disposed.
AddUntilStep("player exited", () => !currentPlayer.IsCurrentScreen());
AddStep("dispose player", () => currentPlayer.Dispose());
AddStep(@"encode and decode score", () =>
{
var encoder = new LegacyScoreEncoder(originalScore, beatmap);
using (var stream = new MemoryStream())
{
encoder.Encode(stream, leaveOpen: true);
stream.Position = 0;
decodedScore = new TestScoreDecoder(Beatmap.Value).Parse(stream);
}
});
AddStep(@"push player", () => pushNewPlayer(decodedScore));
AddUntilStep(@"Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
skipIntroIfPresent();
AddUntilStep(@"Wait for completion", () => currentPlayer.GameplayState.HasCompleted);
AddAssert(@"judgement results after encode are correct", () => results.Select(r => r.Type), () => Is.EquivalentTo(expectedResults));
}
private void pushNewPlayer(Score score)
{
var player = new ReplayPlayer(score);
player.OnLoadComplete += _ =>
{
player.GameplayState.ScoreProcessor.NewJudgement += result =>
{
if (currentPlayer == player)
results.Add(result);
};
};
LoadScreen(currentPlayer = player);
results.Clear();
}
private void skipIntroIfPresent() =>
AddStep(@"skip intro if present", () =>
{
if (currentPlayer.ChildrenOfType<GameplayClockContainer>().Single().CurrentTime < 0)
currentPlayer.Seek(0);
});
private class TestScoreDecoder : LegacyScoreDecoder
{
private readonly WorkingBeatmap beatmap;
public TestScoreDecoder(WorkingBeatmap beatmap)
{
this.beatmap = beatmap;
}
protected override Ruleset GetRuleset(int rulesetId) => beatmap.BeatmapInfo.Ruleset.CreateInstance();
protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmap;
}
}
}