From ed0552a9e8cce45220498581eec036f9640fde3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:18 +0900 Subject: [PATCH 01/14] Add failing test for FK constraint conflict on reimporting modified beatmap with scores present --- .../Beatmaps/IO/ImportBeatmapTest.cs | 72 ++++++++++++++++++- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 16 ++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..66b5cbed0c 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -19,7 +19,9 @@ using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Scoring; using osu.Game.Tests.Resources; +using osu.Game.Tests.Scores.IO; using osu.Game.Users; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -185,10 +187,58 @@ namespace osu.Game.Tests.Beatmaps.IO } } - private string hashFile(string filename) + [Test] + public async Task TestImportThenImportWithChangedHashedFile() { - using (var s = File.OpenRead(filename)) - return s.ComputeMD5Hash(); + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + await createScoreForBeatmap(osu, imported.Beatmaps.First()); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // arbitrary write to hashed file + // this triggers the special BeatmapManager.PreImport deletion/replacement flow. + using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText()) + await sw.WriteLineAsync("// changed"); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); + + ensureLoaded(osu); + + // check the newly "imported" beatmap is not the original. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } } [Test] @@ -895,6 +945,16 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } + private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + { + return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo + { + OnlineScoreID = 2, + Beatmap = beatmap, + BeatmapInfoID = beatmap.ID + }, new ImportScoreTest.TestArchiveReader()); + } + private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -904,6 +964,12 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } + private string hashFile(string filename) + { + using (var s = File.OpenRead(filename)) + return s.ComputeMD5Hash(); + } + private void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 7522aca5dc..cd7d744f53 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO OnlineScoreID = 12345, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Scores.IO beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - var secondImport = await loadScoreIntoOsu(osu, imported); + var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); } finally @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO return scoreManager.GetAllUsableScores().FirstOrDefault(); } - private class TestArchiveReader : ArchiveReader + internal class TestArchiveReader : ArchiveReader { public TestArchiveReader() : base("test_archive") From dcba7bf779fa2ab78a884cc17671eefa15a71082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:32 +0900 Subject: [PATCH 02/14] Fix import flow potentially hitting foreign key constraint --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..e93d8c6eb5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -181,8 +181,13 @@ namespace osu.Game.Beatmaps if (existingOnlineId != null) { Delete(existingOnlineId); - beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged."); + + // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. + existingOnlineId.OnlineBeatmapSetID = null; + foreach (var b in existingOnlineId.Beatmaps) + b.OnlineBeatmapID = null; + + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); } } } From f6180b7e6a8adfa6560da82b4fe6805d5577f61e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:37:24 +0900 Subject: [PATCH 03/14] Mark `static` methods as such --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 66b5cbed0c..3b355da359 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -945,7 +945,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Beatmaps.IO }, new ImportScoreTest.TestArchiveReader()); } - private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) + private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -964,18 +964,18 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } - private string hashFile(string filename) + private static string hashFile(string filename) { using (var s = File.OpenRead(filename)) return s.ComputeMD5Hash(); } - private void checkBeatmapCount(OsuGameBase osu, int expected) + private static void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); } - private void checkSingleReferencedFileCount(OsuGameBase osu, int expected) + private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); } From 1bbfbb0d8e117ac9f5dad5a4f34aa3e1726ada2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 19:30:08 +0900 Subject: [PATCH 04/14] Fix test that never should have worked This was only working by luck until now. It was "correctly" matching on null online ID (see logic at https://github.com/ppy/osu/blob/abc96057b2cf50e02e0ec939645f6421684495d4/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs#L199-L207). Now it works by actually matching on the online ID. --- .../TestScenePlaylistsRoomSubScreen.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..c4da2ab48a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -111,10 +111,27 @@ namespace osu.Game.Tests.Visual.Playlists public void TestBeatmapUpdatedOnReImport() { BeatmapSetInfo importedSet = null; + TestBeatmap beatmap = null; + + // this step is required to make sure the further imports actually get online IDs. + // all the playlist logic relies on online ID matching. + AddStep("remove all matching online IDs", () => + { + beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); + + var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList(); + + foreach (var s in existing) + { + s.OnlineBeatmapSetID = null; + foreach (var b in s.Beatmaps) + b.OnlineBeatmapID = null; + manager.Update(s); + } + }); AddStep("import altered beatmap", () => { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; From cd6f17537531203eaa7f1782e15c73ba520f5e11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 13:29:06 +0900 Subject: [PATCH 05/14] Ensure beatmap is reloaded before each playlist room test run --- .../Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index c4da2ab48a..6d7a254ab9 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -40,8 +40,6 @@ namespace osu.Game.Tests.Visual.Playlists Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); - ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -58,6 +56,7 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { + AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } From e4ca6a4266ce8624e189e9409128b97bd02b44bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 01:55:09 +0900 Subject: [PATCH 06/14] Serialise and send ruleset ID as part of score submission --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 3944c1d3de..4fd1d00fef 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,7 +46,7 @@ namespace osu.Game.Scoring [JsonIgnore] public int Combo { get; set; } // Todo: Shouldn't exist in here - [JsonIgnore] + [JsonProperty("ruleset_id")] public int RulesetID { get; set; } [JsonProperty("passed")] From 9132c42f875576a4d7c9e74cccb1ecc1a9592826 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:58:07 +0900 Subject: [PATCH 07/14] Fix actions posted to the wrong channel --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Online/Chat/NowPlayingCommand.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 8507887357..3136a3960d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -225,7 +225,7 @@ namespace osu.Game.Online.Chat switch (command) { case "np": - AddInternal(new NowPlayingCommand()); + AddInternal(new NowPlayingCommand(target)); break; case "me": @@ -235,7 +235,7 @@ namespace osu.Game.Online.Chat break; } - PostMessage(content, true); + PostMessage(content, true, target); break; case "join": diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 926709694b..f522d8a236 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -21,6 +21,13 @@ namespace osu.Game.Online.Chat [Resolved] private Bindable currentBeatmap { get; set; } + private readonly Channel target; + + public NowPlayingCommand(Channel target) + { + this.target = target; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -48,7 +55,7 @@ namespace osu.Game.Online.Chat var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString(); - channelManager.PostMessage($"is {verb} {beatmapString}", true); + channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); } } From 7a86686f40836dd58939ebbd00c0e7107f7c099c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:40 +0900 Subject: [PATCH 08/14] Make nullable --- osu.Game/Online/Chat/NowPlayingCommand.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index f522d8a236..7756591e03 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -23,7 +23,11 @@ namespace osu.Game.Online.Chat private readonly Channel target; - public NowPlayingCommand(Channel target) + /// + /// Creates a new to post the currently-playing beatmap to a parenting . + /// + /// The target channel to post to. If null, the currently-selected channel will be posted to. + public NowPlayingCommand(Channel target = null) { this.target = target; } From ca0eaab8e21b743ad5bdc14ec3878e7658d5ca97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:46 +0900 Subject: [PATCH 09/14] Add test --- .../Chat/TestSceneChannelManager.cs | 104 ++++++++++++++++++ .../Online/API/Requests/PostMessageRequest.cs | 10 +- 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Chat/TestSceneChannelManager.cs diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs new file mode 100644 index 0000000000..b81c39933d --- /dev/null +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -0,0 +1,104 @@ +// 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.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Tests.Visual; +using osu.Game.Users; + +namespace osu.Game.Tests.Chat +{ + [HeadlessTest] + public class TestSceneChannelManager : OsuTestScene + { + private ChannelManager channelManager; + private int currentMessageId; + + [SetUp] + public void Setup() => Schedule(() => + { + var container = new ChannelManagerContainer(); + Child = container; + channelManager = container.ChannelManager; + }); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + currentMessageId = 0; + + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + + case PostMessageRequest postMessage: + postMessage.TriggerSuccess(new Message(++currentMessageId) + { + IsAction = postMessage.Message.IsAction, + ChannelId = postMessage.Message.ChannelId, + Content = postMessage.Message.Content, + Links = postMessage.Message.Links, + Timestamp = postMessage.Message.Timestamp, + Sender = postMessage.Message.Sender + }); + + return true; + } + + return false; + }; + }); + } + + [Test] + public void TestCommandsPostedToCorrectChannelWhenNotCurrent() + { + Channel channel1 = null; + Channel channel2 = null; + + AddStep("join 2 rooms", () => + { + channelManager.JoinChannel(channel1 = createChannel(1, ChannelType.Public)); + channelManager.JoinChannel(channel2 = createChannel(2, ChannelType.Public)); + }); + + AddStep("select channel 1", () => channelManager.CurrentChannel.Value = channel1); + + AddStep("post /me command to channel 2", () => channelManager.PostCommand("me dances", channel2)); + AddAssert("/me command received by channel 2", () => channel2.Messages.Last().Content == "dances"); + + AddStep("post /np command to channel 2", () => channelManager.PostCommand("np", channel2)); + AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to")); + } + + private Channel createChannel(int id, ChannelType type) => new Channel(new User()) + { + Id = id, + Name = $"Channel {id}", + Topic = $"Topic of channel {id} with type {type}", + Type = type, + }; + + private class ChannelManagerContainer : CompositeDrawable + { + [Cached] + public ChannelManager ChannelManager { get; } = new ChannelManager(); + + public ChannelManagerContainer() + { + InternalChild = ChannelManager; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index 84ab873acf..5d508a4cdf 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -9,11 +9,11 @@ namespace osu.Game.Online.API.Requests { public class PostMessageRequest : APIRequest { - private readonly Message message; + public readonly Message Message; public PostMessageRequest(Message message) { - this.message = message; + Message = message; } protected override WebRequest CreateWebRequest() @@ -21,12 +21,12 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant()); - req.AddParameter(@"message", message.Content); + req.AddParameter(@"is_action", Message.IsAction.ToString().ToLowerInvariant()); + req.AddParameter(@"message", Message.Content); return req; } - protected override string Target => $@"chat/channels/{message.ChannelId}/messages"; + protected override string Target => $@"chat/channels/{Message.ChannelId}/messages"; } } From 8bcb4d13fb66a5c49101c9d10b424ca0bd3f6fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 17:21:09 +0900 Subject: [PATCH 10/14] Fix multiple tests eating host exceptions --- osu.Game.Tests/ImportTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 3 ++- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index ea351e0d45..e888f51e98 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -17,7 +17,8 @@ namespace osu.Game.Tests protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false) { var osu = new TestOsuGameBase(withBeatmap); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 46c3b8bc3b..61f8511e3c 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -149,7 +149,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 4c5f5a7a1a..e4eb5a36fb 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -55,7 +55,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } From 1b2d00f796735f1fa94234cd5ff91778079087f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 20:13:39 +0900 Subject: [PATCH 11/14] Trigger successes --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 3 ++- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index b81c39933d..0ec21a4c7b 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Chat { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; case PostMessageRequest postMessage: diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3971146ff8..a1549dfbce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -82,7 +82,8 @@ namespace osu.Game.Tests.Visual.Online { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; } From 9acc5e38bb1574fe0ff9226cd8ff4221a3e29d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:16:57 +0900 Subject: [PATCH 12/14] Add basic logging for osu! storage migration When looking into the test failure at https://github.com/ppy/osu/runs/2940065457, it became apparent that we are not showing the migration process anywhere in logs. It's the cause of many issues, and we would want to see this in CI and user logs when occurring. --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bf1b449292..7954eafdca 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -422,11 +422,15 @@ namespace osu.Game public void Migrate(string path) { + Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); + using (realmFactory.BlockAllOperations()) { contextFactory.FlushConnections(); (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } + + Logger.Log(@"Migration complete!"); } protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); From 2f1203085b4fc015dbda854510f274564ad18b21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:21:31 +0900 Subject: [PATCH 13/14] Also add logging of realm block/flush operations --- osu.Game/Database/RealmContextFactory.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 71617b258d..fb5e2faff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -89,12 +89,18 @@ namespace osu.Game.Database if (IsDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) + { + factory.blockingLock.Release(); + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + } } protected override void Update() @@ -147,6 +153,8 @@ namespace osu.Game.Database private void flushContexts() { + Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); + var previousContext = context; context = null; @@ -155,6 +163,8 @@ namespace osu.Game.Database Thread.Sleep(50); previousContext?.Dispose(); + + Logger.Log(@"Realm contexts flushed.", LoggingTarget.Database); } protected override void Dispose(bool isDisposing) From e9158ccc41828b1a3fc220d5f15e8df03bca09d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 23:23:21 +0900 Subject: [PATCH 14/14] Fix gameplay tests incorrectly seeking via MusicController --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e111bb1054..1d500dcc14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 8ff21057b5..9da583a073 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); }