Add Unity Pipeline package support

This commit is contained in:
ud18010
2026-07-31 17:34:04 +08:00
parent d1a526094e
commit 786b7ffff9
590 changed files with 51995 additions and 2 deletions
@@ -0,0 +1,199 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Unity.Pipeline.Commands;
using Unity.Pipeline.Editor;
using Unity.Pipeline.Editor.Commands.Baking;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
namespace Unity.Pipeline.Tests.Editor.Baking
{
/// <summary>
/// Tests for the CLI-215 lighting bake commands. These cover the parts that are deterministic
/// without waiting on a real (minutes-long) lightmap bake: command discovery/schema, the
/// settings get/set round-trip, the destructive clear guard, the idle status, the no_scene guard,
/// and dry_run being a no-op. The full bake → completed acceptance flow is exercised live against a
/// running Editor (it is too slow/platform-dependent for the unit suite).
/// </summary>
public class LightingBakeCommandsTests
{
[SetUp]
public void SetUp() => CommandRegistry.SetDiscovery(new TypeCacheCommandDiscovery());
[TearDown]
public void TearDown()
{
// Leave the editor with a fresh empty (untitled) scene so other tests are unaffected.
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
[Test]
public void LightingCommands_AreDiscovered_WithExpectedSchema()
{
var commands = CommandRegistry.DiscoverCommands().ToList();
var bake = commands.FirstOrDefault(c => c.Name == "bake_lighting");
Assert.IsNotNull(bake, "Should discover bake_lighting");
CollectionAssert.AreEquivalent(new[] { "confirm", "dry_run" },
bake.Parameters.Select(p => p.Name).ToList());
var status = commands.FirstOrDefault(c => c.Name == "lighting_bake_status");
Assert.IsNotNull(status, "Should discover lighting_bake_status");
Assert.IsFalse(status.MainThreadRequired,
"lighting_bake_status must be off-main-thread so it answers while a bake holds the main thread");
Assert.AreEqual(0, status.Parameters.Count, "lighting_bake_status takes no parameters");
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "cancel_lighting_bake"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "clear_baked_lighting"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "get_lighting_settings"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "set_lighting_settings"));
}
[Test]
public void GetLightingSettings_ReturnsCoreFields()
{
var settings = LightingBakeCommands.GetLightingSettings();
if (!settings.Available)
{
// No LightingSettings are exposed when no scene is open (e.g. batchmode test
// projects start with an untitled empty scene). Skip rather than fail.
Assert.Pass("No active LightingSettings — skipping (no scene open in test project).");
return;
}
Assert.IsNotNull(settings.Lightmapper, "lightmapper should be reported");
// directionalMode must be one of the two spec values, never a raw flags-enum string.
Assert.That(settings.DirectionalMode, Is.EqualTo("NonDirectional").Or.EqualTo("Directional"),
"directionalMode must be 'NonDirectional' or 'Directional'");
// lightmapCompression must be the LightmapCompression enum name, not a bool.
var validCompressions = new[] { "None", "LowQuality", "NormalQuality", "HighQuality" };
CollectionAssert.Contains(validCompressions, settings.LightmapCompression,
"lightmapCompression must be one of the LightmapCompression enum names");
}
[Test]
public void SetLightingSettings_DirectionalMode_RoundTrips()
{
var before = LightingBakeCommands.GetLightingSettings();
if (!before.Available)
{
Assert.Pass("No active LightingSettings — skipping directionalMode round-trip test.");
return;
}
var target = before.DirectionalMode == "NonDirectional" ? "Directional" : "NonDirectional";
try
{
var result = JObject.FromObject(LightingBakeCommands.SetLightingSettings(
new JObject { ["directionalMode"] = target }));
CollectionAssert.Contains(result["applied"].Select(t => t.Value<string>()).ToList(),
"directionalMode", "directionalMode should be in applied[]");
var after = LightingBakeCommands.GetLightingSettings();
Assert.That(after.DirectionalMode, Is.EqualTo("NonDirectional").Or.EqualTo("Directional"),
"directionalMode must remain a spec-valid string after set");
}
finally
{
LightingBakeCommands.SetLightingSettings(new JObject { ["directionalMode"] = before.DirectionalMode });
}
}
[Test]
public void SetLightingSettings_RoundTrips_BouncesAndResolution()
{
var before = LightingBakeCommands.GetLightingSettings();
var newBounces = before.Bounces == 3 ? 2 : 3;
var newResolution = before.LightmapResolution >= 40f ? 20f : 50f;
try
{
var settings = new JObject
{
["bounces"] = newBounces,
["lightmapResolution"] = newResolution
};
LightingBakeCommands.SetLightingSettings(settings);
var after = LightingBakeCommands.GetLightingSettings();
Assert.AreEqual(newBounces, after.Bounces, "bounces should round-trip");
Assert.AreEqual(newResolution, after.LightmapResolution, 0.001f, "lightmapResolution should round-trip");
}
finally
{
// Restore the prior values so the suite leaves no residue in the host project's
// lighting settings (the active settings may be a shared scene-default or asset).
LightingBakeCommands.SetLightingSettings(new JObject
{
["bounces"] = before.Bounces,
["lightmapResolution"] = before.LightmapResolution
});
}
}
[Test]
public void SetLightingSettings_ReportsUnknownKeys()
{
var settings = new JObject
{
["bounces"] = 2,
["totallyNotARealKey"] = 5
};
var result = JObject.FromObject(LightingBakeCommands.SetLightingSettings(settings));
var applied = result["applied"].Select(t => t.Value<string>()).ToList();
var unknown = result["unknown"].Select(t => t.Value<string>()).ToList();
CollectionAssert.Contains(applied, "bounces");
CollectionAssert.Contains(unknown, "totallyNotARealKey");
}
[Test]
public void SetLightingSettings_DryRun_DoesNotChangeAnything()
{
var before = LightingBakeCommands.GetLightingSettings();
var changed = before.Bounces == 1 ? 4 : 1;
LightingBakeCommands.SetLightingSettings(new JObject { ["bounces"] = changed }, dryRun: true);
var after = LightingBakeCommands.GetLightingSettings();
Assert.AreEqual(before.Bounces, after.Bounces, "dry_run must not mutate settings");
}
[Test]
public void ClearBakedLighting_WithoutConfirm_Throws()
{
Assert.Throws<System.ArgumentException>(() => LightingBakeCommands.ClearBakedLighting());
}
[Test]
public void ClearBakedLighting_DryRun_DoesNotThrow_AndReportsWouldClear()
{
var result = JObject.FromObject(LightingBakeCommands.ClearBakedLighting(confirm: true, dryRun: true));
Assert.AreEqual("dry_run", result["status"].Value<string>());
Assert.IsTrue(result["wouldClear"].Value<bool>());
}
[Test]
public void BakeLighting_NoOpenSavedScene_ReturnsNoScene()
{
// A fresh untitled scene has an empty path, so it is not a bakeable (saved) scene.
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
Assert.IsTrue(string.IsNullOrEmpty(SceneManager.GetActiveScene().path), "test precondition: untitled scene");
var result = JObject.FromObject(LightingBakeCommands.BakeLighting());
Assert.AreEqual("no_scene", result["code"].Value<string>());
}
[Test]
public void LightingBakeStatus_Idle_WhenNothingTriggered_OrAfterStatusFile()
{
// The status reflects either a clean idle or the result of a prior (cancelled/completed)
// bake from another test; in all cases it must be valid JSON carrying a "status" field.
var json = JObject.Parse(LightingBakeCommands.LightingBakeStatus());
Assert.IsNotNull(json["status"], "lighting_bake_status should always include a 'status' field");
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bdeb4ada982746e1aa5d72b49157fd3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,125 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Unity.Pipeline.Commands;
using Unity.Pipeline.Editor;
using Unity.Pipeline.Editor.Commands.Baking;
using UnityEditor.SceneManagement;
namespace Unity.Pipeline.Tests.Editor.Baking
{
/// <summary>
/// Tests for the CLI-215 legacy NavMesh bake commands. Covers discovery/schema, the settings
/// get/set round-trip, the destructive clear guard, the idle status, the no_scene guard, dry_run
/// being a no-op, and the AI-Navigation stub returning package_not_found when the package is
/// absent. The full bake → succeeded flow is exercised live.
/// </summary>
public class NavMeshBakeCommandsTests
{
[SetUp]
public void SetUp() => CommandRegistry.SetDiscovery(new TypeCacheCommandDiscovery());
[TearDown]
public void TearDown()
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
[Test]
public void NavMeshCommands_AreDiscovered_WithExpectedSchema()
{
var commands = CommandRegistry.DiscoverCommands().ToList();
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "bake_navmesh"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "cancel_navmesh_bake"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "clear_navmesh"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "get_navmesh_settings"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "set_navmesh_settings"));
var status = commands.FirstOrDefault(c => c.Name == "navmesh_bake_status");
Assert.IsNotNull(status, "Should discover navmesh_bake_status");
Assert.IsFalse(status.MainThreadRequired, "navmesh_bake_status must be off-main-thread");
Assert.AreEqual(0, status.Parameters.Count, "navmesh_bake_status takes no parameters");
}
[Test]
public void GetNavMeshSettings_ReturnsAgentFields()
{
var settings = NavMeshBakeCommands.GetNavMeshSettings();
Assert.IsTrue(settings.Available, "Legacy NavMesh settings should be available");
Assert.Greater(settings.AgentRadius, 0f, "agentRadius should be a sensible positive default");
Assert.Greater(settings.AgentHeight, 0f, "agentHeight should be a sensible positive default");
}
[Test]
public void SetNavMeshSettings_RoundTrips_AgentRadius()
{
var before = NavMeshBakeCommands.GetNavMeshSettings();
var newRadius = before.AgentRadius >= 0.5f ? 0.35f : 0.75f;
NavMeshBakeCommands.SetNavMeshSettings(new JObject { ["agentRadius"] = newRadius });
var after = NavMeshBakeCommands.GetNavMeshSettings();
Assert.AreEqual(newRadius, after.AgentRadius, 0.001f, "agentRadius should round-trip");
// Restore the prior value so the suite leaves no residue in the host project's settings.
NavMeshBakeCommands.SetNavMeshSettings(new JObject { ["agentRadius"] = before.AgentRadius });
}
[Test]
public void SetNavMeshSettings_ReportsUnknownKeys()
{
var result = JObject.FromObject(
NavMeshBakeCommands.SetNavMeshSettings(new JObject { ["agentRadius"] = 0.5f, ["nope"] = 1 }, dryRun: true));
var applied = result["applied"].Select(t => t.Value<string>()).ToList();
var unknown = result["unknown"].Select(t => t.Value<string>()).ToList();
CollectionAssert.Contains(applied, "agentRadius");
CollectionAssert.Contains(unknown, "nope");
}
[Test]
public void SetNavMeshSettings_DryRun_DoesNotChangeAnything()
{
var before = NavMeshBakeCommands.GetNavMeshSettings();
var changed = before.AgentRadius >= 0.5f ? 0.2f : 0.9f;
NavMeshBakeCommands.SetNavMeshSettings(new JObject { ["agentRadius"] = changed }, dryRun: true);
var after = NavMeshBakeCommands.GetNavMeshSettings();
Assert.AreEqual(before.AgentRadius, after.AgentRadius, 0.001f, "dry_run must not mutate settings");
}
[Test]
public void ClearNavMesh_WithoutConfirm_Throws()
{
Assert.Throws<System.ArgumentException>(() => NavMeshBakeCommands.ClearNavMesh());
}
[Test]
public void BakeNavMesh_NoOpenSavedScene_ReturnsNoScene()
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var result = JObject.FromObject(NavMeshBakeCommands.BakeNavMesh());
Assert.AreEqual("no_scene", result["code"].Value<string>());
}
[Test]
public void NavMeshBakeStatus_AlwaysHasStatusField()
{
var json = JObject.Parse(NavMeshBakeCommands.NavMeshBakeStatus());
Assert.IsNotNull(json["status"]);
}
[Test]
public void BakeNavMeshSurfaces_WithoutPackage_ReturnsPackageNotFound()
{
// This repo does not reference com.unity.ai.navigation, so the stub should report the
// package is absent (or, if the package is present in the host project, not_implemented).
var result = JObject.FromObject(NavMeshBakeCommands.BakeNavMeshSurfaces());
var code = result["code"].Value<string>();
Assert.That(code, Is.EqualTo("package_not_found").Or.EqualTo("not_implemented"));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d88fed4842cc4242b9e25237547c5514
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Unity.Pipeline.Commands;
using Unity.Pipeline.Editor;
using Unity.Pipeline.Editor.Commands.Baking;
using UnityEditor.SceneManagement;
namespace Unity.Pipeline.Tests.Editor.Baking
{
/// <summary>
/// Tests for the CLI-215 occlusion-culling bake commands. Covers discovery/schema, the destructive
/// clear guard, the idle status, the no_scene guard, and dry_run being a no-op (and echoing the
/// effective parameters). The full bake → succeeded flow is exercised live.
/// </summary>
public class OcclusionBakeCommandsTests
{
[SetUp]
public void SetUp() => CommandRegistry.SetDiscovery(new TypeCacheCommandDiscovery());
[TearDown]
public void TearDown()
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
[Test]
public void OcclusionCommands_AreDiscovered_WithExpectedSchema()
{
var commands = CommandRegistry.DiscoverCommands().ToList();
var bake = commands.FirstOrDefault(c => c.Name == "bake_occlusion_culling");
Assert.IsNotNull(bake, "Should discover bake_occlusion_culling");
CollectionAssert.AreEquivalent(
new[] { "smallest_occluder", "smallest_hole", "backface_threshold", "confirm", "dry_run" },
bake.Parameters.Select(p => p.Name).ToList());
var status = commands.FirstOrDefault(c => c.Name == "occlusion_bake_status");
Assert.IsNotNull(status, "Should discover occlusion_bake_status");
Assert.IsFalse(status.MainThreadRequired, "occlusion_bake_status must be off-main-thread");
Assert.AreEqual(0, status.Parameters.Count, "occlusion_bake_status takes no parameters");
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "cancel_occlusion_bake"));
Assert.IsNotNull(commands.FirstOrDefault(c => c.Name == "clear_occlusion_culling"));
}
[Test]
public void ClearOcclusion_WithoutConfirm_Throws()
{
Assert.Throws<System.ArgumentException>(() => OcclusionBakeCommands.ClearOcclusionCulling());
}
[Test]
public void BakeOcclusion_NoOpenSavedScene_ReturnsNoScene()
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var result = JObject.FromObject(OcclusionBakeCommands.BakeOcclusionCulling());
Assert.AreEqual("no_scene", result["code"].Value<string>());
}
[Test]
public void OcclusionBakeStatus_AlwaysHasStatusField()
{
var json = JObject.Parse(OcclusionBakeCommands.OcclusionBakeStatus());
Assert.IsNotNull(json["status"]);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f65c8110a95f44cbbf3aaf0d9832aa0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: