using System.Collections; using System.IO; using NUnit.Framework; using Unity.Pipeline.HotReload; using UnityEngine; using UnityEngine.TestTools; namespace Unity.Pipeline.Tests.Editor { /// /// Integration tests for hot reload end-to-end functionality. /// Tests the complete workflow: register methods → create overrides → compile → verify runtime behavior. /// public class HotReloadIntegrationTests { /// /// Test component that properly integrates hot reload pattern. /// public class IntegratedTestComponent : MonoBehaviour { public int lastValue; public string lastMessage; public Vector3 lastMovement; [HotReloadWithOverrides] public void ProcessValue(int input) { HotReloadHelper.ExecuteWithHotReload(this, "ProcessValue", () => OriginalProcessValue(input), input); } [HotReloadWithOverrides(Id = "CustomMovement")] public void Move(Vector3 direction) { HotReloadHelper.ExecuteWithHotReloadCustomId(this, "CustomMovement", () => OriginalMove(direction), direction); } private void OriginalProcessValue(int input) { lastValue = input * 2; lastMessage = $"Original: {input} * 2 = {lastValue}"; } private void OriginalMove(Vector3 direction) { lastMovement = direction * 1.5f; } } /// /// Mock hot reload override methods for testing. /// This simulates what would be in a HotReload/TestFile.cs. /// public static class MockHotReloadOverrides { [HotReloadOverrideMethod("IntegratedTestComponent.ProcessValue")] public static void ProcessValue(IntegratedTestComponent instance, int input) { instance.lastValue = input * 3; // Different calculation instance.lastMessage = $"HotReload: {input} * 3 = {instance.lastValue}"; } [HotReloadOverrideMethod("CustomMovement")] public static void Move(IntegratedTestComponent instance, Vector3 direction) { instance.lastMovement = direction * 2.5f; // Different multiplier } } private IntegratedTestComponent testComponent; private GameObject testObject; [SetUp] public void SetUp() { // Create test component testObject = new GameObject("IntegrationTestObject"); testComponent = testObject.AddComponent(); // Clear hot reload state (including reloadable methods for test isolation) HotReloadRegistry.ClearAllForTesting(); // Register the test methods as hot reloadable var processMethod = typeof(IntegratedTestComponent).GetMethod("ProcessValue"); var moveMethod = typeof(IntegratedTestComponent).GetMethod("Move"); HotReloadRegistry.RegisterReloadableMethod(processMethod, new HotReloadWithOverridesAttribute()); HotReloadRegistry.RegisterReloadableMethod(moveMethod, new HotReloadWithOverridesAttribute { Id = "CustomMovement" }); } [TearDown] public void TearDown() { if (testObject != null) { Object.DestroyImmediate(testObject); } HotReloadRegistry.ClearAllForTesting(); } [Test] public void IntegratedComponent_OriginalBehavior_WorksCorrectly() { // Act - call methods without hot reload overrides testComponent.ProcessValue(5); testComponent.Move(Vector3.right); // Assert - should use original implementations Assert.That(testComponent.lastValue, Is.EqualTo(10)); // 5 * 2 Assert.That(testComponent.lastMessage, Is.EqualTo("Original: 5 * 2 = 10")); Assert.That(testComponent.lastMovement, Is.EqualTo(Vector3.right * 1.5f)); } [Test] public void IntegratedComponent_WithHotReloadOverride_UsesOverrideLogic() { // Arrange - register hot reload overrides var processOverride = typeof(MockHotReloadOverrides).GetMethod("ProcessValue"); var moveOverride = typeof(MockHotReloadOverrides).GetMethod("Move"); HotReloadRegistry.RegisterMethodOverride(processOverride, new HotReloadOverrideMethodAttribute("IntegratedTestComponent.ProcessValue"), typeof(MockHotReloadOverrides)); HotReloadRegistry.RegisterMethodOverride(moveOverride, new HotReloadOverrideMethodAttribute("CustomMovement"), typeof(MockHotReloadOverrides)); // Act - call methods with hot reload overrides active testComponent.ProcessValue(5); testComponent.Move(Vector3.right); // Assert - should use hot reload implementations Assert.That(testComponent.lastValue, Is.EqualTo(15)); // 5 * 3 (hot reload version) Assert.That(testComponent.lastMessage, Is.EqualTo("HotReload: 5 * 3 = 15")); Assert.That(testComponent.lastMovement, Is.EqualTo(Vector3.right * 2.5f)); // hot reload version } [Test] public void HotReloadHelper_IsHotReloadActive_ReturnsCorrectStatus() { // Arrange - no overrides initially Assert.That(HotReloadHelper.IsHotReloadActive("ProcessValue"), Is.False); Assert.That(HotReloadHelper.IsHotReloadActive("CustomMovement"), Is.False); // Act - register override var processOverride = typeof(MockHotReloadOverrides).GetMethod("ProcessValue"); HotReloadRegistry.RegisterMethodOverride(processOverride, new HotReloadOverrideMethodAttribute("IntegratedTestComponent.ProcessValue"), typeof(MockHotReloadOverrides)); // Assert - now should be active Assert.That(HotReloadHelper.IsHotReloadActive("ProcessValue"), Is.True); Assert.That(HotReloadHelper.IsHotReloadActive("CustomMovement"), Is.False); } [Test] public void HotReloadRegistry_Stats_ReflectCurrentState() { // Arrange - register override var processOverride = typeof(MockHotReloadOverrides).GetMethod("ProcessValue"); HotReloadRegistry.RegisterMethodOverride(processOverride, new HotReloadOverrideMethodAttribute("IntegratedTestComponent.ProcessValue"), typeof(MockHotReloadOverrides)); // Act var stats = HotReloadRegistry.GetStats(); // Assert Assert.That(stats.ReloadableMethodCount, Is.EqualTo(2)); // ProcessValue + Move Assert.That(stats.ActiveOverrideCount, Is.EqualTo(1)); // Only ProcessValue override Assert.That(stats.ActiveOverrideIds.Contains("IntegratedTestComponent.ProcessValue"), Is.True); Assert.That(stats.ReloadableMethodIds.Contains("IntegratedTestComponent.ProcessValue"), Is.True); Assert.That(stats.ReloadableMethodIds.Contains("CustomMovement"), Is.True); } [Test] public void HotReloadRegistry_ClearOverrides_RestoresOriginalBehavior() { // Arrange - register and test override var processOverride = typeof(MockHotReloadOverrides).GetMethod("ProcessValue"); HotReloadRegistry.RegisterMethodOverride(processOverride, new HotReloadOverrideMethodAttribute("IntegratedTestComponent.ProcessValue"), typeof(MockHotReloadOverrides)); testComponent.ProcessValue(5); Assert.That(testComponent.lastValue, Is.EqualTo(15)); // Hot reload behavior // Act - clear overrides (but keep reloadable method registrations) HotReloadRegistry.ClearAllOverrides(); // Assert - should revert to original behavior testComponent.ProcessValue(5); Assert.That(testComponent.lastValue, Is.EqualTo(10)); // Original behavior Assert.That(testComponent.lastMessage, Is.EqualTo("Original: 5 * 2 = 10")); } [UnityTest] public IEnumerator HotReloadIntegration_MainThreadDispatch_WorksCorrectly() { // Arrange var processOverride = typeof(MockHotReloadOverrides).GetMethod("ProcessValue"); HotReloadRegistry.RegisterMethodOverride(processOverride, new HotReloadOverrideMethodAttribute("IntegratedTestComponent.ProcessValue"), typeof(MockHotReloadOverrides)); // Act - invoke from main thread testComponent.ProcessValue(7); // Yield to allow any async operations yield return null; // Assert - hot reload should work on main thread Assert.That(testComponent.lastValue, Is.EqualTo(21)); // 7 * 3 Assert.That(testComponent.lastMessage, Is.EqualTo("HotReload: 7 * 3 = 21")); } } }