Files
sharpemu/tests/SharpEmu.Libs.Tests/Ampr/AmprFileRegistryTests.cs
T
2026-08-03 00:36:00 +03:00

155 lines
6.1 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Libs.Ampr;
using Xunit;
namespace SharpEmu.Libs.Tests.Ampr;
// AmprFileRegistry is process-global static state, so the classes that index
// or clear it must not run concurrently with each other.
[Collection("AmprFileRegistry")]
public class AmprFileRegistryTests
{
[Fact]
public void ComputeFileId_matches_utf8_fnv1a()
{
const string relative = "CoreData/foo/bar.bin";
Assert.Equal(FnvUtf8("$/" + relative), AmprFileRegistry.ComputeFileId("$/" + relative));
Assert.Equal(FnvUtf8("/app0/" + relative), AmprFileRegistry.ComputeFileId("/app0/" + relative));
Assert.Equal(FnvUtf8("app0/" + relative), AmprFileRegistry.ComputeFileId("app0/" + relative));
Assert.Equal(FnvUtf8(relative), AmprFileRegistry.ComputeFileId(relative));
}
[Fact]
public void RegisterApp0Relative_publishes_same_ids_as_string_hashes()
{
AmprFileRegistry.ClearForTests();
const string relative = "misc/loadouts/test.txt";
var host = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-test", relative);
AmprFileRegistry.RegisterApp0RelativeForTests(relative, host);
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("$/" + relative), out var a));
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("/app0/" + relative), out var b));
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("app0/" + relative), out var c));
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId(relative), out var d));
Assert.Equal(host, a);
Assert.Equal(host, b);
Assert.Equal(host, c);
Assert.Equal(host, d);
}
[Fact]
public void Register_publishes_all_app0_path_aliases()
{
AmprFileRegistry.ClearForTests();
const string relative = "scripts/cp11/cp11main.script";
var host = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-test2", relative);
AmprFileRegistry.Register("$/" + relative, host);
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("$/" + relative), out var a));
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("/app0/" + relative), out var b));
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("app0/" + relative), out var c));
Assert.True(AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId(relative), out var d));
Assert.Equal(host, a);
Assert.Equal(host, b);
Assert.Equal(host, c);
Assert.Equal(host, d);
}
[Fact]
public void App0_index_cache_keeps_files_that_differ_only_by_case()
{
var root = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-case-" + Guid.NewGuid().ToString("N"));
var cacheDir = Path.Combine(root, "..", "sharpemu-ampr-cache-" + Guid.NewGuid().ToString("N"));
var upper = Path.Combine(root, "data", "ASSET.bin");
var lower = Path.Combine(root, "data", "asset.bin");
var previousCacheDir = Environment.GetEnvironmentVariable("SHARPEMU_AMPR_INDEX_CACHE");
try
{
Directory.CreateDirectory(Path.Combine(root, "sce_sys"));
Directory.CreateDirectory(Path.Combine(root, "data"));
File.WriteAllText(Path.Combine(root, "sce_sys", "param.json"), "{}");
File.WriteAllBytes(upper, [1, 2, 3]);
if (File.Exists(lower))
{
// Case-insensitive host: the two names are one file, so there is
// nothing for an ignore-case index to lose.
return;
}
File.WriteAllBytes(lower, [4, 5, 6]);
Environment.SetEnvironmentVariable("SHARPEMU_AMPR_INDEX_CACHE", cacheDir);
var normalizedRoot = Path.GetFullPath(root);
var expectedUpper = Path.Combine(normalizedRoot, "data", "ASSET.bin");
var expectedLower = Path.Combine(normalizedRoot, "data", "asset.bin");
// Fresh tree walk, which also writes the on-disk index cache.
AmprFileRegistry.ClearForTests();
AmprFileRegistry.EnsureApp0Indexed(root);
AssertResolves(expectedUpper, expectedLower);
// Second boot: served from the cache the walk just wrote.
AmprFileRegistry.ClearForTests();
AmprFileRegistry.EnsureApp0Indexed(root);
AssertResolves(expectedUpper, expectedLower);
}
finally
{
Environment.SetEnvironmentVariable("SHARPEMU_AMPR_INDEX_CACHE", previousCacheDir);
AmprFileRegistry.ClearForTests();
TryDeleteDirectory(cacheDir);
TryDeleteDirectory(root);
}
static void AssertResolves(string expectedUpper, string expectedLower)
{
Assert.True(
AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("$/data/ASSET.bin"), out var actualUpper),
"data/ASSET.bin is missing from the app0 index.");
Assert.True(
AmprFileRegistry.TryGetHostPath(
AmprFileRegistry.ComputeFileId("$/data/asset.bin"), out var actualLower),
"data/asset.bin is missing from the app0 index.");
Assert.Equal(expectedUpper, actualUpper);
Assert.Equal(expectedLower, actualLower);
}
}
private static void TryDeleteDirectory(string path)
{
try
{
Directory.Delete(path, recursive: true);
}
catch (Exception)
{
// Temp cleanup is best-effort.
}
}
private static uint FnvUtf8(string text)
{
const uint offset = 2166136261;
const uint prime = 16777619;
var hash = offset;
foreach (var b in System.Text.Encoding.UTF8.GetBytes(text))
{
hash ^= b;
hash *= prime;
}
return hash;
}
}