mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
211 lines
8.1 KiB
C#
211 lines
8.1 KiB
C#
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using Translator.Cli.Configuration;
|
|
using Translator.Core.Mods;
|
|
using Xunit;
|
|
|
|
namespace Translator.Tests;
|
|
|
|
public sealed class TranslateRecursiveRuntimeConfigTests
|
|
{
|
|
[Fact]
|
|
public void UnsupportedOpcodeFailsStrictTranslation()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), $"translator-unsupported-opcode-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(root);
|
|
try
|
|
{
|
|
const uint entry = 0x80001000u;
|
|
File.WriteAllBytes(
|
|
Path.Combine(root, "main.dol"),
|
|
SyntheticDolFactory.CreateBytes(
|
|
entry,
|
|
sections: [SyntheticDolFactory.Text(
|
|
0,
|
|
entry,
|
|
0x04000000u, // unassigned primary opcode; deliberately unsupported by the lifter
|
|
0x4E800020u)])); // blr
|
|
var projectPath = Path.Combine(root, "recomp.yml");
|
|
File.WriteAllText(
|
|
projectPath,
|
|
"""
|
|
schema_version: 1
|
|
project:
|
|
id: unsupported-opcode-test
|
|
memory:
|
|
sda_base: 0x80002000
|
|
sda2_base: 0x80003000
|
|
inputs:
|
|
dol:
|
|
path: main.dol
|
|
translation:
|
|
entry_points: [0x80001000]
|
|
output:
|
|
root: generated
|
|
""");
|
|
Assert.False(TranslationProjectConfig.Load(projectPath).Translation.AllowUnsupportedInstructions);
|
|
|
|
var strictError = Assert.Throws<TargetInvocationException>(() => InvokeCli(
|
|
"translate-recursive",
|
|
$"0x{entry:X8}",
|
|
"--project", projectPath,
|
|
"--outdir", Path.Combine(root, "strict", "functions"),
|
|
"--output-metadata", Path.Combine(root, "strict", "metadata.json"),
|
|
"--threads", "1"));
|
|
Assert.Contains("UNIMPLEMENTED", strictError.InnerException?.ToString(), StringComparison.Ordinal);
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void RepeatedRunsReuseUnchangedOutputsAndPruneStaleFiles()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), $"translator-recursive-prune-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(root);
|
|
try
|
|
{
|
|
const uint entry = 0x80001000u;
|
|
File.WriteAllBytes(
|
|
Path.Combine(root, "main.dol"),
|
|
SyntheticDolFactory.CreateBytes(
|
|
entry,
|
|
sections: [SyntheticDolFactory.Text(
|
|
0,
|
|
entry,
|
|
0x60000000u, // nop
|
|
0x60000000u, // nop
|
|
0x4E800020u)])); // blr
|
|
var projectPath = Path.Combine(root, "recomp.yml");
|
|
File.WriteAllText(
|
|
projectPath,
|
|
"""
|
|
schema_version: 1
|
|
project:
|
|
id: recursive-prune-test
|
|
display_name: Recursive Prune Test
|
|
memory:
|
|
sda_base: 0x80002000
|
|
sda2_base: 0x80003000
|
|
inputs:
|
|
dol:
|
|
path: main.dol
|
|
translation:
|
|
entry_points: [0x80001000]
|
|
output:
|
|
root: generated
|
|
""");
|
|
|
|
var stagedFunctions = Path.Combine(root, "staging", "functions");
|
|
var stagedOutputMetadata = Path.Combine(root, "staging", "functions_metadata.json");
|
|
var exitCode = InvokeCli(
|
|
"translate-recursive",
|
|
$"0x{entry:X8}",
|
|
"--project",
|
|
projectPath,
|
|
"--outdir",
|
|
stagedFunctions,
|
|
"--output-metadata",
|
|
stagedOutputMetadata,
|
|
"--threads",
|
|
"1");
|
|
|
|
Assert.Equal(0, exitCode);
|
|
var firstMetadata = BaseTranslationOutputMetadataFile.Read(stagedOutputMetadata);
|
|
Assert.Null(firstMetadata.TranslationIdentityHash);
|
|
Assert.Equal(0, firstMetadata.Quality.UnsupportedInstructionCount);
|
|
Assert.Equal(0, firstMetadata.Quality.InvalidSsaFunctionCount);
|
|
Assert.Single(firstMetadata.Functions);
|
|
Assert.Single(Directory.GetFiles(stagedFunctions, "*.cpp"));
|
|
var generatedPath = Directory.GetFiles(stagedFunctions, "*.cpp").Single();
|
|
var unchangedTimestamp = new DateTime(2004, 5, 6, 7, 8, 10, DateTimeKind.Utc);
|
|
File.SetLastWriteTimeUtc(generatedPath, unchangedTimestamp);
|
|
// A populated output tree is not an input to recursive discovery.
|
|
// This stale filename used to become a synthetic function boundary
|
|
// and truncate the second translation at entry + 4.
|
|
File.WriteAllText(Path.Combine(stagedFunctions, $"func_{entry + 4:X8}.cpp"), "stale output hint");
|
|
var metadataTimestamp = new DateTime(2004, 5, 6, 7, 8, 12, DateTimeKind.Utc);
|
|
File.SetLastWriteTimeUtc(stagedOutputMetadata, metadataTimestamp);
|
|
|
|
var secondExitCode = InvokeCli(
|
|
"translate-recursive",
|
|
$"0x{entry:X8}",
|
|
"--project",
|
|
projectPath,
|
|
"--outdir",
|
|
stagedFunctions,
|
|
"--output-metadata",
|
|
stagedOutputMetadata,
|
|
"--threads",
|
|
"1");
|
|
|
|
Assert.Equal(0, secondExitCode);
|
|
Assert.Equal(unchangedTimestamp, File.GetLastWriteTimeUtc(generatedPath));
|
|
Assert.Equal(metadataTimestamp, File.GetLastWriteTimeUtc(stagedOutputMetadata));
|
|
|
|
var stalePath = Path.Combine(stagedFunctions, "func_80001004.cpp");
|
|
var unlistedPath = Path.Combine(stagedFunctions, "unlisted.cpp");
|
|
File.WriteAllText(stalePath, "// stale");
|
|
File.WriteAllText(unlistedPath, "// not translator-owned");
|
|
BaseTranslationOutputMetadataFile.WriteIfChangedAtomic(
|
|
stagedOutputMetadata,
|
|
BaseTranslationOutputMetadata.Create(
|
|
firstMetadata.Functions.Concat([
|
|
new BaseTranslationFunctionMetadata(
|
|
"func_80001004.cpp",
|
|
8,
|
|
new string('e', 64),
|
|
0x80001004u,
|
|
[])
|
|
]),
|
|
TranslationQualityMetadata.Clean));
|
|
|
|
var pruneExitCode = InvokeCli(
|
|
"translate-recursive",
|
|
$"0x{entry:X8}",
|
|
"--project",
|
|
projectPath,
|
|
"--outdir",
|
|
stagedFunctions,
|
|
"--output-metadata",
|
|
stagedOutputMetadata,
|
|
"--prune-stale",
|
|
"--threads",
|
|
"1");
|
|
Assert.Equal(0, pruneExitCode);
|
|
Assert.False(File.Exists(stalePath));
|
|
Assert.True(File.Exists(unlistedPath));
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(root))
|
|
{
|
|
Directory.Delete(root, recursive: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int InvokeCli(params string[] args)
|
|
{
|
|
var entryPoint = typeof(TranslationProjectConfig).Assembly.EntryPoint;
|
|
Assert.NotNull(entryPoint);
|
|
var result = entryPoint!.Invoke(null, new object?[] { args });
|
|
return result switch
|
|
{
|
|
int exitCode => exitCode,
|
|
Task<int> intTask => intTask.GetAwaiter().GetResult(),
|
|
Task task => CompleteTask(task),
|
|
null => 0,
|
|
_ => throw new InvalidOperationException($"Unexpected CLI entry point result type: {result.GetType().FullName}")
|
|
};
|
|
}
|
|
|
|
private static int CompleteTask(Task task)
|
|
{
|
|
task.GetAwaiter().GetResult();
|
|
return 0;
|
|
}
|
|
}
|