diff --git a/osu.Android.props b/osu.Android.props index 4f0125bed2..31a3bc8c45 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6d0d5702e9..9f3709f7a3 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWhenClosed() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDelete() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteFromStream() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithChangedHashedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -247,7 +247,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Ignore("intentionally broken by import optimisations")] public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -298,7 +298,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithDifferentFilename() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -351,7 +351,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportCorruptThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestModelCreationFailureDoesntReturn() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -428,7 +428,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestRollbackOnFailure() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -507,7 +507,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -534,7 +534,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -566,7 +566,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWithDuplicateBeatmapIDs() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -621,8 +621,8 @@ namespace osu.Game.Tests.Beatmaps.IO [NonParallelizable] public void TestImportOverIPC() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-host", true)) - using (HeadlessGameHost client = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-client", true)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(true)) + using (HeadlessGameHost client = new CleanRunHeadlessGameHost(true)) { try { @@ -651,7 +651,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenFileOpen() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -673,7 +673,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithDuplicateHashes() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -715,7 +715,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportNestedStructure() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -760,7 +760,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithIgnoredDirectoryInArchive() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -814,7 +814,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapInfo() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -844,7 +844,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -888,7 +888,7 @@ namespace osu.Game.Tests.Beatmaps.IO public void TestSaveRemovesInvalidCharactersFromPath() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -922,7 +922,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -949,7 +949,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 686e053246..d4ec5e897b 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -128,8 +128,12 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestSaveAndReload() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(bypassCleanup: true)) + string firstRunName; + + using (var host = new CleanRunHeadlessGameHost(bypassCleanup: true)) { + firstRunName = host.Name; + try { var osu = LoadOsuIntoHost(host, true); @@ -150,7 +154,7 @@ namespace osu.Game.Tests.Collections.IO } // Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location. - using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestSaveAndReload))) + using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName)) { try { diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 6904464485..4e67f09dca 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { host.Run(new RealmTestGame(() => { @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Database protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { host.Run(new RealmTestGame(async () => { diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 8d15be44fa..4bb54f1625 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual { try { - string defaultStorageLocation = getDefaultLocationFor(nameof(TestDefaultDirectory)); + string defaultStorageLocation = getDefaultLocationFor(host); var osu = LoadOsuIntoHost(host); var storage = osu.Dependencies.Get(); @@ -109,7 +109,7 @@ namespace osu.Game.Tests.NonVisual { try { - string defaultStorageLocation = getDefaultLocationFor(nameof(TestMigration)); + string defaultStorageLocation = getDefaultLocationFor(host); var osu = LoadOsuIntoHost(host); var storage = osu.Dependencies.Get(); @@ -284,9 +284,9 @@ namespace osu.Game.Tests.NonVisual } } - private static string getDefaultLocationFor(string testTypeName) + private static string getDefaultLocationFor(CustomTestHeadlessGameHost host) { - string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, testTypeName); + string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, host.Name); if (Directory.Exists(path)) Directory.Delete(path, true); @@ -315,7 +315,7 @@ namespace osu.Game.Tests.NonVisual public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"") : base(callingMethodName: callingMethodName) { - string defaultStorageLocation = getDefaultLocationFor(callingMethodName); + string defaultStorageLocation = getDefaultLocationFor(this); InitialStorage = new DesktopStorage(defaultStorageLocation, this); InitialStorage.DeleteDirectory(string.Empty); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index f2ce002650..40d2455106 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -319,7 +319,7 @@ namespace osu.Game.Tests.Skins.IO private async Task runSkinTest(Func action, [CallerMemberName] string callingMethodName = @"") { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: callingMethodName)) { try { diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 3619aae7e0..fc5d3b652f 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -2,10 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Tests; using osu.Game.Tournament.Configuration; @@ -17,7 +18,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -36,9 +37,9 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestCustomDirectory() { - using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. { - string osuDesktopStorage = PrepareBasePath(nameof(TestCustomDirectory)); + string osuDesktopStorage = Path.Combine(host.UserStoragePaths.First(), nameof(TestCustomDirectory)); const string custom_tournament = "custom"; // need access before the game has constructed its own storage yet. @@ -60,15 +61,6 @@ namespace osu.Game.Tournament.Tests.NonVisual finally { host.Exit(); - - try - { - if (Directory.Exists(osuDesktopStorage)) - Directory.Delete(osuDesktopStorage, true); - } - catch - { - } } } } @@ -76,9 +68,9 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. { - string osuRoot = PrepareBasePath(nameof(TestMigration)); + string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); if (File.Exists(configFile)) @@ -146,28 +138,8 @@ namespace osu.Game.Tournament.Tests.NonVisual finally { host.Exit(); - - try - { - if (Directory.Exists(osuRoot)) - Directory.Delete(osuRoot, true); - } - catch - { - } } } } - - public static string PrepareBasePath(string testInstance) - { - string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); - - // manually clean before starting in case there are left-over files at the test site. - if (Directory.Exists(basePath)) - Directory.Delete(basePath, true); - - return basePath; - } } } diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index 692cb3870c..db019f9242 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestUnavailableRuleset() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUnavailableRuleset))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index db89855db7..952eb72bf4 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Tournament.IO; using osu.Game.Tournament.IPC; @@ -17,9 +19,9 @@ namespace osu.Game.Tournament.Tests.NonVisual public void CheckIPCLocation() { // don't use clean run because files are being written before osu! launches. - using (HeadlessGameHost host = new HeadlessGameHost(nameof(CheckIPCLocation))) + using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation))) { - string basePath = CustomTourneyDirectoryTest.PrepareBasePath(nameof(CheckIPCLocation)); + string basePath = Path.Combine(host.UserStoragePaths.First(), nameof(CheckIPCLocation)); // Set up a fake IPC client for the IPC Storage to switch to. string testStableInstallDirectory = Path.Combine(basePath, "stable-ce"); @@ -42,15 +44,6 @@ namespace osu.Game.Tournament.Tests.NonVisual finally { host.Exit(); - - try - { - if (Directory.Exists(basePath)) - Directory.Delete(basePath, true); - } - catch - { - } } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 76358fca3f..3346c6d97d 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -342,11 +342,9 @@ namespace osu.Game.Overlays private void changeTrack() { + var queuedTrack = getQueuedTrack(); + var lastTrack = CurrentTrack; - - var queuedTrack = new DrawableTrack(current.LoadTrack()); - queuedTrack.Completed += () => onTrackCompleted(current); - CurrentTrack = queuedTrack; // At this point we may potentially be in an async context from tests. This is extremely dangerous but we have to make do for now. @@ -370,6 +368,15 @@ namespace osu.Game.Overlays }); } + private DrawableTrack getQueuedTrack() + { + // Important to keep this in its own method to avoid inadvertently capturing unnecessary variables in the callback. + // Can lead to leaks. + var queuedTrack = new DrawableTrack(current.LoadTrack()); + queuedTrack.Completed += () => onTrackCompleted(current); + return queuedTrack; + } + private void onTrackCompleted(WorkingBeatmap workingBeatmap) { // the source of track completion is the audio thread, so the beatmap may have changed before firing. diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 7d7f011f6a..754c9044e8 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -1,6 +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; using System.Runtime.CompilerServices; using osu.Framework.Testing; @@ -14,19 +15,25 @@ namespace osu.Game.Tests /// /// Create a new instance. /// - /// An optional suffix which will isolate this host from others called from the same method source. /// Whether to bind IPC channels. /// Whether the host should be forced to run in realtime, rather than accelerated test time. /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. /// The name of the calling method, used for test file isolation and clean-up. - public CleanRunHeadlessGameHost(string gameSuffix = @"", bool bindIPC = false, bool realtime = true, bool bypassCleanup = false, [CallerMemberName] string callingMethodName = @"") - : base(callingMethodName + gameSuffix, bindIPC, realtime, bypassCleanup: bypassCleanup) + public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanup = false, [CallerMemberName] string callingMethodName = @"") + : base($"{callingMethodName}-{Guid.NewGuid()}", bindIPC, realtime, bypassCleanup: bypassCleanup) { } protected override void SetupForRun() { - Storage.DeleteDirectory(string.Empty); + try + { + Storage.DeleteDirectory(string.Empty); + } + catch + { + // May fail if a logging target has already been set via OsuStorage.ChangeTargetStorage. + } // base call needs to be run *after* storage is emptied, as it updates the (static) logger's storage and may start writing // log entries from another source if a unit test host is shared over multiple tests, causing a file access denied exception. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 565f78e2d4..2c97bae360 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index bdb3b0b149..de084c0297 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - +