2021-09-30 14:46:16 +00:00
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
|
2021-12-14 04:53:23 +00:00
|
|
|
using osu.Framework.Testing;
|
2021-09-30 14:46:16 +00:00
|
|
|
using osu.Game.Database;
|
|
|
|
using osu.Game.Models;
|
|
|
|
using Realms;
|
|
|
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
namespace osu.Game.Tests.Database
|
|
|
|
{
|
|
|
|
public class RealmLiveTests : RealmTest
|
|
|
|
{
|
2021-10-01 05:30:27 +00:00
|
|
|
[Test]
|
2021-11-26 05:38:39 +00:00
|
|
|
public void TestLiveEquality()
|
2021-10-01 05:30:27 +00:00
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
2021-11-26 05:38:39 +00:00
|
|
|
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
|
2021-10-01 05:30:27 +00:00
|
|
|
|
2021-11-26 05:38:39 +00:00
|
|
|
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive();
|
2021-10-01 05:30:27 +00:00
|
|
|
|
2021-11-26 05:38:39 +00:00
|
|
|
Assert.AreEqual(beatmap, beatmap2);
|
2021-10-01 05:30:27 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-14 04:53:23 +00:00
|
|
|
[Test]
|
|
|
|
public void TestAccessAfterStorageMigrate()
|
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, storage) =>
|
|
|
|
{
|
|
|
|
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
|
|
|
|
|
|
|
|
ILive<RealmBeatmap> liveBeatmap;
|
|
|
|
|
|
|
|
using (var context = realmFactory.CreateContext())
|
|
|
|
{
|
|
|
|
context.Write(r => r.Add(beatmap));
|
|
|
|
|
|
|
|
liveBeatmap = beatmap.ToLive();
|
|
|
|
}
|
|
|
|
|
|
|
|
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
|
|
|
{
|
|
|
|
migratedStorage.DeleteDirectory(string.Empty);
|
|
|
|
|
|
|
|
storage.Migrate(migratedStorage);
|
|
|
|
|
|
|
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-11-29 08:34:56 +00:00
|
|
|
[Test]
|
|
|
|
public void TestAccessAfterAttach()
|
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
|
|
|
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
|
|
|
|
|
|
|
|
var liveBeatmap = beatmap.ToLive();
|
|
|
|
|
|
|
|
using (var context = realmFactory.CreateContext())
|
|
|
|
context.Write(r => r.Add(beatmap));
|
|
|
|
|
|
|
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-11-26 05:39:35 +00:00
|
|
|
[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<InvalidOperationException>(() => liveBeatmap.PerformWrite(l => l.Hidden = true));
|
|
|
|
|
|
|
|
Assert.IsFalse(beatmap.Hidden);
|
|
|
|
Assert.IsFalse(liveBeatmap.Value.Hidden);
|
|
|
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
|
|
|
}
|
|
|
|
|
2021-09-30 14:46:16 +00:00
|
|
|
[Test]
|
2021-11-30 02:55:13 +00:00
|
|
|
public void TestScopedReadWithoutContext()
|
2021-09-30 14:46:16 +00:00
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
2021-11-29 09:00:28 +00:00
|
|
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
2021-09-30 14:46:16 +00:00
|
|
|
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(() =>
|
|
|
|
{
|
2021-11-30 02:55:13 +00:00
|
|
|
liveBeatmap.PerformRead(beatmap =>
|
2021-09-30 14:46:16 +00:00
|
|
|
{
|
2021-11-30 02:55:13 +00:00
|
|
|
Assert.IsTrue(beatmap.IsValid);
|
|
|
|
Assert.IsFalse(beatmap.Hidden);
|
|
|
|
});
|
2021-09-30 14:46:16 +00:00
|
|
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
2021-11-30 02:55:13 +00:00
|
|
|
public void TestScopedWriteWithoutContext()
|
2021-09-30 14:46:16 +00:00
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
2021-11-29 09:00:28 +00:00
|
|
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
2021-09-30 14:46:16 +00:00
|
|
|
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(() =>
|
|
|
|
{
|
2021-11-30 02:55:13 +00:00
|
|
|
liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; });
|
|
|
|
liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); });
|
2021-09-30 14:46:16 +00:00
|
|
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
2021-11-30 02:55:13 +00:00
|
|
|
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()
|
2021-09-30 14:46:16 +00:00
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
2021-11-29 09:00:28 +00:00
|
|
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
2021-11-30 03:02:35 +00:00
|
|
|
|
2021-09-30 14:46:16 +00:00
|
|
|
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(() =>
|
|
|
|
{
|
2021-11-30 02:55:13 +00:00
|
|
|
// Can't be used, without a valid context.
|
|
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
|
|
{
|
|
|
|
var __ = liveBeatmap.Value;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Can't be used, even from within a valid context.
|
|
|
|
using (realmFactory.CreateContext())
|
|
|
|
{
|
|
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
|
|
{
|
|
|
|
var __ = liveBeatmap.Value;
|
|
|
|
});
|
|
|
|
}
|
2021-09-30 14:46:16 +00:00
|
|
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestValueAccessWithoutOpenContextFails()
|
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
2021-11-29 09:00:28 +00:00
|
|
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
2021-09-30 14:46:16 +00:00
|
|
|
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<InvalidOperationException>(() =>
|
|
|
|
{
|
|
|
|
var unused = liveBeatmap.Value;
|
|
|
|
});
|
|
|
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestLiveAssumptions()
|
|
|
|
{
|
|
|
|
RunTestWithRealm((realmFactory, _) =>
|
|
|
|
{
|
|
|
|
int changesTriggered = 0;
|
|
|
|
|
|
|
|
using (var updateThreadContext = realmFactory.CreateContext())
|
|
|
|
{
|
2021-12-01 06:09:51 +00:00
|
|
|
updateThreadContext.All<RealmBeatmap>().QueryAsyncWithNotifications(gotChange);
|
2021-11-29 09:00:28 +00:00
|
|
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
2021-09-30 14:46:16 +00:00
|
|
|
|
|
|
|
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<RealmBeatmap>().Count());
|
|
|
|
Assert.AreEqual(0, changesTriggered);
|
|
|
|
|
2021-11-29 06:14:27 +00:00
|
|
|
liveBeatmap.PerformRead(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<RealmBeatmap>().Count());
|
|
|
|
Assert.AreEqual(1, changesTriggered);
|
2021-09-30 14:46:16 +00:00
|
|
|
|
2021-11-29 06:14:27 +00:00
|
|
|
// can access properties without a crash.
|
|
|
|
Assert.IsFalse(resolved.Hidden);
|
2021-09-30 14:46:16 +00:00
|
|
|
|
2021-11-29 06:14:27 +00:00
|
|
|
// ReSharper disable once AccessToDisposedClosure
|
|
|
|
updateThreadContext.Write(r =>
|
|
|
|
{
|
|
|
|
// can use with the main context.
|
|
|
|
r.Remove(resolved);
|
|
|
|
});
|
2021-09-30 14:46:16 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void gotChange(IRealmCollection<RealmBeatmap> sender, ChangeSet changes, Exception error)
|
|
|
|
{
|
|
|
|
changesTriggered++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|