using System; using System.Collections.Generic; using NUnit.Framework; using Unity.Pipeline.Compilation; namespace Unity.Pipeline.Tests.Runtime { /// /// Compact smoke test targeting the interpreter's AOT-hazard surface. In the editor / a Mono /// player it passes trivially; its purpose is to fail loudly when run in an IL2CPP standalone /// player if an AOT regression (linker stripping, MakeGenericMethod, generic virtual /// dispatch, or the AllowTypeStruct<T> boxed-struct fast path) creeps in. /// /// The probe scripts are pre-compiled on desktop and embedded as IL blobs /// (, generated by IlInterpreter/tests/IlInterpreter.ProbeGen) — /// Roslyn cannot compile inside an IL2CPP player (proven by CI job 73639037), and the faithful /// push_reload scenario ships compiled bytes anyway; the interpreter itself needs no Roslyn. /// /// Categorised "Il2Cpp" so the CI player-build job can target it (see .yamato/il2cpp-playmode.yml). /// [Category("Il2Cpp")] class IlInterpreterAotSmokeTests { static IlInterpreter.Interpreter.ScriptInterpreter Load(string base64) { // CreateStandard registers AllowTypeStruct() — the boxed-struct fast path that // is the primary IL2CPP hazard this test guards. It's the same builder the eval and // code-reload sinks run in the real player. var binding = IlInterpreterHostBindings.CreateStandard(); var interp = new IlInterpreter.Interpreter.ScriptInterpreter(binding); interp.Load(new RawScript(Convert.FromBase64String(base64))); return interp; } [Test] public void AllowTypeStruct_Vector3_FlatFrame_RoundTrips() { using var interp = Load(IlInterpreterAotProbeBlobs.HazardProbe); Assert.AreEqual(6f, (float)interp.Invoke("VecSum"), 1e-4f); } [Test] public void GenericTypedInvoke_TwoArgs() { using var interp = Load(IlInterpreterAotProbeBlobs.HazardProbe); // Invoke exercises the generic typed-arg path (MakeGenericMethod under IL2CPP). Assert.AreEqual(42, interp.Invoke("Add", 19, 23)); } // A representative slice of the desktop differential-oracle corpus, replayed under IL2CPP. // Sources and expected values live in the generator, which re-verifies both (real-C# oracle // + interpreter) at generation time; this closes the "desktop interpreter == IL2CPP // interpreter" link for more than the two hazard paths above. static IEnumerable IntCorpusCases() { foreach (var c in IlInterpreterAotProbeBlobs.IntCorpus) yield return new TestCaseData(c.Base64, c.Expected).SetName( nameof(Corpus_Int_MatchesDesktop) + "_" + c.Name); } [TestCaseSource(nameof(IntCorpusCases))] public void Corpus_Int_MatchesDesktop(string base64, int expected) { using var interp = Load(base64); Assert.AreEqual(expected, (int)interp.Invoke("Run")); } [Test] public void Corpus_Float_MatchesDesktop() { using var interp = Load(IlInterpreterAotProbeBlobs.FloatProbe); Assert.AreEqual(3.5f, (float)interp.Invoke("Run"), 1e-4f); } sealed class RawScript : IlInterpreter.IScript { public RawScript(byte[] bytes) { Il = bytes; } public string Name => "IlInterpreterAotProbe"; public ReadOnlyMemory Il { get; } } } }