using System.Collections;
using System.IO;
using NUnit.Framework;
using Unity.Pipeline.CodeReload;
using Unity.Pipeline.Runtime.Commands;
using UnityEngine;
namespace Unity.Pipeline.Tests.Editor
{
///
/// Probe with method-level [CodeReload] STATIC methods. The weaver injects the same dispatch
/// prologue as for instance methods, passing null as the instance argument; the override
/// keeps the leading instance parameter and receives null.
///
public class StaticReloadProbe
{
public static int baseline;
public static int hotValue;
public int instanceValue;
[CodeReload]
public static void Compute()
{
baseline++; // original body
}
[CodeReload]
public static void Mark(StaticReloadProbe target)
{
target.instanceValue = 1; // original body
}
[CodeReload]
public static IEnumerator Countdown(StaticReloadProbe target)
{
target.instanceValue = -1; // original body
yield return null;
}
}
static class StaticOverrides
{
[CodeReloadOverrideMethod("StaticReloadProbe.Compute")]
public static void Compute(StaticReloadProbe instance) // instance arrives null
{
StaticReloadProbe.hotValue++;
}
}
///
/// Static method code reload: woven dispatch with a null instance, end-to-end reload_file on
/// the compiled (Assembly.Load) and interpreter (IlInterpreter) backends, and the static +
/// coroutine combination.
///
class CodeReloadStaticTests
{
[SetUp]
public void Setup()
{
CodeReloadRegistry.ClearAllForTesting();
StaticReloadProbe.baseline = 0;
StaticReloadProbe.hotValue = 0;
}
[TearDown]
public void TearDown() => CodeReloadRegistry.ClearAllForTesting();
static void RegisterProbeMethod(string name) =>
CodeReloadRegistry.RegisterReloadableMethod(
typeof(StaticReloadProbe).GetMethod(name),
new CodeReloadAttribute());
[Test]
public void WovenDispatch_StaticMethod_RoutesToOverride_WithNullInstance()
{
RegisterProbeMethod(nameof(StaticReloadProbe.Compute));
var registered = CodeReloadRegistry.RegisterMethodOverride(
typeof(StaticOverrides).GetMethod(nameof(StaticOverrides.Compute)),
new CodeReloadOverrideMethodAttribute("StaticReloadProbe.Compute"),
typeof(StaticOverrides),
out var reason);
Assert.IsTrue(registered, reason);
StaticReloadProbe.Compute();
Assert.AreEqual(0, StaticReloadProbe.baseline, "Original body must not run while an override is active");
Assert.AreEqual(1, StaticReloadProbe.hotValue, "Override should have run (with a null instance)");
}
[Test]
public void ReloadFileCompiled_StaticMethod_AppliesEdit()
{
RegisterProbeMethod(nameof(StaticReloadProbe.Compute));
var editedSource = @"
using Unity.Pipeline.CodeReload;
namespace Unity.Pipeline.Tests.Editor
{
public class StaticReloadProbe
{
public static int baseline;
public static int hotValue;
[CodeReload]
public static void Compute()
{
hotValue = 99;
}
}
}";
var path = Path.Combine(Application.temporaryCachePath, "StaticReloadProbe_edit.cs");
File.WriteAllText(path, editedSource);
try
{
var response = CodeReloadCommands.ReloadFile(path);
Assert.IsTrue(response.Success,
$"reload_file should succeed for a static method. Error: {response.ErrorDetails}");
StaticReloadProbe.Compute();
Assert.AreEqual(99, StaticReloadProbe.hotValue, "Edited static body should run via the woven dispatch");
Assert.AreEqual(0, StaticReloadProbe.baseline, "Original body must not run");
}
finally
{
if (File.Exists(path)) File.Delete(path);
}
}
[Test]
public void ReloadFileInterpreter_StaticMethod_WritesThroughParameter()
{
RegisterProbeMethod(nameof(StaticReloadProbe.Mark));
var editedSource = @"
using Unity.Pipeline.CodeReload;
namespace Unity.Pipeline.Tests.Editor
{
public class StaticReloadProbe
{
public int instanceValue;
[CodeReload]
public static void Mark(StaticReloadProbe target)
{
target.instanceValue = 42;
}
}
}";
var path = Path.Combine(Application.temporaryCachePath, "StaticReloadProbe_mark_edit.cs");
File.WriteAllText(path, editedSource);
try
{
var response = CodeReloadCommands.ReloadFileEditorInterpreter(path);
Assert.IsTrue(response.Success,
$"reload_file_editor_interpreter should succeed for a static method. Error: {response.ErrorDetails}");
var probe = new StaticReloadProbe();
StaticReloadProbe.Mark(probe);
Assert.AreEqual(42, probe.instanceValue,
"Edited static body should run via the interpreter dispatch (null instance, real args)");
}
finally
{
if (File.Exists(path)) File.Delete(path);
}
}
[Test]
public void ReloadFileInterpreter_StaticMethod_WritesStaticField()
{
RegisterProbeMethod(nameof(StaticReloadProbe.Compute));
var editedSource = @"
using Unity.Pipeline.CodeReload;
namespace Unity.Pipeline.Tests.Editor
{
public class StaticReloadProbe
{
public static int baseline;
public static int hotValue;
[CodeReload]
public static void Compute()
{
hotValue = 77;
}
}
}";
var path = Path.Combine(Application.temporaryCachePath, "StaticReloadProbe_sfield_edit.cs");
File.WriteAllText(path, editedSource);
try
{
var response = CodeReloadCommands.ReloadFileEditorInterpreter(path);
Assert.IsTrue(response.Success,
$"reload_file_editor_interpreter should succeed. Error: {response.ErrorDetails}");
StaticReloadProbe.Compute();
Assert.AreEqual(77, StaticReloadProbe.hotValue,
"Edited static body should write the host static field via the interpreter");
Assert.AreEqual(0, StaticReloadProbe.baseline, "Original body must not run");
}
finally
{
if (File.Exists(path)) File.Delete(path);
}
}
[Test]
public void ReloadFileInterpreter_StaticCoroutine_RunsAsInterpretedStateMachine()
{
RegisterProbeMethod(nameof(StaticReloadProbe.Countdown));
var editedSource = @"
using System.Collections;
using Unity.Pipeline.CodeReload;
namespace Unity.Pipeline.Tests.Editor
{
public class StaticReloadProbe
{
public int instanceValue;
[CodeReload]
public static IEnumerator Countdown(StaticReloadProbe target)
{
target.instanceValue = 1;
yield return null;
target.instanceValue = 2;
}
}
}";
var path = Path.Combine(Application.temporaryCachePath, "StaticReloadProbe_coro_edit.cs");
File.WriteAllText(path, editedSource);
try
{
var response = CodeReloadCommands.ReloadFileEditorInterpreter(path);
Assert.IsTrue(response.Success,
$"reload_file_editor_interpreter should succeed for a static coroutine. Error: {response.ErrorDetails}");
var probe = new StaticReloadProbe();
var routine = StaticReloadProbe.Countdown(probe); // woven dispatch → interpreter → bridge
Assert.IsTrue(routine.MoveNext());
Assert.AreEqual(1, probe.instanceValue);
Assert.IsFalse(routine.MoveNext());
Assert.AreEqual(2, probe.instanceValue,
"Static coroutine edit should run as an interpreted state machine");
}
finally
{
if (File.Exists(path)) File.Delete(path);
}
}
}
}