From 9843da59f4866918391902de30125577b60c1464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 20:29:17 +0100 Subject: [PATCH 1/3] Fix intermittent test fail due to duplicate user `TestSceneRealtimeReadyButton` was manually adding `API.LocalUser`, which wasn't actually needed. The base `RealtimeMultiplayerTestScene` by default creates a new room as `API.LocalUser`, therefore automatically adding that user to the room - and as such there is no need to add them manually unless the `joinRoom` ctor param is specified as `false`. --- .../Visual/RealtimeMultiplayer/TestSceneRealtimeReadyButton.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeReadyButton.cs b/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeReadyButton.cs index b7cd81fb32..e9d3ddb32d 100644 --- a/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeReadyButton.cs +++ b/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeReadyButton.cs @@ -55,8 +55,6 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer } } }; - - Client.AddUser(API.LocalUser.Value); }); [Test] From 47020c888731c2370d6f81fbfe9f11d6e7fcfdc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 21:00:47 +0100 Subject: [PATCH 2/3] Add failing test cases --- .../TestSceneRealtimeMultiplayer.cs | 24 +++++++++++++++++++ .../TestRealtimeMultiplayerClient.cs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeMultiplayer.cs b/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeMultiplayer.cs index 80955ca380..5cf80df6aa 100644 --- a/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeMultiplayer.cs +++ b/osu.Game.Tests/Visual/RealtimeMultiplayer/TestSceneRealtimeMultiplayer.cs @@ -1,7 +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 NUnit.Framework; using osu.Game.Screens.Multi.Components; +using osu.Game.Users; namespace osu.Game.Tests.Visual.RealtimeMultiplayer { @@ -15,6 +17,28 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer AddUntilStep("wait for loaded", () => multi.IsLoaded); } + [Test] + public void TestOneUserJoinedMultipleTimes() + { + var user = new User { Id = 33 }; + + AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); + + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + } + + [Test] + public void TestOneUserLeftMultipleTimes() + { + var user = new User { 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); + } + private class TestRealtimeMultiplayer : Screens.Multi.RealtimeMultiplayer.RealtimeMultiplayer { protected override RoomManager CreateRoomManager() => new TestRealtimeRoomManager(); diff --git a/osu.Game/Tests/Visual/RealtimeMultiplayer/TestRealtimeMultiplayerClient.cs b/osu.Game/Tests/Visual/RealtimeMultiplayer/TestRealtimeMultiplayerClient.cs index de52633c88..52047016e2 100644 --- a/osu.Game/Tests/Visual/RealtimeMultiplayer/TestRealtimeMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/RealtimeMultiplayer/TestRealtimeMultiplayerClient.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer public void RemoveUser(User user) { Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserLeft(Room.Users.Single(u => u.User == user)); + ((IMultiplayerClient)this).UserLeft(new MultiplayerRoomUser(user.Id)); Schedule(() => { From a71496bc4e9f0386d13b295eb92496f33144e5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 20:34:19 +0100 Subject: [PATCH 3/3] Sanity check received user joined messages While test failures fixed in 9843da5 were a shortcoming of the test, they exposed a potential vulnerable point of the multiplayer client logic. In case of unreliable message delivery it is not unreasonable that duplicate messages might arrive, in which case the same scenario that failed in the tests could crash the game. To ensure that is not the case, explicitly screen each new joined user against the room user list, to ensure that duplicates do not show up. `UserLeft` is already tolerant in that respect (if a user is requested to be removed twice by the server, the second removal just won't do anything). --- .../Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 6331d324a6..e8dbeda9cb 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -226,6 +226,10 @@ namespace osu.Game.Online.RealtimeMultiplayer if (Room == null) return; + // for sanity, ensure that there can be no duplicate users in the room user list. + if (Room.Users.Any(existing => existing.UserID == user.UserID)) + return; + Room.Users.Add(user); RoomChanged?.Invoke();