using System; using System.IO; using Newtonsoft.Json.Linq; using NUnit.Framework; using Unity.Pipeline.Console; using Unity.Pipeline.Editor.Commands; using Unity.Pipeline.Models; namespace Unity.Pipeline.Tests.Editor { /// /// Tests that the recompile status never claims a clean state while compile errors stand. The /// status file lives under Temp/, which Unity deletes at every editor launch, and a failed /// editor compile suppresses the domain reload that would rewrite it — so the file alone is not /// enough and the native compile-failure flag has to win. /// /// No test triggers a real compile: the flag is read through an injected probe, and the ground /// truth the status overlays is set directly. /// class RecompileStatusTests { const string StatusFile = "Temp/pipeline_recompile_status.json"; ConsoleGroundTruth m_PreviousTruth; Func m_PreviousProbe; string m_PreviousStatus; [SetUp] public void SetUp() { m_PreviousTruth = ConsoleLogCapture.GroundTruth; m_PreviousProbe = RecompileCommand.s_CompilationFailedProbe; m_PreviousStatus = File.Exists(StatusFile) ? File.ReadAllText(StatusFile) : null; } [TearDown] public void TearDown() { ConsoleLogCapture.GroundTruth = m_PreviousTruth; RecompileCommand.s_CompilationFailedProbe = m_PreviousProbe; if (m_PreviousStatus != null) File.WriteAllText(StatusFile, m_PreviousStatus); else if (File.Exists(StatusFile)) File.Delete(StatusFile); } [Test] public void RecompileStatus_ReportsFailedWhenGroundTruthSaysCompilationFailed() { // "idle" is what the file reads after an editor restart, since Temp/ is deleted. WriteStatus("idle", false); ConsoleLogCapture.GroundTruth = Sample(compilationFailed: true); var status = JObject.Parse(RecompileCommand.RecompileStatus()); Assert.IsTrue(status["failed"].ToObject(), "The native flag must win over a stale status file"); Assert.IsTrue(status["compilationFailed"].ToObject()); Assert.AreEqual("completed", status["status"].ToString()); } [Test] public void RecompileStatus_DoesNotOverrideCompletedWithTheNativeFlag() { // The native flag still reads true for a moment after a fixing compile finishes; it // clears with the domain reload that follows. A "completed" status lists what the compile // that just ran actually produced, so it wins. WriteStatus("completed", false); ConsoleLogCapture.GroundTruth = Sample(compilationFailed: true); var status = JObject.Parse(RecompileCommand.RecompileStatus()); Assert.IsFalse(status["failed"].ToObject(), "A build that has just been fixed must not be reported as broken"); Assert.AreEqual("completed", status["status"].ToString()); Assert.IsTrue(status["compilationFailed"].ToObject(), "The flag is still reported, so a client can see the disagreement"); } [Test] public void RecompileStatus_LeavesCleanStatusAloneWhenCompilationSucceeded() { WriteStatus("up_to_date", false); ConsoleLogCapture.GroundTruth = Sample(compilationFailed: false); var status = JObject.Parse(RecompileCommand.RecompileStatus()); Assert.IsFalse(status["failed"].ToObject()); Assert.IsFalse(status["compilationFailed"].ToObject()); Assert.AreEqual("up_to_date", status["status"].ToString()); } [Test] public void Recompile_DoesNotOverwriteFailedWithUpToDate() { // The reported symptom: a repeat recompile with nothing changed compiles nothing, and // "up_to_date" used to erase the failure that was still true. WriteStatus("completed", true, "Assets/Broken.cs(1,1): error CS1002: ; expected"); RecompileCommand.s_CompilationFailedProbe = () => true; var result = RecompileCommand.Recompile(); Assert.AreEqual("failed", result.GetType().GetProperty("status").GetValue(result)); var status = JObject.Parse(RecompileCommand.RecompileStatus()); Assert.IsTrue(status["failed"].ToObject(), "The recorded failure must survive a no-op recompile"); Assert.That(status["errors"].ToObject(), Has.Exactly(1).Contains("CS1002"), "The errors from the failed compile must survive too"); } [Test] public void RecompileStatus_IgnoresStaleGroundTruth() { // Sampling stops while the Editor is blocked, so an old sample says nothing about now. WriteStatus("idle", false); ConsoleLogCapture.GroundTruth = new ConsoleGroundTruth { CompilationFailed = true, SampledUtc = DateTime.UtcNow.AddSeconds(-30) }; var status = JObject.Parse(RecompileCommand.RecompileStatus()); Assert.IsFalse(status["failed"].ToObject(), "A stale sample must not report a build that has just been fixed as broken"); Assert.IsFalse(status["compilationFailed"].ToObject()); } static ConsoleGroundTruth Sample(bool compilationFailed) => new ConsoleGroundTruth { CompilationFailed = compilationFailed, SampledUtc = DateTime.UtcNow }; static void WriteStatus(string status, bool failed, params string[] errors) { Directory.CreateDirectory("Temp"); File.WriteAllText(StatusFile, new JObject { ["status"] = status, ["failed"] = failed, ["errors"] = new JArray(errors) }.ToString()); } } }