diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2139572601..3c52802cf6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: run: dotnet build -c Debug -warnaserror osu.Desktop.slnf - name: Test - run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --blame-crash --blame-hang --blame-hang-timeout 5m --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" + run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" shell: pwsh # Attempt to upload results even if test fails. @@ -48,7 +48,7 @@ jobs: if: ${{ always() }} with: name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} - path: ${{github.workspace}}/TestResults/**/* + path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx build-only-android: name: Build only (Android) @@ -77,10 +77,6 @@ jobs: run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug build-only-ios: - # While this workflow technically *can* run, it fails as iOS builds are blocked by multiple issues. - # See https://github.com/ppy/osu-framework/issues/4677 for the details. - # The job can be unblocked once those issues are resolved and game deployments can happen again. - if: false name: Build only (iOS) runs-on: macos-latest timeout-minutes: 60 diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index b72803482d..c567adc0ae 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -10,3 +10,6 @@ T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods. M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. +M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. +M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. +M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. diff --git a/osu.Android.props b/osu.Android.props index d80fb858e9..0c922c09ac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,8 +51,8 @@ - - + + diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index e1e7e6ad18..3642f70a56 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -108,7 +108,10 @@ namespace osu.Desktop presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset - presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom"; + int onlineID = ruleset.Value.OnlineID; + bool isLegacyRuleset = onlineID >= 0 && onlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; + + presence.Assets.SmallImageKey = isLegacyRuleset ? $"mode_{onlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; client.SetPresence(presence); diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs new file mode 100644 index 0000000000..d6ef390a8f --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationRequest.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.LegacyIpc +{ + /// + /// A difficulty calculation request from the legacy client. + /// + /// + /// Synchronise any changes with osu!stable. + /// + public class LegacyIpcDifficultyCalculationRequest + { + public string BeatmapFile { get; set; } + public int RulesetId { get; set; } + public int Mods { get; set; } + } +} diff --git a/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs new file mode 100644 index 0000000000..7b9fae5797 --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyIpcDifficultyCalculationResponse.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.LegacyIpc +{ + /// + /// A difficulty calculation response returned to the legacy client. + /// + /// + /// Synchronise any changes with osu!stable. + /// + public class LegacyIpcDifficultyCalculationResponse + { + public double StarRating { get; set; } + } +} diff --git a/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs new file mode 100644 index 0000000000..0fa60e2068 --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyIpcMessage.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Platform; +using Newtonsoft.Json.Linq; + +namespace osu.Desktop.LegacyIpc +{ + /// + /// An that can be used to communicate to and from legacy clients. + /// + /// In order to deserialise types at either end, types must be serialised as their , + /// however this cannot be done since osu!stable and osu!lazer live in two different assemblies. + ///
+ /// To get around this, this class exists which serialises a payload () as an type, + /// which can be deserialised at either end because it is part of the core library (mscorlib / System.Private.CorLib). + /// The payload contains the data to be sent over the IPC channel. + ///
+ /// At either end, Json.NET deserialises the payload into a which is manually converted back into the expected type, + /// which then further contains another representing the data sent over the IPC channel whose type can likewise be lazily matched through + /// . + ///
+ ///
+ /// + /// Synchronise any changes with osu-stable. + /// + public class LegacyIpcMessage : IpcMessage + { + public LegacyIpcMessage() + { + // Types/assemblies are not inter-compatible, so always serialise/deserialise into objects. + base.Type = typeof(object).FullName; + } + + public new string Type => base.Type; // Hide setter. + + public new object Value + { + get => base.Value; + set => base.Value = new Data + { + MessageType = value.GetType().Name, + MessageData = value + }; + } + + public class Data + { + public string MessageType { get; set; } + public object MessageData { get; set; } + } + } +} diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs new file mode 100644 index 0000000000..97a4c57bf0 --- /dev/null +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -0,0 +1,121 @@ +// 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 System.Linq; +using Newtonsoft.Json.Linq; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; + +#nullable enable + +namespace osu.Desktop.LegacyIpc +{ + /// + /// Provides IPC to legacy osu! clients. + /// + public class LegacyTcpIpcProvider : TcpIpcProvider + { + private static readonly Logger logger = Logger.GetLogger("legacy-ipc"); + + public LegacyTcpIpcProvider() + : base(45357) + { + MessageReceived += msg => + { + try + { + logger.Add("Processing legacy IPC message..."); + logger.Add($" {msg.Value}", LogLevel.Debug); + + // See explanation in LegacyIpcMessage for why this is done this way. + var legacyData = ((JObject)msg.Value).ToObject(); + object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType); + + return new LegacyIpcMessage + { + Value = onLegacyIpcMessageReceived(value) + }; + } + catch (Exception ex) + { + logger.Add($"Processing IPC message failed: {msg.Value}", exception: ex); + return null; + } + }; + } + + private object parseObject(JObject value, string type) + { + switch (type) + { + case nameof(LegacyIpcDifficultyCalculationRequest): + return value.ToObject() + ?? throw new InvalidOperationException($"Failed to parse request {value}"); + + case nameof(LegacyIpcDifficultyCalculationResponse): + return value.ToObject() + ?? throw new InvalidOperationException($"Failed to parse request {value}"); + + default: + throw new ArgumentException($"Unsupported object type {type}"); + } + } + + private object onLegacyIpcMessageReceived(object message) + { + switch (message) + { + case LegacyIpcDifficultyCalculationRequest req: + try + { + var ruleset = getLegacyRulesetFromID(req.RulesetId); + + Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); + WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); + + return new LegacyIpcDifficultyCalculationResponse + { + StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating + }; + } + catch + { + return new LegacyIpcDifficultyCalculationResponse(); + } + + default: + throw new ArgumentException($"Unsupported message type {message}"); + } + } + + private static Ruleset getLegacyRulesetFromID(int rulesetId) + { + switch (rulesetId) + { + case 0: + return new OsuRuleset(); + + case 1: + return new TaikoRuleset(); + + case 2: + return new CatchRuleset(); + + case 3: + return new ManiaRuleset(); + + default: + throw new ArgumentException("Invalid ruleset id"); + } + } + } +} diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 898f7d5105..a9e3575a49 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using osu.Desktop.LegacyIpc; using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; @@ -18,8 +19,10 @@ namespace osu.Desktop { private const string base_game_name = @"osu"; + private static LegacyTcpIpcProvider legacyIpc; + [STAThread] - public static int Main(string[] args) + public static void Main(string[] args) { // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; @@ -69,14 +72,29 @@ namespace osu.Desktop throw new TimeoutException(@"IPC took too long to send"); } - return 0; + return; } // we want to allow multiple instances to be started when in debug. if (!DebugUtils.IsDebugBuild) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); - return 0; + return; + } + } + + if (host.IsPrimaryInstance) + { + try + { + Logger.Log("Starting legacy IPC provider..."); + legacyIpc = new LegacyTcpIpcProvider(); + legacyIpc.Bind(); + legacyIpc.StartAsync(); + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to start legacy IPC provider"); } } @@ -84,8 +102,6 @@ namespace osu.Desktop host.Run(new TournamentGame()); else host.Run(new OsuGameDesktop(args)); - - return 0; } } diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index d48c9e9661..7b60bc03e4 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -103,7 +103,10 @@ namespace osu.Desktop.Updater } else { + // In the case of an error, a separate notification will be displayed. notification.State = ProgressNotificationState.Cancelled; + notification.Close(); + Logger.Error(e, @"update failed!"); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 70b2c8c82a..14a4d02396 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Catch.Tests private Drawable setupSkinHierarchy(Drawable child, ISkin skin) { - var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info)); + var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.CreateInfo())); var testSkinProvider = new SkinProvidingContainer(skin); var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 24d2a786a0..91f5f93905 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Database; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Screens.Edit; @@ -55,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestDefaultSkin() { - AddStep("set default skin", () => skins.CurrentSkinInfo.Value = SkinInfo.Default); + AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLive()); } [Test] public void TestLegacySkin() { - AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLive()); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 9d1f5429a1..1aa20f4737 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.UI; @@ -46,12 +45,6 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private EditorBeatmap beatmap { get; set; } - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } - - [Resolved] - private Bindable working { get; set; } - [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index dc858fb54f..9fe1eb7932 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -7,16 +7,12 @@ using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit { public class ManiaSelectionHandler : EditorSelectionHandler { - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } - [Resolved] private HitObjectComposer composer { get; set; } diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs index 90d3c6c4c7..9f4963b022 100644 --- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs @@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.UI public JudgementResult Result { get; private set; } - [Resolved] - private Column column { get; set; } - private SkinnableDrawable skinnableExplosion; public PoolableHitExplosion() diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index e698766aac..d673b7a6ac 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("create slider", () => { - var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.Info); + var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo()); tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1"; Child = new SkinProvidingContainer(tintingSkin) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index d832411104..8b323eefa6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -26,12 +26,12 @@ namespace osu.Game.Rulesets.Osu.Mods public BindableFloat Scale { get; } = new BindableFloat(4) { Precision = 0.1f, - MinValue = 2, + MinValue = 1.5f, MaxValue = 10, }; [SettingSource("Style", "Change the animation style of the approach circles.", 1)] - public Bindable Style { get; } = new Bindable(); + public Bindable Style { get; } = new Bindable(AnimationStyle.Gravity); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -52,9 +52,18 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (style) { - default: + case AnimationStyle.Linear: return Easing.None; + case AnimationStyle.Gravity: + return Easing.InBack; + + case AnimationStyle.InOut1: + return Easing.InOutCubic; + + case AnimationStyle.InOut2: + return Easing.InOutQuint; + case AnimationStyle.Accelerate1: return Easing.In; @@ -64,9 +73,6 @@ namespace osu.Game.Rulesets.Osu.Mods case AnimationStyle.Accelerate3: return Easing.InQuint; - case AnimationStyle.Gravity: - return Easing.InBack; - case AnimationStyle.Decelerate1: return Easing.Out; @@ -76,16 +82,14 @@ namespace osu.Game.Rulesets.Osu.Mods case AnimationStyle.Decelerate3: return Easing.OutQuint; - case AnimationStyle.InOut1: - return Easing.InOutCubic; - - case AnimationStyle.InOut2: - return Easing.InOutQuint; + default: + throw new ArgumentOutOfRangeException(nameof(style), style, @"Unsupported animation style"); } } public enum AnimationStyle { + Linear, Gravity, InOut1, InOut2, diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 861b800038..16be20f7f3 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -149,9 +148,6 @@ namespace osu.Game.Rulesets.Taiko.UI centreHit.Colour = colours.Pink; } - [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } - public bool OnPressed(KeyBindingPressEvent e) { Drawable target = null; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 9c71466489..a73ae9dcdb 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -2,14 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; @@ -21,6 +27,14 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyScoreDecoderTest { + private CultureInfo originalCulture; + + [SetUp] + public void SetUp() + { + originalCulture = CultureInfo.CurrentCulture; + } + [Test] public void TestDecodeManiaReplay() { @@ -30,7 +44,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var score = decoder.Parse(resourceStream); - Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID); + Assert.AreEqual(3, score.ScoreInfo.Ruleset.OnlineID); Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]); Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]); @@ -44,6 +58,59 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestCultureInvariance() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = new TestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton) + } + } + }; + + // the "se" culture is used here, as it encodes the negative number sign as U+2212 MINUS SIGN, + // rather than the classic ASCII U+002D HYPHEN-MINUS. + CultureInfo.CurrentCulture = new CultureInfo("se"); + + var encodeStream = new MemoryStream(); + + var encoder = new LegacyScoreEncoder(score, beatmap); + encoder.Encode(encodeStream); + + var decodeStream = new MemoryStream(encodeStream.GetBuffer()); + + var decoder = new TestLegacyScoreDecoder(); + var decodedAfterEncode = decoder.Parse(decodeStream); + + Assert.Multiple(() => + { + Assert.That(decodedAfterEncode, Is.Not.Null); + + Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username)); + Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID)); + Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset)); + Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore)); + Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo)); + Assert.That(decodedAfterEncode.ScoreInfo.Date, Is.EqualTo(scoreInfo.Date)); + + Assert.That(decodedAfterEncode.Replay.Frames.Count, Is.EqualTo(1)); + }); + } + + [TearDown] + public void TearDown() + { + CultureInfo.CurrentCulture = originalCulture; + } + private class TestLegacyScoreDecoder : LegacyScoreDecoder { private static readonly Dictionary rulesets = new Ruleset[] diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 836cbfa6c5..6e2b9d20a8 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -584,7 +584,7 @@ namespace osu.Game.Tests.Beatmaps.IO { OnlineID = 1, Metadata = metadata, - Beatmaps = new List + Beatmaps = { new BeatmapInfo { @@ -596,7 +596,7 @@ namespace osu.Game.Tests.Beatmaps.IO { OnlineID = 2, Metadata = metadata, - Status = BeatmapSetOnlineStatus.Loved, + Status = BeatmapOnlineStatus.Loved, BaseDifficulty = difficulty } } @@ -1050,7 +1050,7 @@ namespace osu.Game.Tests.Beatmaps.IO private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) { - Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); + Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1)); } private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index f84dbca0be..3a82cbc785 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -25,9 +25,6 @@ namespace osu.Game.Tests.Beatmaps private BeatmapSetInfo importedSet; - [Resolved] - private BeatmapManager beatmaps { get; set; } - private TestBeatmapDifficultyCache difficultyCache; private IBindable starDifficultyBindable; @@ -100,8 +97,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyEqualsWithDifferentModInstances() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); @@ -110,8 +107,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyEqualsWithDifferentModOrder() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() }); Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); @@ -120,8 +117,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyDoesntEqualWithDifferentModSettings() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } }); Assert.That(key1, Is.Not.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode())); @@ -130,8 +127,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyEqualWithMatchingModSettings() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 71e5e9081c..a6edd6cb5f 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -12,6 +12,7 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; @@ -474,7 +475,7 @@ namespace osu.Game.Tests.Database } [Test] - public void TestImportThenDeleteThenImport() + public void TestImportThenDeleteThenImportOptimisedPath() { RunTestWithRealmAsync(async (realmFactory, storage) => { @@ -485,11 +486,39 @@ namespace osu.Game.Tests.Database deleteBeatmapSet(imported, realmFactory.Context); + Assert.IsTrue(imported.DeletePending); + var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + Assert.IsFalse(imported.DeletePending); + Assert.IsFalse(importedSecondTime.DeletePending); + }); + } + + [Test] + public void TestImportThenDeleteThenImportNonOptimisedPath() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage); + using var store = new RealmRulesetStore(realmFactory, storage); + + var imported = await LoadOszIntoStore(importer, realmFactory.Context); + + deleteBeatmapSet(imported, realmFactory.Context); + + Assert.IsTrue(imported.DeletePending); + + var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + Assert.IsFalse(imported.DeletePending); + Assert.IsFalse(importedSecondTime.DeletePending); }); } @@ -550,7 +579,7 @@ namespace osu.Game.Tests.Database new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { OnlineID = 2, - Status = BeatmapSetOnlineStatus.Loved, + Status = BeatmapOnlineStatus.Loved, } } }; @@ -823,7 +852,11 @@ namespace osu.Game.Tests.Database { IQueryable? resultSets = null; - waitForOrAssert(() => (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any(), + waitForOrAssert(() => + { + realm.Refresh(); + return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any(); + }, @"BeatmapSet did not import to the database in allocated time.", timeout); // ensure we were stored to beatmap database backing... @@ -836,16 +869,16 @@ namespace osu.Game.Tests.Database // ReSharper disable once PossibleUnintendedReferenceComparison IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set); - waitForOrAssert(() => queryBeatmaps().Count() == 12, @"Beatmaps did not import to the database in allocated time", timeout); - waitForOrAssert(() => queryBeatmapSets().Count() == 1, @"BeatmapSet did not import to the database in allocated time", timeout); + Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct"); + Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct"); - int countBeatmapSetBeatmaps = 0; - int countBeatmaps = 0; + int countBeatmapSetBeatmaps; + int countBeatmaps; - waitForOrAssert(() => - (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) == - (countBeatmaps = queryBeatmaps().Count()), - $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout); + Assert.AreEqual( + countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count, + countBeatmaps = queryBeatmaps().Count(), + $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps})."); foreach (RealmBeatmap b in set.Beatmaps) Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID)); @@ -867,5 +900,15 @@ namespace osu.Game.Tests.Database Assert.Fail(failureMessage); } + + public class NonOptimisedBeatmapImporter : BeatmapImporter + { + public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage) + : base(realmFactory, storage) + { + } + + protected override bool HasCustomHashFunction => true; + } } } diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 3e8b6091fd..2285b22a3a 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -5,6 +5,8 @@ using System; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Game.Database; +using osu.Game.Models; #nullable enable @@ -33,6 +35,39 @@ namespace osu.Game.Tests.Database }); } + /// + /// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks + /// due to context fetching semaphores. + /// + [Test] + public void TestNestedContextCreationWithSubscription() + { + RunTestWithRealm((realmFactory, _) => + { + bool callbackRan = false; + + using (var context = realmFactory.CreateContext()) + { + var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) => + { + using (realmFactory.CreateContext()) + { + callbackRan = true; + } + }); + + // Force the callback above to run. + using (realmFactory.CreateContext()) + { + } + + subscription?.Dispose(); + } + + Assert.IsTrue(callbackRan); + }); + } + [Test] public void TestBlockOperationsWithContention() { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 33aa1afb89..9b6769b788 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Models; using Realms; @@ -18,61 +17,57 @@ namespace osu.Game.Tests.Database public class RealmLiveTests : RealmTest { [Test] - public void TestLiveCastability() + public void TestLiveEquality() { RunTestWithRealm((realmFactory, _) => { - RealmLive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); + ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); - ILive iBeatmap = beatmap; + ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(); - Assert.AreEqual(0, iBeatmap.Value.Length); + Assert.AreEqual(beatmap, beatmap2); }); } [Test] - public void TestValueAccessWithOpenContext() + public void TestAccessAfterAttach() { RunTestWithRealm((realmFactory, _) => { - RealmLive? liveBeatmap = null; - Task.Factory.StartNew(() => - { - using (var threadContext = realmFactory.CreateContext()) - { - var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); - liveBeatmap = beatmap.ToLive(); - } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + var liveBeatmap = beatmap.ToLive(); - Debug.Assert(liveBeatmap != null); + using (var context = realmFactory.CreateContext()) + context.Write(r => r.Add(beatmap)); - Task.Factory.StartNew(() => - { - Assert.DoesNotThrow(() => - { - using (realmFactory.CreateContext()) - { - var resolved = liveBeatmap.Value; - - Assert.IsTrue(resolved.Realm.IsClosed); - Assert.IsTrue(resolved.IsValid); - - // can access properties without a crash. - Assert.IsFalse(resolved.Hidden); - } - }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); }); } + [Test] + public void TestAccessNonManaged() + { + var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + var liveBeatmap = beatmap.ToLive(); + + Assert.IsFalse(beatmap.Hidden); + Assert.IsFalse(liveBeatmap.Value.Hidden); + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + + Assert.Throws(() => liveBeatmap.PerformWrite(l => l.Hidden = true)); + + Assert.IsFalse(beatmap.Hidden); + Assert.IsFalse(liveBeatmap.Value.Hidden); + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + } + [Test] public void TestScopedReadWithoutContext() { RunTestWithRealm((realmFactory, _) => { - RealmLive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) @@ -101,7 +96,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - RealmLive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) @@ -122,12 +117,66 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestValueAccessNonManaged() + { + RunTestWithRealm((realmFactory, _) => + { + var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + var liveBeatmap = beatmap.ToLive(); + + Assert.DoesNotThrow(() => + { + var __ = liveBeatmap.Value; + }); + }); + } + + [Test] + public void TestValueAccessWithOpenContextFails() + { + RunTestWithRealm((realmFactory, _) => + { + ILive? liveBeatmap = null; + + Task.Factory.StartNew(() => + { + using (var threadContext = realmFactory.CreateContext()) + { + var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + + liveBeatmap = beatmap.ToLive(); + } + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + + Debug.Assert(liveBeatmap != null); + + Task.Factory.StartNew(() => + { + // Can't be used, without a valid context. + Assert.Throws(() => + { + var __ = liveBeatmap.Value; + }); + + // Can't be used, even from within a valid context. + using (realmFactory.CreateContext()) + { + Assert.Throws(() => + { + var __ = liveBeatmap.Value; + }); + } + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }); + } + [Test] public void TestValueAccessWithoutOpenContextFails() { RunTestWithRealm((realmFactory, _) => { - RealmLive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) @@ -159,8 +208,8 @@ namespace osu.Game.Tests.Database using (var updateThreadContext = realmFactory.CreateContext()) { - updateThreadContext.All().SubscribeForNotifications(gotChange); - RealmLive? liveBeatmap = null; + updateThreadContext.All().QueryAsyncWithNotifications(gotChange); + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { @@ -183,23 +232,22 @@ namespace osu.Game.Tests.Database Assert.AreEqual(0, updateThreadContext.All().Count()); Assert.AreEqual(0, changesTriggered); - var resolved = liveBeatmap.Value; - - // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. - Assert.AreEqual(2, updateThreadContext.All().Count()); - Assert.AreEqual(1, changesTriggered); - - // even though the realm that this instance was resolved for was closed, it's still valid. - Assert.IsTrue(resolved.Realm.IsClosed); - Assert.IsTrue(resolved.IsValid); - - // can access properties without a crash. - Assert.IsFalse(resolved.Hidden); - - updateThreadContext.Write(r => + liveBeatmap.PerformRead(resolved => { - // can use with the main context. - r.Remove(resolved); + // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. + // ReSharper disable once AccessToDisposedClosure + Assert.AreEqual(2, updateThreadContext.All().Count()); + Assert.AreEqual(1, changesTriggered); + + // can access properties without a crash. + Assert.IsFalse(resolved.Hidden); + + // ReSharper disable once AccessToDisposedClosure + updateThreadContext.Write(r => + { + // can use with the main context. + r.Remove(resolved); + }); }); } diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index f4e0838be1..cc7e8a0c97 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -45,9 +45,9 @@ namespace osu.Game.Tests.Database { var rulesets = new RealmRulesetStore(realmFactory, storage); - Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false); - Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false); - Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false); + Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); + Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); + Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged); }); } } diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs index f3a4f10210..f9b7bfa586 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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 Moq; @@ -13,7 +12,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -33,14 +31,10 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { - new BeatmapSetFileInfo - { - Filename = "abc123.mp4", - FileInfo = new FileInfo { Hash = "abcdef" } - } - }) + CheckTestHelpers.CreateMockFile("mp4"), + } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 05bfae7e63..bb560054a3 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System; using System.IO; using System.Linq; using JetBrains.Annotations; @@ -12,7 +12,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -25,25 +24,17 @@ namespace osu.Game.Tests.Editing.Checks [SetUp] public void Setup() { + var file = CheckTestHelpers.CreateMockFile("jpg"); + check = new CheckBackgroundQuality(); beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { - Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + Metadata = new BeatmapMetadata { BackgroundFile = file.Filename }, BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] - { - new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo - { - Hash = "abcdef" - } - } - }) + Files = { file } } } }; @@ -54,7 +45,7 @@ namespace osu.Game.Tests.Editing.Checks { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = string.Empty; - var context = getContext(null, new MemoryStream(System.Array.Empty())); + var context = getContext(null, new MemoryStream(Array.Empty())); Assert.That(check.Run(context), Is.Empty); } diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 70e4c76b19..f36454aa71 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.IO; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -22,22 +20,17 @@ namespace osu.Game.Tests.Editing.Checks [SetUp] public void Setup() { + var file = CheckTestHelpers.CreateMockFile("jpg"); + check = new CheckBackgroundPresence(); beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { - Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + Metadata = new BeatmapMetadata { BackgroundFile = file.Filename }, BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] - { - new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo { Hash = "abcdef" } - } - }) + Files = { file } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs new file mode 100644 index 0000000000..f702921986 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.IO; + +namespace osu.Game.Tests.Editing.Checks +{ + public static class CheckTestHelpers + { + public static BeatmapSetFileInfo CreateMockFile(string extension) => + new BeatmapSetFileInfo + { + Filename = $"abc123.{extension}", + FileInfo = new FileInfo { Hash = "abcdef" } + }; + } +} diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 9b090591bc..8adf0d3764 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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 ManagedBass; @@ -14,7 +13,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK.Audio; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -34,14 +32,7 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] - { - new BeatmapSetFileInfo - { - Filename = "abc123.wav", - FileInfo = new FileInfo { Hash = "abcdef" } - } - }) + Files = { CheckTestHelpers.CreateMockFile("wav") } } } }; @@ -55,11 +46,7 @@ namespace osu.Game.Tests.Editing.Checks public void TestDifferentExtension() { beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); - beatmap.BeatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo { Hash = "abcdef" } - }); + beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg")); // Should fail to load, but not produce an error due to the extension not being expected to load. Assert.IsEmpty(check.Run(getContext(null, allowMissing: true))); diff --git a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs index c9adc030c1..79d00e6a60 100644 --- a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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 Moq; @@ -10,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -30,14 +28,10 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { - new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo { Hash = "abcdef" } - } - }) + CheckTestHelpers.CreateMockFile("jpg"), + } } } }; diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 3bf6aaac7a..88f35976ad 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Gameplay private class TestSkin : LegacySkin { public TestSkin(string resourceName, IStorageResourceProvider resources) - : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), resources, "skin.ini") + : base(DefaultLegacySkin.CreateInfo(), new TestResourceStore(resourceName), resources, "skin.ini") { } } diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs index b612899d79..28937b2120 100644 --- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs +++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs @@ -18,9 +18,6 @@ namespace osu.Game.Tests.Input [Resolved] private FrameworkConfigManager frameworkConfigManager { get; set; } - [Resolved] - private OsuConfigManager osuConfigManager { get; set; } - [TestCase(WindowMode.Windowed)] [TestCase(WindowMode.Borderless)] public void TestDisableConfining(WindowMode windowMode) diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index 95af21eb5f..2d0bda88e4 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -29,9 +29,9 @@ namespace osu.Game.Tests.Models { var mock = new Mock(); - mock.Setup(m => m.Metadata!.Artist).Returns("artist"); - mock.Setup(m => m.Metadata!.Title).Returns("title"); - mock.Setup(m => m.Metadata!.Author.Username).Returns("author"); + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns("author"); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); } @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Models { var mock = new Mock(); - mock.Setup(m => m.Metadata!.Artist).Returns("artist"); - mock.Setup(m => m.Metadata!.Title).Returns("title"); - mock.Setup(m => m.Metadata!.Author.Username).Returns(string.Empty); + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title")); } diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index e458e66ab7..ae8eec2629 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Tests.NonVisual { @@ -20,8 +22,10 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(1, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) } + }, combinations); } [Test] @@ -29,9 +33,11 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(2, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) } + }, combinations); } [Test] @@ -39,14 +45,13 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(4, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); - Assert.IsTrue(combinations[2] is MultiMod); - Assert.IsTrue(combinations[3] is ModB); - - Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); - Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) }, + new[] { typeof(ModA), typeof(ModB) }, + new[] { typeof(ModB) } + }, combinations); } [Test] @@ -54,10 +59,12 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); - Assert.IsTrue(combinations[2] is ModIncompatibleWithA); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) }, + new[] { typeof(ModIncompatibleWithA) } + }, combinations); } [Test] @@ -65,22 +72,17 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(8, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); - Assert.IsTrue(combinations[2] is MultiMod); - Assert.IsTrue(combinations[3] is ModB); - Assert.IsTrue(combinations[4] is MultiMod); - Assert.IsTrue(combinations[5] is ModIncompatibleWithA); - Assert.IsTrue(combinations[6] is MultiMod); - Assert.IsTrue(combinations[7] is ModIncompatibleWithAAndB); - - Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); - Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); - Assert.IsTrue(((MultiMod)combinations[4]).Mods[0] is ModB); - Assert.IsTrue(((MultiMod)combinations[4]).Mods[1] is ModIncompatibleWithA); - Assert.IsTrue(((MultiMod)combinations[6]).Mods[0] is ModIncompatibleWithA); - Assert.IsTrue(((MultiMod)combinations[6]).Mods[1] is ModIncompatibleWithAAndB); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) }, + new[] { typeof(ModA), typeof(ModB) }, + new[] { typeof(ModB) }, + new[] { typeof(ModB), typeof(ModIncompatibleWithA) }, + new[] { typeof(ModIncompatibleWithA) }, + new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }, + new[] { typeof(ModIncompatibleWithAAndB) }, + }, combinations); } [Test] @@ -88,10 +90,12 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModAofA); - Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModAofA) }, + new[] { typeof(ModIncompatibleWithAofA) } + }, combinations); } [Test] @@ -99,17 +103,13 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModC())).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(4, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); - Assert.IsTrue(combinations[2] is MultiMod); - Assert.IsTrue(combinations[3] is MultiMod); - - Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); - Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); - Assert.IsTrue(((MultiMod)combinations[2]).Mods[2] is ModC); - Assert.IsTrue(((MultiMod)combinations[3]).Mods[0] is ModB); - Assert.IsTrue(((MultiMod)combinations[3]).Mods[1] is ModC); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) }, + new[] { typeof(ModA), typeof(ModB), typeof(ModC) }, + new[] { typeof(ModB), typeof(ModC) } + }, combinations); } [Test] @@ -117,13 +117,12 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModIncompatibleWithA())).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); - Assert.IsTrue(combinations[2] is MultiMod); - - Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModB); - Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModIncompatibleWithA); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) }, + new[] { typeof(ModB), typeof(ModIncompatibleWithA) } + }, combinations); } [Test] @@ -131,13 +130,28 @@ namespace osu.Game.Tests.NonVisual { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModA(), new ModB())).CreateDifficultyAdjustmentModCombinations(); - Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is ModNoMod); - Assert.IsTrue(combinations[1] is ModA); - Assert.IsTrue(combinations[2] is MultiMod); + assertCombinations(new[] + { + new[] { typeof(ModNoMod) }, + new[] { typeof(ModA) }, + new[] { typeof(ModA), typeof(ModB) } + }, combinations); + } - Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); - Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + private void assertCombinations(Type[][] expectedCombinations, Mod[] actualCombinations) + { + Assert.AreEqual(expectedCombinations.Length, actualCombinations.Length); + + Assert.Multiple(() => + { + for (int i = 0; i < expectedCombinations.Length; ++i) + { + Type[] expectedTypes = expectedCombinations[i]; + Type[] actualTypes = ModUtils.FlattenMod(actualCombinations[i]).Select(m => m.GetType()).ToArray(); + + Assert.That(expectedTypes, Is.EquivalentTo(actualTypes)); + } + }); } private class ModA : Mod diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index ee1feeca8d..55378043e6 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.NonVisual.Filtering { private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { - Ruleset = new RulesetInfo { ID = 5 }, + Ruleset = new RulesetInfo { OnlineID = 5 }, StarRating = 4.0d, BaseDifficulty = new BeatmapDifficulty { @@ -38,7 +38,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Length = 2500, BPM = 160, BeatDivisor = 12, - Status = BeatmapSetOnlineStatus.Loved + Status = BeatmapOnlineStatus.Loved }; [Test] @@ -57,7 +57,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { ID = 6 } + Ruleset = new RulesetInfo { OnlineID = 6 } }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); carouselItem.Filter(criteria); @@ -70,7 +70,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { ID = 6 }, + Ruleset = new RulesetInfo { OnlineID = 6 }, AllowConvertedBeatmaps = true }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); @@ -86,7 +86,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { ID = 6 }, + Ruleset = new RulesetInfo { OnlineID = 6 }, AllowConvertedBeatmaps = true, ApproachRate = new FilterCriteria.OptionalRange { @@ -107,7 +107,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { ID = 6 }, + Ruleset = new RulesetInfo { OnlineID = 6 }, AllowConvertedBeatmaps = true, BPM = new FilterCriteria.OptionalRange { @@ -132,7 +132,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { ID = 6 }, + Ruleset = new RulesetInfo { OnlineID = 6 }, AllowConvertedBeatmaps = true, SearchText = terms }; diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index df42c70c87..460f89528b 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -162,9 +162,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim()); Assert.AreEqual(4, filterCriteria.SearchTerms.Length); - Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); + Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive); - Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); + Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); } diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index abe5664737..42305ccd81 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -16,6 +16,27 @@ namespace osu.Game.Tests.NonVisual.Multiplayer [HeadlessTest] public class StatefulMultiplayerClientTest : MultiplayerTestScene { + [Test] + public void TestUserAddedOnJoin() + { + var user = new APIUser { Id = 33 }; + + AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + } + + [Test] + public void TestUserRemovedOnLeave() + { + var user = new APIUser { Id = 44 }; + + AddStep("add user", () => Client.AddUser(user)); + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + + AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); + AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); + } + [Test] public void TestPlayingUserTracking() { @@ -72,7 +93,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer RoomManager.CreateRoom(newRoom); }); - AddUntilStep("wait for room join", () => Client.Room != null); + AddUntilStep("wait for room join", () => RoomJoined); checkPlayingUserCount(1); } diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs similarity index 94% rename from osu.Game.Tests/Online/TestSceneBeatmapManager.cs rename to osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs index fc1b4f224d..4e77973655 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs @@ -12,9 +12,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public class TestSceneBeatmapManager : OsuTestScene + public class TestSceneBeatmapDownloading : OsuTestScene { - private BeatmapManager beatmaps; + private BeatmapModelDownloader beatmaps; private ProgressNotification recentNotification; private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Online }; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) + private void load(BeatmapModelDownloader beatmaps) { this.beatmaps = beatmaps; diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index b66da028f1..24824b1e23 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -33,6 +33,7 @@ namespace osu.Game.Tests.Online { private RulesetStore rulesets; private TestBeatmapManager beatmaps; + private TestBeatmapModelDownloader beatmapDownloader; private string testBeatmapFile; private BeatmapInfo testBeatmapInfo; @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Online { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } [SetUp] @@ -80,13 +82,13 @@ namespace osu.Game.Tests.Online AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("start downloading", () => beatmaps.Download(testBeatmapSet)); + AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet)); addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f)); - AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f)); + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f)); addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f)); - AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); + AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); @@ -143,7 +145,10 @@ namespace osu.Game.Tests.Online var beatmap = decoder.Decode(reader); info = beatmap.BeatmapInfo; - info.BeatmapSet.Beatmaps = new List { info }; + + Debug.Assert(info.BeatmapSet != null); + + info.BeatmapSet.Beatmaps.Add(info); info.BeatmapSet.Metadata = info.Metadata; info.MD5Hash = stream.ComputeMD5Hash(); info.Hash = stream.ComputeSHA2Hash(); @@ -168,22 +173,6 @@ namespace osu.Game.Tests.Online return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host); } - protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter manager, IAPIProvider api, GameHost host) - { - return new TestBeatmapModelDownloader(manager, api, host); - } - - internal class TestBeatmapModelDownloader : BeatmapModelDownloader - { - public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) - : base(importer, apiProvider, gameHost) - { - } - - protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) - => new TestDownloadRequest(set); - } - internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; @@ -202,6 +191,17 @@ namespace osu.Game.Tests.Online } } + internal class TestBeatmapModelDownloader : BeatmapModelDownloader + { + public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) + : base(importer, apiProvider) + { + } + + protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) + => new TestDownloadRequest(set); + } + private class TestDownloadRequest : ArchiveDownloadRequest { public new void SetProgress(float progress) => base.SetProgress(progress); diff --git a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs deleted file mode 100644 index 5a621ecf84..0000000000 --- a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Framework.Testing; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Tests.Visual.Multiplayer; - -namespace osu.Game.Tests.OnlinePlay -{ - [HeadlessTest] - public class StatefulMultiplayerClientTest : MultiplayerTestScene - { - [Test] - public void TestUserAddedOnJoin() - { - var user = new APIUser { Id = 33 }; - - AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); - } - - [Test] - public void TestUserRemovedOnLeave() - { - var user = new APIUser { Id = 44 }; - - AddStep("add user", () => Client.AddUser(user)); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); - - AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); - AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); - } - } -} diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index dff9478852..440d5e701f 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -2,10 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; +using System.Text; +using System.Threading; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; namespace osu.Game.Tests.Resources { @@ -56,5 +64,78 @@ namespace osu.Game.Tests.Resources } private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz"); + + private static int importId; + + /// + /// Create a test beatmap set model. + /// + /// Number of difficulties. If null, a random number between 1 and 20 will be used. + /// Rulesets to cycle through when creating difficulties. If null, osu! ruleset will be used. + public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null) + { + int j = 0; + RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo; + + int setId = Interlocked.Increment(ref importId); + + var metadata = new BeatmapMetadata + { + // Create random metadata, then we can check if sorting works based on these + Artist = "Some Artist " + RNG.Next(0, 9), + Title = $"Some Song (set id {setId}) {Guid.NewGuid()}", + AuthorString = "Some Guy " + RNG.Next(0, 9), + }; + + var beatmapSet = new BeatmapSetInfo + { + OnlineID = setId, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + DateAdded = DateTimeOffset.UtcNow, + Metadata = metadata + }; + + foreach (var b in getBeatmaps(difficultyCount ?? RNG.Next(1, 20))) + beatmapSet.Beatmaps.Add(b); + + return beatmapSet; + + IEnumerable getBeatmaps(int count) + { + for (int i = 0; i < count; i++) + { + int beatmapId = setId * 1000 + i; + + int length = RNG.Next(30000, 200000); + double bpm = RNG.NextSingle(80, 200); + + float diff = (float)i / count * 10; + + string version = "Normal"; + if (diff > 6.6) + version = "Insane"; + else if (diff > 3.3) + version = "Hard"; + + var rulesetInfo = getRuleset(); + + yield return new BeatmapInfo + { + OnlineID = beatmapId, + DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", + StarRating = diff, + Length = length, + BPM = bpm, + Ruleset = rulesetInfo, + RulesetID = rulesetInfo.ID ?? -1, + Metadata = metadata, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = diff, + } + }; + } + } + } } } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index be7803734e..0dee0f89ea 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO var scoreManager = osu.Dependencies.Get(); beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))); - Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); + Assert.That(scoreManager.Query(s => s.Equals(imported)).DeletePending, Is.EqualTo(true)); var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index ecc9c92025..f2ce002650 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Game.Database; using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Skinning; @@ -163,32 +164,109 @@ namespace osu.Game.Tests.Skins.IO assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", osu); }); + [Test] + public Task TestExportThenImportDefaultSkin() => runSkinTest(osu => + { + var skinManager = osu.Dependencies.Get(); + + skinManager.EnsureMutableSkin(); + + MemoryStream exportStream = new MemoryStream(); + + Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID; + + skinManager.CurrentSkinInfo.Value.PerformRead(s => + { + Assert.IsFalse(s.Protected); + Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType()); + + new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream); + + Assert.Greater(exportStream.Length, 0); + }); + + var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); + + imported.Result.PerformRead(s => + { + Assert.IsFalse(s.Protected); + Assert.AreNotEqual(originalSkinId, s.ID); + Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType()); + }); + + return Task.CompletedTask; + }); + + [Test] + public Task TestExportThenImportClassicSkin() => runSkinTest(osu => + { + var skinManager = osu.Dependencies.Get(); + + skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo; + + skinManager.EnsureMutableSkin(); + + MemoryStream exportStream = new MemoryStream(); + + Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID; + + skinManager.CurrentSkinInfo.Value.PerformRead(s => + { + Assert.IsFalse(s.Protected); + Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType()); + + new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream); + + Assert.Greater(exportStream.Length, 0); + }); + + var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); + + imported.Result.PerformRead(s => + { + Assert.IsFalse(s.Protected); + Assert.AreNotEqual(originalSkinId, s.ID); + Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType()); + }); + + return Task.CompletedTask; + }); + #endregion - private void assertCorrectMetadata(SkinInfo import1, string name, string creator, OsuGameBase osu) + private void assertCorrectMetadata(ILive import1, string name, string creator, OsuGameBase osu) { - Assert.That(import1.Name, Is.EqualTo(name)); - Assert.That(import1.Creator, Is.EqualTo(creator)); + import1.PerformRead(i => + { + Assert.That(i.Name, Is.EqualTo(name)); + Assert.That(i.Creator, Is.EqualTo(creator)); - // for extra safety let's reconstruct the skin, reading from the skin.ini. - var instance = import1.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager))); + // for extra safety let's reconstruct the skin, reading from the skin.ini. + var instance = i.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager))); - Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name)); - Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator)); + Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name)); + Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator)); + }); } - private void assertImportedBoth(SkinInfo import1, SkinInfo import2) + private void assertImportedBoth(ILive import1, ILive import2) { - Assert.That(import2.ID, Is.Not.EqualTo(import1.ID)); - Assert.That(import2.Hash, Is.Not.EqualTo(import1.Hash)); - Assert.That(import2.Files.Select(f => f.FileInfoID), Is.Not.EquivalentTo(import1.Files.Select(f => f.FileInfoID))); + import1.PerformRead(i1 => import2.PerformRead(i2 => + { + Assert.That(i2.ID, Is.Not.EqualTo(i1.ID)); + Assert.That(i2.Hash, Is.Not.EqualTo(i1.Hash)); + Assert.That(i2.Files.First(), Is.Not.EqualTo(i1.Files.First())); + })); } - private void assertImportedOnce(SkinInfo import1, SkinInfo import2) + private void assertImportedOnce(ILive import1, ILive import2) { - Assert.That(import2.ID, Is.EqualTo(import1.ID)); - Assert.That(import2.Hash, Is.EqualTo(import1.Hash)); - Assert.That(import2.Files.Select(f => f.FileInfoID), Is.EquivalentTo(import1.Files.Select(f => f.FileInfoID))); + import1.PerformRead(i1 => import2.PerformRead(i2 => + { + Assert.That(i2.ID, Is.EqualTo(i1.ID)); + Assert.That(i2.Hash, Is.EqualTo(i1.Hash)); + Assert.That(i2.Files.First(), Is.EqualTo(i1.Files.First())); + })); } private MemoryStream createEmptyOsk() @@ -255,10 +333,10 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); - return (await skinManager.Import(archive)).Value; + return await skinManager.Import(archive); } } } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 10f1ab31df..09535b76e3 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins private void load() { var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; - skin = skins.GetSkin(imported.Value); + skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } [Test] diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index bb9b705c7e..330d3dd2ae 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Testing { // temporary ID to let RulesetConfigCache pass our // config manager to the ruleset dependencies. - RulesetInfo.ID = -1; + RulesetInfo.OnlineID = -1; } public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources"); diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index ec16578b71..bdd1b92c8d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -122,7 +123,7 @@ namespace osu.Game.Tests.Visual.Background private void setCustomSkin() { // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. - AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo { ID = 5 }); + AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLive()); } private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault()); diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index addec15881..f835d21603 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -6,12 +6,14 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -23,6 +25,11 @@ namespace osu.Game.Tests.Visual.Beatmaps { public class TestSceneBeatmapCard : OsuTestScene { + /// + /// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources. + /// + private const int online_id = 163112; + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private APIBeatmapSet[] testCases; @@ -38,11 +45,10 @@ namespace osu.Game.Tests.Visual.Beatmaps var normal = CreateAPIBeatmapSet(Ruleset.Value); normal.HasVideo = true; normal.HasStoryboard = true; - normal.OnlineID = 241526; var withStatistics = CreateAPIBeatmapSet(Ruleset.Value); withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats"; - withStatistics.Status = BeatmapSetOnlineStatus.Approved; + withStatistics.Status = BeatmapOnlineStatus.Approved; withStatistics.FavouriteCount = 284_239; withStatistics.PlayCount = 999_001; withStatistics.Ranked = DateTimeOffset.Now.AddDays(-45); @@ -63,7 +69,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var someDifficulties = getManyDifficultiesBeatmapSet(11); someDifficulties.Title = someDifficulties.TitleUnicode = "favourited"; someDifficulties.Title = someDifficulties.TitleUnicode = "some difficulties"; - someDifficulties.Status = BeatmapSetOnlineStatus.Qualified; + someDifficulties.Status = BeatmapOnlineStatus.Qualified; someDifficulties.HasFavourited = true; someDifficulties.FavouriteCount = 1; someDifficulties.NominationStatus = new BeatmapSetNominationStatus @@ -73,7 +79,7 @@ namespace osu.Game.Tests.Visual.Beatmaps }; var manyDifficulties = getManyDifficultiesBeatmapSet(100); - manyDifficulties.Status = BeatmapSetOnlineStatus.Pending; + manyDifficulties.Status = BeatmapOnlineStatus.Pending; var explicitMap = CreateAPIBeatmapSet(Ruleset.Value); explicitMap.Title = someDifficulties.TitleUnicode = "explicit beatmap"; @@ -106,6 +112,9 @@ namespace osu.Game.Tests.Visual.Beatmaps explicitFeaturedMap, longName }; + + foreach (var testCase in testCases) + testCase.OnlineID = online_id; } private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet @@ -191,9 +200,9 @@ namespace osu.Game.Tests.Visual.Beatmaps private void ensureSoleilyRemoved() { AddUntilStep("ensure manager loaded", () => beatmaps != null); - AddStep("remove soleily", () => + AddStep("remove map", () => { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == online_id); if (beatmap != null) beatmaps.Delete(beatmap); }); @@ -220,7 +229,7 @@ namespace osu.Game.Tests.Visual.Beatmaps new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Child = new ReverseChildIDFillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -241,6 +250,17 @@ namespace osu.Game.Tests.Visual.Beatmaps } [Test] - public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + public void TestNormal() + { + createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + + AddToggleStep("toggle expanded state", expanded => + { + var card = this.ChildrenOfType().Last(); + if (!card.Expanded.Disabled) + card.Expanded.Value = expanded; + }); + AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType().ForEach(card => card.Expanded.Disabled = disabled)); + } } } diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs new file mode 100644 index 0000000000..aec75884d6 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardDifficultyList : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + var beatmapSet = new APIBeatmapSet + { + Beatmaps = new[] + { + new APIBeatmap { RulesetID = 1, StarRating = 5.76, DifficultyName = "Oni" }, + new APIBeatmap { RulesetID = 1, StarRating = 3.20, DifficultyName = "Muzukashii" }, + new APIBeatmap { RulesetID = 1, StarRating = 2.45, DifficultyName = "Futsuu" }, + + new APIBeatmap { RulesetID = 0, StarRating = 2.04, DifficultyName = "Normal" }, + new APIBeatmap { RulesetID = 0, StarRating = 3.51, DifficultyName = "Hard" }, + new APIBeatmap { RulesetID = 0, StarRating = 5.25, DifficultyName = "Insane" }, + + new APIBeatmap { RulesetID = 2, StarRating = 2.64, DifficultyName = "Salad" }, + new APIBeatmap { RulesetID = 2, StarRating = 3.56, DifficultyName = "Platter" }, + new APIBeatmap { RulesetID = 2, StarRating = 4.65, DifficultyName = "Rain" }, + + new APIBeatmap { RulesetID = 3, StarRating = 1.93, DifficultyName = "[7K] Normal" }, + new APIBeatmap { RulesetID = 3, StarRating = 3.18, DifficultyName = "[7K] Hyper" }, + new APIBeatmap { RulesetID = 3, StarRating = 4.82, DifficultyName = "[7K] Another" }, + + new APIBeatmap { RulesetID = 4, StarRating = 9.99, DifficultyName = "Unknown?!" }, + } + }; + + Child = new Container + { + Width = 300, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2 + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Child = new BeatmapCardDifficultyList(beatmapSet) + } + } + }; + } + } +} diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs new file mode 100644 index 0000000000..a5b52f75f6 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Overlays; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardThumbnail : OsuManualInputManagerTestScene + { + private PlayButton playButton => this.ChildrenOfType().Single(); + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestThumbnailPreview() + { + BeatmapCardThumbnail thumbnail = null; + + AddStep("create thumbnail", () => + { + var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online. + + Child = thumbnail = new BeatmapCardThumbnail(beatmapSet) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200) + }; + }); + AddStep("enable dim", () => thumbnail.Dimmed.Value = true); + AddUntilStep("button visible", () => playButton.IsPresent); + + AddStep("click button", () => + { + InputManager.MoveMouseTo(playButton); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value); + iconIs(FontAwesome.Solid.Stop); + + AddStep("click again", () => + { + InputManager.MoveMouseTo(playButton); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for stop", () => !playButton.Playing.Value && playButton.Enabled.Value); + iconIs(FontAwesome.Solid.Play); + + AddStep("click again", () => + { + InputManager.MoveMouseTo(playButton); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value); + iconIs(FontAwesome.Solid.Stop); + + AddStep("disable dim", () => thumbnail.Dimmed.Value = false); + AddWaitStep("wait some", 3); + AddAssert("button still visible", () => playButton.IsPresent); + + // The track plays in real-time, so we need to check for progress in increments to avoid timeout. + AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.25); + AddUntilStep("progress > 0.5", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.5); + AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.75); + + AddUntilStep("wait for track to end", () => !playButton.Playing.Value); + AddUntilStep("button hidden", () => !playButton.IsPresent); + } + + private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType().Any(icon => icon.Icon.Equals(usage))); + } +} diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs index c48b63ac89..8132fe6ab8 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Beatmaps Origin = Anchor.Centre, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10), - ChildrenEnumerable = Enum.GetValues(typeof(BeatmapSetOnlineStatus)).Cast().Select(status => new BeatmapSetOnlineStatusPill + ChildrenEnumerable = Enum.GetValues(typeof(BeatmapOnlineStatus)).Cast().Select(status => new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index e1e869cfbf..f89be0adf3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -69,7 +69,10 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - PushAndConfirm(() => new PlaySongSelect()); + Screens.Select.SongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new PlaySongSelect()); + AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); AddStep("Open options", () => InputManager.Key(Key.F3)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 7398527f57..cccc962a3f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEmptyLegacyBeatmapSkinFallsBack() { - CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null)); + CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null)); AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } @@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("setup skins", () => { - skinManager.CurrentSkinInfo.Value = gameCurrentSkin; + skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLive(); currentBeatmapSkin = getBeatmapSkin(); }); }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index cb5058779c..324a132120 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = rulesetId } }); + createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } }); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index dcc193669b..e6361a15d7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -43,83 +43,88 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); - [SetUp] - public void SetUp() => Schedule(() => + [SetUpSteps] + public void SetUpSteps() { - replay = new Replay(); + AddStep("Reset recorder state", cleanUpState); - Add(new GridContainer + AddStep("Setup containers", () => { - RelativeSizeAxes = Axes.Both, - Content = new[] + replay = new Replay(); + + Add(new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + new Drawable[] { - Recorder = recorder = new TestReplayRecorder(new Score + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Replay = replay, - ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } - }) - { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Recorder = recorder = new TestReplayRecorder(new Score { - new Box + Replay = replay, + ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } + }) + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Recording", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Recording", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] { - ReplayInputHandler = new TestFramedReplayInputHandler(replay) + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + ReplayInputHandler = new TestFramedReplayInputHandler(replay) { - new Box + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Playback", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Playback", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } } } - } + }); }); - }); + } [Test] public void TestBasic() @@ -184,7 +189,14 @@ namespace osu.Game.Tests.Visual.Gameplay [TearDownSteps] public void TearDown() { - AddStep("stop recorder", () => recorder.Expire()); + AddStep("stop recorder", cleanUpState); + } + + private void cleanUpState() + { + // Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`. + recorder?.RemoveAndDisposeImmediately(); + recorder = null; } public class TestFramedReplayInputHandler : FramedReplayInputHandler diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs deleted file mode 100644 index 3f7155f1e2..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ /dev/null @@ -1,228 +0,0 @@ -// 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 System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Framework.Input.StateChanges; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; -using osu.Game.Replays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; -using osu.Game.Screens.Play; -using osu.Game.Tests.Visual.UserInterface; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestSceneReplayRecording : OsuTestScene - { - private readonly TestRulesetInputManager playbackManager; - - private readonly TestRulesetInputManager recordingManager; - - [Cached] - private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); - - public TestSceneReplayRecording() - { - Replay replay = new Replay(); - - Add(new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Recorder = new TestReplayRecorder(new Score - { - Replay = replay, - ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } - }) - { - ScreenSpaceToGamefield = pos => recordingManager?.ToLocalSpace(pos) ?? Vector2.Zero, - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Recording", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - ReplayInputHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager?.ToScreenSpace(pos) ?? Vector2.Zero, - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Playback", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestConsumer() - } - }, - } - } - } - }); - } - - protected override void Update() - { - base.Update(); - - playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); - } - } - - public class TestFramedReplayInputHandler : FramedReplayInputHandler - { - public TestFramedReplayInputHandler(Replay replay) - : base(replay) - { - } - - public override void CollectPendingInputs(List inputs) - { - inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); - inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); - } - } - - public class TestConsumer : CompositeDrawable, IKeyBindingHandler - { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); - - private readonly Box box; - - public TestConsumer() - { - Size = new Vector2(30); - - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - box = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - }; - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - Position = e.MousePosition; - return base.OnMouseMove(e); - } - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat) - return false; - - box.Colour = Color4.White; - return true; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - box.Colour = Color4.Black; - } - } - - public class TestRulesetInputManager : RulesetInputManager - { - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - - protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - => new TestKeyBindingContainer(); - - internal class TestKeyBindingContainer : KeyBindingContainer - { - public override IEnumerable DefaultKeyBindings => new[] - { - new KeyBinding(InputKey.MouseLeft, TestAction.Down), - }; - } - } - - public class TestReplayFrame : ReplayFrame - { - public Vector2 Position; - - public List Actions = new List(); - - public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) - : base(time) - { - Position = position; - Actions.AddRange(actions); - } - } - - public enum TestAction - { - Down, - } - - internal class TestReplayRecorder : ReplayRecorder - { - public TestReplayRecorder(Score target) - : base(target) - { - } - - protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index a0b27755b7..a0602e21b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Skinning; using osu.Game.Skinning.Editor; namespace osu.Game.Tests.Visual.Gameplay @@ -16,9 +14,6 @@ namespace osu.Game.Tests.Visual.Gameplay { private SkinEditor skinEditor; - [Resolved] - private SkinManager skinManager { get; set; } - protected override bool Autoplay => true; [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 723e35ed55..3074a91dc6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -36,9 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); - [Resolved] - private OsuConfigManager config { get; set; } - [Test] public void TestComboCounterIncrementing() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index 2a82c65c7c..409cec4cf6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestClientSendsCorrectRuleset() { AddUntilStep("spectator client sending frames", () => spectatorClient.PlayingUserStates.ContainsKey(dummy_user_id)); - AddAssert("spectator client sent correct ruleset", () => spectatorClient.PlayingUserStates[dummy_user_id].RulesetID == Ruleset.Value.ID); + AddAssert("spectator client sent correct ruleset", () => spectatorClient.PlayingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } public override void TearDownSteps() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 5fbccd54c8..f7e9a1fe16 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; - private readonly ManualClock manualClock = new ManualClock(); + private ManualClock manualClock; private OsuSpriteText latencyDisplay; @@ -66,113 +66,121 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); - [SetUp] - public void SetUp() => Schedule(() => + [SetUpSteps] + public void SetUpSteps() { - replay = new Replay(); + AddStep("Reset recorder state", cleanUpState); - users.BindTo(spectatorClient.PlayingUsers); - users.BindCollectionChanged((obj, args) => + AddStep("Setup containers", () => { - switch (args.Action) + replay = new Replay(); + manualClock = new ManualClock(); + + spectatorClient.OnNewFrames += onNewFrames; + + users.BindTo(spectatorClient.PlayingUsers); + users.BindCollectionChanged((obj, args) => { - case NotifyCollectionChangedAction.Add: - Debug.Assert(args.NewItems != null); - - foreach (int user in args.NewItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.WatchUser(user); - } - - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(args.OldItems != null); - - foreach (int user in args.OldItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.StopWatchingUser(user); - } - - break; - } - }, true); - - spectatorClient.OnNewFrames += onNewFrames; - - Add(new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] + switch (args.Action) { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + + foreach (int user in args.NewItems) + { + if (user == api.LocalUser.Value.Id) + spectatorClient.WatchUser(user); + } + + break; + + case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + + foreach (int user in args.OldItems) + { + if (user == api.LocalUser.Value.Id) + spectatorClient.StopWatchingUser(user); + } + + break; + } + }, true); + + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { - Recorder = recorder = new TestReplayRecorder + new Drawable[] { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - new Box + Recorder = recorder = new TestReplayRecorder + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container { - Colour = Color4.Brown, RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Sending", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } }, - new OsuSpriteText - { - Text = "Sending", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() } }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Clock = new FramedClock(manualClock), + ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Receiving", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } } }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Clock = new FramedClock(manualClock), - ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Receiving", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - } - } + latencyDisplay = new OsuSpriteText() + }; }); - - Add(latencyDisplay = new OsuSpriteText()); - }); + } private void onNewFrames(int userId, FrameDataBundle frames) { @@ -189,6 +197,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasic() { + AddStep("Wait for user input", () => { }); } private double latency = SpectatorClient.TIME_BETWEEN_SENDS; @@ -232,11 +241,15 @@ namespace osu.Game.Tests.Visual.Gameplay [TearDownSteps] public void TearDown() { - AddStep("stop recorder", () => - { - recorder.Expire(); - spectatorClient.OnNewFrames -= onNewFrames; - }); + AddStep("stop recorder", cleanUpState); + } + + private void cleanUpState() + { + // Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`. + recorder?.RemoveAndDisposeImmediately(); + recorder = null; + spectatorClient.OnNewFrames -= onNewFrames; } public class TestFramedReplayInputHandler : FramedReplayInputHandler diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 6dda8df6f0..55e453c3d3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -30,25 +30,10 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestMusicNavigationActions() { - int importId = 0; Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. - AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo - { - Beatmaps = new List - { - new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - } - }, - Metadata = new BeatmapMetadata - { - Artist = $"a test map {importId++}", - Title = "title", - } - }).Wait(), 5); + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).Wait(), 5); AddStep("import beatmap with track", () => { diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 57d60cea9e..c65595d82e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Menus private TestToolbar toolbar; [Resolved] - private RulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } [SetUp] public void SetUp() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index e94e91dfe3..357db16e2c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -92,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for join", () => Client.Room != null); + AddUntilStep("wait for join", () => RoomManager.RoomJoined); } [Test] @@ -104,23 +105,24 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); - - AddStep("click ready button", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + clickReadyButton(); AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); - - AddStep("click ready button", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + clickReadyButton(); AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); } + + private void clickReadyButton() + { + AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType