Path: blob/master/osu.Game/Tests/Visual/ReplayStabilityTestScene.cs
4334 views
// Copyright (c) ppy Pty Ltd <[email protected]>. 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; } } }