Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12e938d23c | ||
|
|
e79abfbc55 | ||
|
|
8ec3b0a6f2 | ||
|
|
eebc5602a5 | ||
|
|
63a5f65fe6 | ||
|
|
619877f0a3 | ||
|
|
a401d5454e | ||
|
|
528bc8c225 | ||
|
|
11c611d435 |
6 changed files with 114 additions and 25 deletions
|
|
@ -1567,15 +1567,19 @@ class CultCacheTests(unittest.TestCase):
|
||||||
# Every vector in the folder has a manifest row.
|
# Every vector in the folder has a manifest row.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(path.name for path in root.glob("*.bin")),
|
sorted(path.name for path in root.glob("*.bin")),
|
||||||
sorted(vector for vector, *_ in rows if not vector.startswith("..")),
|
sorted(vector for vector, *_ in rows if not vector.startswith(("..", "@"))),
|
||||||
)
|
)
|
||||||
envelopes = SingleFileMessagePackBackingStore(self._C2A_VECTORS / "v3-base.msgpack").pull_all()
|
envelopes = SingleFileMessagePackBackingStore(self._C2A_VECTORS / "v3-base.msgpack").pull_all()
|
||||||
for vector, _, _, _, python in rows:
|
for vector, _, _, _, python in rows:
|
||||||
content = (root / vector).read_bytes()
|
# "@directory": the walker makes an empty directory at the store path; it is refused and left untouched.
|
||||||
|
content = None if vector == "@directory" else (root / vector).read_bytes()
|
||||||
for operation in ("pull_all", "push_all", "push", "delete"):
|
for operation in ("pull_all", "push_all", "push", "delete"):
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
store_path = Path(tmp) / "store.msgpack"
|
store_path = Path(tmp) / "store.msgpack"
|
||||||
store_path.write_bytes(content)
|
if content is None:
|
||||||
|
store_path.mkdir()
|
||||||
|
else:
|
||||||
|
store_path.write_bytes(content)
|
||||||
store = SingleFileMessagePackBackingStore(store_path)
|
store = SingleFileMessagePackBackingStore(store_path)
|
||||||
run = {
|
run = {
|
||||||
"pull_all": store.pull_all,
|
"pull_all": store.pull_all,
|
||||||
|
|
@ -1598,6 +1602,11 @@ class CultCacheTests(unittest.TestCase):
|
||||||
self.assertIn(envelopes[0].key, keys, f"{vector} {operation}")
|
self.assertIn(envelopes[0].key, keys, f"{vector} {operation}")
|
||||||
else:
|
else:
|
||||||
self.assertNotIn(envelopes[0].key, keys, f"{vector} {operation}")
|
self.assertNotIn(envelopes[0].key, keys, f"{vector} {operation}")
|
||||||
|
elif content is None:
|
||||||
|
# A path that cannot be reached as a file stays an OSError (_read_store): not a store that failed to decode.
|
||||||
|
with self.assertRaises(OSError, msg=f"{vector} {operation}"):
|
||||||
|
run()
|
||||||
|
self.assertTrue(store_path.is_dir() and not any(store_path.iterdir()), f"{vector} {operation} touched the directory")
|
||||||
else:
|
else:
|
||||||
with self.assertRaises(StoreUnreadableError, msg=f"{vector} {operation}") as refused:
|
with self.assertRaises(StoreUnreadableError, msg=f"{vector} {operation}") as refused:
|
||||||
run()
|
run()
|
||||||
|
|
|
||||||
|
|
@ -6140,16 +6140,22 @@ mod tests {
|
||||||
for line in manifest.lines().filter(|line| !line.is_empty() && !line.starts_with('#')) {
|
for line in manifest.lines().filter(|line| !line.is_empty() && !line.starts_with('#')) {
|
||||||
let cells: Vec<&str> = line.split_whitespace().collect();
|
let cells: Vec<&str> = line.split_whitespace().collect();
|
||||||
let (vector, reads) = (cells[0], cells[2] == "reads");
|
let (vector, reads) = (cells[0], cells[2] == "reads");
|
||||||
if !vector.starts_with("..") {
|
if !vector.starts_with("..") && !vector.starts_with('@') {
|
||||||
listed.push(vector.to_string());
|
listed.push(vector.to_string());
|
||||||
}
|
}
|
||||||
let bytes = std::fs::read(root.join(vector))?;
|
// `@directory`: the walker puts an empty directory at the store path; it is refused and left untouched.
|
||||||
|
let directory = vector == "@directory";
|
||||||
|
let bytes = if directory { Vec::new() } else { std::fs::read(root.join(vector))? };
|
||||||
// A v3 store keeps its marker through a rewrite; every other file that reads is written v1.
|
// A v3 store keeps its marker through a rewrite; every other file that reads is written v1.
|
||||||
let header = if vector.contains("v3") { "cultcache.store.v3" } else { STORE_FORMAT_V1 };
|
let header = if vector.contains("v3") { "cultcache.store.v3" } else { STORE_FORMAT_V1 };
|
||||||
for operation in ["open", "soft_push_all", "push_all", "push"] {
|
for operation in ["open", "soft_push_all", "push_all", "push"] {
|
||||||
let temp = tempfile::tempdir()?;
|
let temp = tempfile::tempdir()?;
|
||||||
let path = temp.path().join("store.msgpack");
|
let path = temp.path().join("store.msgpack");
|
||||||
std::fs::write(&path, &bytes)?;
|
if directory {
|
||||||
|
std::fs::create_dir(&path)?;
|
||||||
|
} else {
|
||||||
|
std::fs::write(&path, &bytes)?;
|
||||||
|
}
|
||||||
let mut store = SingleFileMessagePackBackingStore::new(&path);
|
let mut store = SingleFileMessagePackBackingStore::new(&path);
|
||||||
let outcome = match operation {
|
let outcome = match operation {
|
||||||
"open" => store.pull_all().map(|_| ()),
|
"open" => store.pull_all().map(|_| ()),
|
||||||
|
|
@ -6159,6 +6165,10 @@ mod tests {
|
||||||
};
|
};
|
||||||
let what = format!("{vector} {operation}");
|
let what = format!("{vector} {operation}");
|
||||||
assert_eq!(outcome.is_ok(), reads, "{what}: {outcome:?}");
|
assert_eq!(outcome.is_ok(), reads, "{what}: {outcome:?}");
|
||||||
|
if directory {
|
||||||
|
assert!(path.is_dir() && std::fs::read_dir(&path)?.next().is_none(), "{what} touched the directory");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if operation == "soft_push_all" && reads {
|
if operation == "soft_push_all" && reads {
|
||||||
// A soft flush leaves a store it reads byte for byte.
|
// A soft flush leaves a store it reads byte for byte.
|
||||||
assert_eq!(std::fs::read(&path)?, bytes, "{what} rewrote a store it reads");
|
assert_eq!(std::fs::read(&path)?, bytes, "{what} rewrote a store it reads");
|
||||||
|
|
|
||||||
|
|
@ -2582,17 +2582,24 @@ test("SingleFileMessagePackBackingStore replaces a file exactly when it reads",
|
||||||
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
||||||
.map((line) => line.split(/\s+/u));
|
.map((line) => line.split(/\s+/u));
|
||||||
// Every vector in the folder has a manifest row.
|
// Every vector in the folder has a manifest row.
|
||||||
const listed = rows.map(([vector]) => vector!).filter((vector) => !vector.startsWith("..")).sort();
|
const listed = rows.map(([vector]) => vector!).filter((vector) => !vector.startsWith("..") && !vector.startsWith("@")).sort();
|
||||||
assert.deepEqual((await readdir(root)).filter((name) => name.endsWith(".bin")).sort(), listed);
|
assert.deepEqual((await readdir(root)).filter((name) => name.endsWith(".bin")).sort(), listed);
|
||||||
const dir = await mkdtemp(join(tmpdir(), "cultcache-readability-"));
|
const dir = await mkdtemp(join(tmpdir(), "cultcache-readability-"));
|
||||||
const seed = await copyVector(dir, join(c2aVectors, "v3-base.msgpack"), "seed.msgpack");
|
const seed = await copyVector(dir, join(c2aVectors, "v3-base.msgpack"), "seed.msgpack");
|
||||||
const envelopes = await new SingleFileMessagePackBackingStore(seed).pullAll();
|
const envelopes = await new SingleFileMessagePackBackingStore(seed).pullAll();
|
||||||
for (const [vector, , , typescript] of rows) {
|
for (const [vector, , , typescript] of rows) {
|
||||||
const reads = typescript === "reads";
|
const reads = typescript === "reads";
|
||||||
const bytes = await readFile(join(root, vector));
|
// "@directory": the walker makes an empty directory at the store path; the runtime refuses to open or replace it and leaves it be.
|
||||||
|
const isDirectory = vector === "@directory";
|
||||||
|
const bytes = isDirectory ? Buffer.alloc(0) : await readFile(join(root, vector));
|
||||||
for (const operation of ["pullAll", "softPushAll", "pushAll", "push", "delete"] as const) {
|
for (const operation of ["pullAll", "softPushAll", "pushAll", "push", "delete"] as const) {
|
||||||
const file = join(dir, `${vector.replace(/[^a-z0-9.-]/giu, "_")}.${operation}.msgpack`);
|
const file = join(dir, `${vector.replace(/[^a-z0-9.-]/giu, "_")}.${operation}.msgpack`);
|
||||||
await writeFile(file, bytes);
|
if (isDirectory) {
|
||||||
|
assert.equal(typescript, "refuses", "a directory is never a store");
|
||||||
|
await mkdir(file);
|
||||||
|
} else {
|
||||||
|
await writeFile(file, bytes);
|
||||||
|
}
|
||||||
const store = new SingleFileMessagePackBackingStore(file);
|
const store = new SingleFileMessagePackBackingStore(file);
|
||||||
const run = () =>
|
const run = () =>
|
||||||
operation === "pullAll"
|
operation === "pullAll"
|
||||||
|
|
@ -2627,6 +2634,10 @@ test("SingleFileMessagePackBackingStore replaces a file exactly when it reads",
|
||||||
} else {
|
} else {
|
||||||
assert.ok(!keys.includes(envelopes[0]!.key), `${vector} ${operation}`);
|
assert.ok(!keys.includes(envelopes[0]!.key), `${vector} ${operation}`);
|
||||||
}
|
}
|
||||||
|
} else if (isDirectory) {
|
||||||
|
await assert.rejects(run, `${vector} ${operation}`);
|
||||||
|
assert.ok((await lstat(file)).isDirectory(), `${vector} ${operation} replaced the directory`);
|
||||||
|
assert.deepEqual(await readdir(file), [], `${vector} ${operation} wrote into the directory`);
|
||||||
} else {
|
} else {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
run,
|
run,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||||
using GameCult.Caching.MessagePack;
|
using GameCult.Caching.MessagePack;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Constraints;
|
||||||
using static GameCult.Caching.Tests.ElementIdTests;
|
using static GameCult.Caching.Tests.ElementIdTests;
|
||||||
|
|
||||||
namespace GameCult.Caching.Tests
|
namespace GameCult.Caching.Tests
|
||||||
|
|
@ -61,6 +62,39 @@ namespace GameCult.Caching.Tests
|
||||||
.Select(line => line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries))
|
.Select(line => line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries))
|
||||||
.Select(cells => new TestCaseData(cells[0], cells[CSharp] == "reads").SetName($"{cells[0]} {cells[CSharp]}"));
|
.Select(cells => new TestCaseData(cells[0], cells[CSharp] == "reads").SetName($"{cells[0]} {cells[CSharp]}"));
|
||||||
|
|
||||||
|
// The row `@directory` has no bytes: the walker makes an empty directory at the store path, and a runtime must refuse it untouched.
|
||||||
|
private const string DirectoryRow = "@directory";
|
||||||
|
|
||||||
|
private static byte[] Place(string path, string vector)
|
||||||
|
{
|
||||||
|
if (vector != DirectoryRow)
|
||||||
|
{
|
||||||
|
var bytes = File.ReadAllBytes(Path.Combine(VectorRoot(), vector));
|
||||||
|
File.WriteAllBytes(path, bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A directory at the store path is refused as IOException ("not a store file", CultCache.cs): not a store that is unreadable.
|
||||||
|
private static IResolveConstraint Refusal(string vector) =>
|
||||||
|
vector == DirectoryRow ? Throws.TypeOf<IOException>() : Throws.TypeOf<CultStoreUnreadableException>();
|
||||||
|
|
||||||
|
private static void AssertUntouched(string path, string vector, byte[] bytes, string message)
|
||||||
|
{
|
||||||
|
if (vector == DirectoryRow)
|
||||||
|
{
|
||||||
|
Assert.That(Directory.Exists(path), message);
|
||||||
|
Assert.That(Directory.EnumerateFileSystemEntries(path), Is.Empty, message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Assert.That(File.ReadAllBytes(path), Is.EqualTo(bytes), message);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool HoldsAVariant(byte[] bytes) =>
|
private static bool HoldsAVariant(byte[] bytes) =>
|
||||||
CultDocumentMessagePackSerialization.DeserializeSnapshot(bytes).Records.Any(record => record.Variant != null);
|
CultDocumentMessagePackSerialization.DeserializeSnapshot(bytes).Records.Any(record => record.Variant != null);
|
||||||
|
|
||||||
|
|
@ -90,14 +124,17 @@ namespace GameCult.Caching.Tests
|
||||||
public void OpenReadsExactlyTheFilesTheVectorsSayItReads(string vector, bool reads)
|
public void OpenReadsExactlyTheFilesTheVectorsSayItReads(string vector, bool reads)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_directory, "open.cc");
|
var path = Path.Combine(_directory, "open.cc");
|
||||||
File.WriteAllBytes(path, File.ReadAllBytes(Path.Combine(VectorRoot(), vector)));
|
var bytes = Place(path, vector);
|
||||||
|
|
||||||
void Open() => CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry }).Dispose();
|
void Open() => CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry }).Dispose();
|
||||||
|
|
||||||
if (reads)
|
if (reads)
|
||||||
Assert.DoesNotThrow(Open);
|
Assert.DoesNotThrow(Open);
|
||||||
else
|
else
|
||||||
Assert.That(Open, Throws.TypeOf<CultStoreUnreadableException>().With.Property(nameof(CultStoreUnreadableException.Path)).EqualTo(path));
|
{
|
||||||
|
Assert.That(Open, vector == DirectoryRow ? Refusal(vector) : Throws.TypeOf<CultStoreUnreadableException>().With.Property(nameof(CultStoreUnreadableException.Path)).EqualTo(path));
|
||||||
|
AssertUntouched(path, vector, bytes, "a store this runtime cannot open was changed by the attempt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCaseSource(nameof(Vectors))]
|
[TestCaseSource(nameof(Vectors))]
|
||||||
|
|
@ -105,8 +142,7 @@ namespace GameCult.Caching.Tests
|
||||||
{
|
{
|
||||||
var path = Seed("flush.cc");
|
var path = Seed("flush.cc");
|
||||||
using var cache = CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry });
|
using var cache = CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry });
|
||||||
var bytes = File.ReadAllBytes(Path.Combine(VectorRoot(), vector));
|
var bytes = Place(path, vector);
|
||||||
File.WriteAllBytes(path, bytes);
|
|
||||||
var store = cache.BackingStores[0];
|
var store = cache.BackingStores[0];
|
||||||
|
|
||||||
if (reads && HoldsAVariant(bytes))
|
if (reads && HoldsAVariant(bytes))
|
||||||
|
|
@ -126,8 +162,8 @@ namespace GameCult.Caching.Tests
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(() => store.PushAll(), Throws.TypeOf<CultStoreUnreadableException>());
|
Assert.That(() => store.PushAll(), Refusal(vector));
|
||||||
Assert.That(File.ReadAllBytes(path), Is.EqualTo(bytes), "a file this runtime cannot read was rewritten");
|
AssertUntouched(path, vector, bytes, "a file this runtime cannot read was rewritten");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCaseSource(nameof(Vectors))]
|
[TestCaseSource(nameof(Vectors))]
|
||||||
|
|
@ -135,8 +171,7 @@ namespace GameCult.Caching.Tests
|
||||||
{
|
{
|
||||||
var path = Seed("commit.cc");
|
var path = Seed("commit.cc");
|
||||||
using var cache = CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry });
|
using var cache = CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry });
|
||||||
var bytes = File.ReadAllBytes(Path.Combine(VectorRoot(), vector));
|
var bytes = Place(path, vector);
|
||||||
File.WriteAllBytes(path, bytes);
|
|
||||||
|
|
||||||
if (reads && HoldsAVariant(bytes))
|
if (reads && HoldsAVariant(bytes))
|
||||||
{
|
{
|
||||||
|
|
@ -153,8 +188,8 @@ namespace GameCult.Caching.Tests
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(() => cache.Commit(batch => batch.Upsert(typeof(IdDeck), new IdDeck { Name = "e" }, new CultRecordKey("e"))), Throws.TypeOf<CultStoreUnreadableException>());
|
Assert.That(() => cache.Commit(batch => batch.Upsert(typeof(IdDeck), new IdDeck { Name = "e" }, new CultRecordKey("e"))), Refusal(vector));
|
||||||
Assert.That(File.ReadAllBytes(path), Is.EqualTo(bytes), "a file this runtime cannot read was rewritten");
|
AssertUntouched(path, vector, bytes, "a file this runtime cannot read was rewritten");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DirectoryStore(string name)
|
private string DirectoryStore(string name)
|
||||||
|
|
@ -679,7 +714,7 @@ namespace GameCult.Caching.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public void EveryVectorInTheFolderHasAManifestRow()
|
public void EveryVectorInTheFolderHasAManifestRow()
|
||||||
{
|
{
|
||||||
var rows = Vectors().Select(row => (string)row.Arguments[0]).Where(name => !name.StartsWith("..", StringComparison.Ordinal)).OrderBy(name => name, StringComparer.Ordinal);
|
var rows = Vectors().Select(row => (string)row.Arguments[0]).Where(name => !name.StartsWith("..", StringComparison.Ordinal) && name != DirectoryRow).OrderBy(name => name, StringComparer.Ordinal);
|
||||||
var files = Directory.GetFiles(VectorRoot(), "*.bin").Select(Path.GetFileName).OrderBy(name => name, StringComparer.Ordinal);
|
var files = Directory.GetFiles(VectorRoot(), "*.bin").Select(Path.GetFileName).OrderBy(name => name, StringComparer.Ordinal);
|
||||||
Assert.That(rows, Is.EqualTo(files));
|
Assert.That(rows, Is.EqualTo(files));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ namespace GameCult.Mesh.Tests;
|
||||||
public sealed class CultMeshSingleFileReadabilityTests
|
public sealed class CultMeshSingleFileReadabilityTests
|
||||||
{
|
{
|
||||||
private const int CSharp = 1;
|
private const int CSharp = 1;
|
||||||
|
// The row `@directory` has no bytes: the walker makes an empty directory at the store path, and the write must refuse it untouched.
|
||||||
|
private const string DirectoryRow = "@directory";
|
||||||
private string _root = null!;
|
private string _root = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
|
@ -67,8 +69,15 @@ public sealed class CultMeshSingleFileReadabilityTests
|
||||||
foreach (var (name, write) in writes)
|
foreach (var (name, write) in writes)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_root, name.Replace(' ', '-') + ".cc");
|
var path = Path.Combine(_root, name.Replace(' ', '-') + ".cc");
|
||||||
var bytes = File.ReadAllBytes(Path.Combine(VectorRoot(), vector));
|
var isDirectory = vector == DirectoryRow;
|
||||||
File.WriteAllBytes(path, bytes);
|
var bytes = Array.Empty<byte>();
|
||||||
|
if (isDirectory)
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytes = File.ReadAllBytes(Path.Combine(VectorRoot(), vector));
|
||||||
|
File.WriteAllBytes(path, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
if (reads)
|
if (reads)
|
||||||
{
|
{
|
||||||
|
|
@ -96,8 +105,18 @@ public sealed class CultMeshSingleFileReadabilityTests
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(() => write(path), Throws.TypeOf<CultStoreUnreadableException>(), name);
|
// A directory at the path is refused as IOException ("not a store file"), not as an unreadable store.
|
||||||
File.ReadAllBytes(path).Should().Equal(bytes, name + " rewrote a file this runtime cannot read");
|
if (isDirectory)
|
||||||
|
Assert.That(() => write(path), Throws.TypeOf<IOException>(), name);
|
||||||
|
else
|
||||||
|
Assert.That(() => write(path), Throws.TypeOf<CultStoreUnreadableException>(), name);
|
||||||
|
if (isDirectory)
|
||||||
|
{
|
||||||
|
Directory.Exists(path).Should().BeTrue(name + " replaced a directory");
|
||||||
|
Directory.EnumerateFileSystemEntries(path).Should().BeEmpty(name + " wrote into a directory");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
File.ReadAllBytes(path).Should().Equal(bytes, name + " rewrote a file this runtime cannot read");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +134,7 @@ public sealed class CultMeshSingleFileReadabilityTests
|
||||||
[Test]
|
[Test]
|
||||||
public void EveryVectorInTheFolderHasAManifestRow()
|
public void EveryVectorInTheFolderHasAManifestRow()
|
||||||
{
|
{
|
||||||
var rows = Vectors().Select(row => (string)row.Arguments[0]).Where(name => !name.StartsWith("..", StringComparison.Ordinal)).OrderBy(name => name, StringComparer.Ordinal);
|
var rows = Vectors().Select(row => (string)row.Arguments[0]).Where(name => !name.StartsWith("..", StringComparison.Ordinal) && name != DirectoryRow).OrderBy(name => name, StringComparer.Ordinal);
|
||||||
var files = Directory.GetFiles(VectorRoot(), "*.bin").Select(Path.GetFileName).OrderBy(name => name, StringComparer.Ordinal);
|
var files = Directory.GetFiles(VectorRoot(), "*.bin").Select(Path.GetFileName).OrderBy(name => name, StringComparer.Ordinal);
|
||||||
rows.Should().Equal(files);
|
rows.Should().Equal(files);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# Whether each runtime's own reader opens the file. A flush and a commit replace a file exactly when it reads; where a runtime has a soft flush (TypeScript, Rust) it leaves a file that reads byte for byte and refuses one that does not.
|
# Whether each runtime's own reader opens the file. A flush and a commit replace a file exactly when it reads; where a runtime has a soft flush (TypeScript, Rust) it leaves a file that reads byte for byte and refuses one that does not.
|
||||||
# A store holding two records under one key is unreadable; keys are ordinal strings (no case folding, no normalisation), so distinct spellings are distinct keys.
|
# A store holding two records under one key is unreadable; keys are ordinal strings (no case folding, no normalisation), so distinct spellings are distinct keys.
|
||||||
# Columns: file, csharp, rust, typescript, python. `../v3-base.msgpack` is the one valid store.
|
# Columns: file, csharp, rust, typescript, python. `../v3-base.msgpack` is the one valid store.
|
||||||
|
# `@directory` is no file: the walker makes an empty directory at the store path and every runtime must refuse to open it and to write over it (flush, commit, delete), leaving the directory empty.
|
||||||
../v3-base.msgpack reads reads reads reads
|
../v3-base.msgpack reads reads reads reads
|
||||||
zero-byte.bin refuses refuses refuses refuses
|
zero-byte.bin refuses refuses refuses refuses
|
||||||
empty-array.bin reads reads reads reads
|
empty-array.bin reads reads reads reads
|
||||||
|
|
@ -38,3 +39,7 @@ dup-key-different-payload-v3.bin refuses refuses refuses refuses
|
||||||
distinct-keys-case-v3.bin reads reads reads reads
|
distinct-keys-case-v3.bin reads reads reads reads
|
||||||
distinct-keys-nfd-v3.bin reads reads reads reads
|
distinct-keys-nfd-v3.bin reads reads reads reads
|
||||||
../../document-variants-c0/current-catalog-bad-canonical.msgpack refuses refuses refuses refuses
|
../../document-variants-c0/current-catalog-bad-canonical.msgpack refuses refuses refuses refuses
|
||||||
|
../../document-variants-c0/legacy-catalog-plain.msgpack refuses refuses refuses refuses
|
||||||
|
../../document-variants-c0/legacy-catalog-extra-slot.msgpack refuses refuses refuses refuses
|
||||||
|
../../document-variants-c0/legacy-catalog-trailing.msgpack refuses refuses refuses refuses
|
||||||
|
@directory refuses refuses refuses refuses
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue