// 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.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using osu.Game.Database; using osu.Game.Models; using Realms; #nullable enable namespace osu.Game.Tests.Database { public class RealmLiveTests : RealmTest { [Test] public void TestLiveEquality() { RunTestWithRealm((realmFactory, _) => { ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(); Assert.AreEqual(beatmap, beatmap2); }); } [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 TestValueAccessWithOpenContext() { 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()))); liveBeatmap = beatmap.ToLive(); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); Debug.Assert(liveBeatmap != null); 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(); }); } [Test] public void TestScopedReadWithoutContext() { 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()))); liveBeatmap = beatmap.ToLive(); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); Debug.Assert(liveBeatmap != null); Task.Factory.StartNew(() => { liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.IsValid); Assert.IsFalse(beatmap.Hidden); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }); } [Test] public void TestScopedWriteWithoutContext() { 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()))); liveBeatmap = beatmap.ToLive(); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); Debug.Assert(liveBeatmap != null); Task.Factory.StartNew(() => { liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; }); liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }); } [Test] public void TestValueAccessWithoutOpenContextFails() { 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()))); liveBeatmap = beatmap.ToLive(); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); Debug.Assert(liveBeatmap != null); Task.Factory.StartNew(() => { Assert.Throws(() => { var unused = liveBeatmap.Value; }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }); } [Test] public void TestLiveAssumptions() { RunTestWithRealm((realmFactory, _) => { int changesTriggered = 0; using (var updateThreadContext = realmFactory.CreateContext()) { updateThreadContext.All().SubscribeForNotifications(gotChange); RealmLive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) { var ruleset = CreateRuleset(); var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); // add a second beatmap to ensure that a full refresh occurs below. // not just a refresh from the resolved Live. threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); liveBeatmap = beatmap.ToLive(); } }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); Debug.Assert(liveBeatmap != null); // not yet seen by main context 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 => { // can use with the main context. r.Remove(resolved); }); } void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) { changesTriggered++; } }); } } }