Add Unity Pipeline package support
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// A trivial ScriptableObject used by the asset-command tests to exercise create / find / move /
|
||||
/// delete with a real, project-resolvable type.
|
||||
/// </summary>
|
||||
public class CLI191SampleAsset : ScriptableObject
|
||||
{
|
||||
public int Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the CLI-191 asset lifecycle commands, exercised both directly (static method) and via
|
||||
/// the isolated <see cref="PipelineTestServer"/>. Covers the acceptance flow (create folder tree →
|
||||
/// create ScriptableObject → find by type → move → delete), the sandbox guard (out-of-project
|
||||
/// writes rejected), and the destructive guard (delete without confirm rejected).
|
||||
/// </summary>
|
||||
public class AssetCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI191Test";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectRef PathRef(string path) => new ObjectRef { Path = path };
|
||||
|
||||
#region Direct
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_ScriptableObject_CreatesAssetAndReturnsIdentity()
|
||||
{
|
||||
var path = Root + "/Data/Sample.asset";
|
||||
var result = AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
Assert.IsNotEmpty(result.Guid, "Created asset should have a GUID");
|
||||
Assert.IsNotNull(AssetDatabase.LoadAssetAtPath<CLI191SampleAsset>(path), "Asset should exist on disk");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_OverwriteWithoutConfirm_Throws()
|
||||
{
|
||||
var path = Root + "/Data/Dup.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName),
|
||||
"Overwriting an existing asset without confirm should be rejected");
|
||||
|
||||
// With confirm it succeeds.
|
||||
var result = AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName, confirm: true);
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_OutOfProject_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AssetCommands.CreateAsset("../Outside.asset", typeof(CLI191SampleAsset).FullName));
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AssetCommands.CreateAsset("Assets/../Outside.asset", typeof(CLI191SampleAsset).FullName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindAssets_ByType_FindsCreatedAsset()
|
||||
{
|
||||
var path = Root + "/Find/Target.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
var found = AssetCommands.FindAssets(type: nameof(CLI191SampleAsset), searchIn: Root);
|
||||
|
||||
Assert.GreaterOrEqual(found.Count, 1, "Should find the created asset by type");
|
||||
CollectionAssert.Contains(
|
||||
System.Linq.Enumerable.Select(found.Assets, a => a.AssetPath),
|
||||
path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindAssets_NoFilter_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(() => AssetCommands.FindAssets());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveAsset_RelocatesAndPreservesGuid()
|
||||
{
|
||||
var src = Root + "/Move/Src.asset";
|
||||
AssetCommands.CreateAsset(src, typeof(CLI191SampleAsset).FullName);
|
||||
var srcGuid = AssetDatabase.AssetPathToGUID(src);
|
||||
|
||||
var dest = Root + "/Move/Dest/Moved.asset";
|
||||
var result = AssetCommands.MoveAsset(PathRef(src), dest);
|
||||
|
||||
Assert.AreEqual(dest, result.AssetPath);
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(src), "Source should no longer exist");
|
||||
Assert.AreEqual(srcGuid, AssetDatabase.AssetPathToGUID(dest), "GUID should be preserved across a move");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyAsset_CreatesDistinctAsset()
|
||||
{
|
||||
var src = Root + "/Copy/Src.asset";
|
||||
AssetCommands.CreateAsset(src, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
var dest = Root + "/Copy/Copy.asset";
|
||||
var result = AssetCommands.CopyAsset(PathRef(src), dest);
|
||||
|
||||
Assert.AreEqual(dest, result.AssetPath);
|
||||
Assert.IsNotNull(AssetDatabase.LoadMainAssetAtPath(src), "Source should still exist after copy");
|
||||
Assert.AreNotEqual(AssetDatabase.AssetPathToGUID(src), AssetDatabase.AssetPathToGUID(dest),
|
||||
"A copy should get a fresh GUID");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RenameAsset_RenamesInPlace()
|
||||
{
|
||||
var src = Root + "/Rename/Old.asset";
|
||||
AssetCommands.CreateAsset(src, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
var result = AssetCommands.RenameAsset(PathRef(src), "New");
|
||||
|
||||
Assert.AreEqual(Root + "/Rename/New.asset", result.AssetPath);
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(src));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteAsset_WithoutConfirm_Throws()
|
||||
{
|
||||
var path = Root + "/Delete/Doomed.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
Assert.Throws<System.ArgumentException>(() => AssetCommands.DeleteAsset(PathRef(path)),
|
||||
"delete_asset without confirm should be rejected");
|
||||
Assert.IsNotNull(AssetDatabase.LoadMainAssetAtPath(path), "Asset should survive a refused delete");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteAsset_WithConfirm_Deletes()
|
||||
{
|
||||
var path = Root + "/Delete/Doomed2.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
var result = AssetCommands.DeleteAsset(PathRef(path), confirm: true);
|
||||
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(path), "Asset should be gone after a confirmed delete");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcceptanceFlow_CreateFindMoveDelete()
|
||||
{
|
||||
// create folder tree
|
||||
FolderCommands.CreateFolder(Root + "/Flow/Configs");
|
||||
Assert.IsTrue(AssetDatabase.IsValidFolder(Root + "/Flow/Configs"));
|
||||
|
||||
// create ScriptableObject asset
|
||||
var path = Root + "/Flow/Configs/Cfg.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
// find by type
|
||||
var found = AssetCommands.FindAssets(type: nameof(CLI191SampleAsset), searchIn: Root + "/Flow");
|
||||
Assert.GreaterOrEqual(found.Count, 1);
|
||||
|
||||
// move it
|
||||
var moved = AssetCommands.MoveAsset(PathRef(path), Root + "/Flow/Moved.asset");
|
||||
Assert.AreEqual(Root + "/Flow/Moved.asset", moved.AssetPath);
|
||||
|
||||
// delete it (with confirm)
|
||||
AssetCommands.DeleteAsset(PathRef(moved.AssetPath), confirm: true);
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(moved.AssetPath));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViaClient
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_ViaClient_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var path = Root + "/ViaClient/Made.asset";
|
||||
var response = server.Execute("create_asset", new { path, type = typeof(CLI191SampleAsset).FullName });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"create_asset should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
Assert.IsNotNull(AssetDatabase.LoadAssetAtPath<CLI191SampleAsset>(path), "Asset should exist");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteAsset_ViaClient_WithoutConfirm_Fails()
|
||||
{
|
||||
var path = Root + "/ViaClient/ToDelete.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
// The server logs the refusal as a Unity [Error]; expect it so the test does not fail on
|
||||
// the unexpected log. Matches the message thrown by DeleteAsset ("Refusing to delete ...").
|
||||
LogAssert.Expect(LogType.Error, new Regex("Refusing to delete"));
|
||||
|
||||
var response = server.Execute("delete_asset", new { asset = new { path } });
|
||||
|
||||
Assert.IsFalse(response.IsSuccess, "delete_asset without confirm should fail over the wire");
|
||||
Assert.IsNotNull(AssetDatabase.LoadMainAssetAtPath(path), "Asset should survive a refused delete");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindAssets_ViaClient_ReturnsResults()
|
||||
{
|
||||
var path = Root + "/ViaClient/Findable.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("find_assets", new { type = nameof(CLI191SampleAsset), search_in = Root });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"find_assets should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4736a7747a847edb2355c95559ce0b0
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for set_import_settings (CLI-191). Uses the importer's <c>userData</c> property — present
|
||||
/// on every AssetImporter — so the test is independent of any specific importer kind. Verifies the
|
||||
/// applied/unknown bookkeeping and that the setting actually persists on the importer.
|
||||
/// </summary>
|
||||
public class AssetImportCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI191ImportTest";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectRef PathRef(string path) => new ObjectRef { Path = path };
|
||||
|
||||
private static string CreateSampleAsset()
|
||||
{
|
||||
var path = Root + "/Imported.asset";
|
||||
AssetCommands.CreateAsset(path, typeof(CLI191SampleAsset).FullName);
|
||||
return path;
|
||||
}
|
||||
|
||||
#region Direct
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_AppliesKnownMember()
|
||||
{
|
||||
var path = CreateSampleAsset();
|
||||
var settings = new JObject { ["userData"] = "cli191" };
|
||||
|
||||
var result = AssetImportCommands.SetImportSettings(PathRef(path), settings);
|
||||
|
||||
CollectionAssert.Contains(result.Applied, "userData");
|
||||
CollectionAssert.IsEmpty(result.Unknown);
|
||||
Assert.AreEqual("cli191", AssetImporter.GetAtPath(path).userData, "Setting should persist on the importer");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_UnknownOnly_Throws()
|
||||
{
|
||||
var path = CreateSampleAsset();
|
||||
var settings = new JObject { ["definitelyNotAMember"] = 42 };
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => AssetImportCommands.SetImportSettings(PathRef(path), settings),
|
||||
"When no supplied setting exists on the importer, the command should fail");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_DryRun_DoesNotPersist()
|
||||
{
|
||||
var path = CreateSampleAsset();
|
||||
var settings = new JObject { ["userData"] = "dryrun" };
|
||||
|
||||
var result = AssetImportCommands.SetImportSettings(PathRef(path), settings, dryRun: true);
|
||||
|
||||
// dry_run reports the member as applicable but never mutates the importer.
|
||||
CollectionAssert.Contains(result.Applied, "userData");
|
||||
Assert.AreNotEqual("dryrun", AssetImporter.GetAtPath(path).userData, "dry_run should not persist the change");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViaClient
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_ViaClient_Succeeds()
|
||||
{
|
||||
var path = CreateSampleAsset();
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("set_import_settings", new
|
||||
{
|
||||
asset = new { path },
|
||||
settings = new { userData = "viaclient" }
|
||||
});
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"set_import_settings should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.AreEqual("viaclient", AssetImporter.GetAtPath(path).userData);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 121fa6671cf449ce863c389aa9b7e18c
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the CLI-212 per-type import settings + platform overrides:
|
||||
/// <c>get_import_settings</c> and the platform-aware extension of <c>set_import_settings</c>.
|
||||
///
|
||||
/// Real importers are exercised by writing tiny generated assets, so the actual Unity read/write
|
||||
/// code paths (including per-platform overrides) are covered end to end: a 4x4 PNG (TextureImporter),
|
||||
/// a minimal OBJ (ModelImporter), and a tiny 8-bit PCM WAV (AudioImporter). The audio fixture drives
|
||||
/// a full per-platform override round-trip (<c>ApplyAudioPlatform</c> +
|
||||
/// <c>ReadAudioPlatformOverride</c>): set override, read it back, then clear it.
|
||||
/// </summary>
|
||||
public class ImportSettingsCommandTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI212ImportTest";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectRef PathRef(string path) => new ObjectRef { Path = path };
|
||||
|
||||
// ------------------------------------------------------------------ asset fixtures
|
||||
|
||||
/// <summary>Write a tiny 4x4 PNG so Unity imports it with a TextureImporter.</summary>
|
||||
private static string CreateTexture(string name = "Tex.png")
|
||||
{
|
||||
EnsureRoot();
|
||||
var tex = new Texture2D(4, 4, TextureFormat.RGBA32, false);
|
||||
var pixels = new Color32[16];
|
||||
for (var i = 0; i < pixels.Length; i++)
|
||||
pixels[i] = new Color32(255, 128, 64, 255);
|
||||
tex.SetPixels32(pixels);
|
||||
tex.Apply();
|
||||
var bytes = tex.EncodeToPNG();
|
||||
Object.DestroyImmediate(tex);
|
||||
|
||||
var projectPath = Root + "/" + name;
|
||||
File.WriteAllBytes(ToAbsolute(projectPath), bytes);
|
||||
AssetDatabase.ImportAsset(projectPath, ImportAssetOptions.ForceSynchronousImport);
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
/// <summary>Write a minimal single-triangle OBJ so Unity imports it with a ModelImporter.</summary>
|
||||
private static string CreateModel(string name = "Tri.obj")
|
||||
{
|
||||
EnsureRoot();
|
||||
const string obj =
|
||||
"o Tri\n" +
|
||||
"v 0 0 0\n" +
|
||||
"v 1 0 0\n" +
|
||||
"v 0 1 0\n" +
|
||||
"vn 0 0 1\n" +
|
||||
"f 1//1 2//1 3//1\n";
|
||||
var projectPath = Root + "/" + name;
|
||||
File.WriteAllText(ToAbsolute(projectPath), obj);
|
||||
AssetDatabase.ImportAsset(projectPath, ImportAssetOptions.ForceSynchronousImport);
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a tiny mono 8-bit PCM WAV (a handful of samples) so Unity imports it with an
|
||||
/// AudioImporter — enough to exercise the real per-platform override read/write paths.
|
||||
/// </summary>
|
||||
private static string CreateAudio(string name = "Beep.wav")
|
||||
{
|
||||
EnsureRoot();
|
||||
|
||||
const int sampleRate = 8000;
|
||||
const int sampleCount = 64;
|
||||
var pcm = new byte[sampleCount];
|
||||
for (var i = 0; i < pcm.Length; i++)
|
||||
pcm[i] = (byte)(128 + (i % 2 == 0 ? 32 : -32)); // crude square wave around the 8-bit midpoint
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
using (var w = new BinaryWriter(stream))
|
||||
{
|
||||
const short channels = 1;
|
||||
const short bitsPerSample = 8;
|
||||
var byteRate = sampleRate * channels * bitsPerSample / 8;
|
||||
var blockAlign = (short)(channels * bitsPerSample / 8);
|
||||
|
||||
// RIFF header
|
||||
w.Write(new[] { 'R', 'I', 'F', 'F' });
|
||||
w.Write(36 + pcm.Length);
|
||||
w.Write(new[] { 'W', 'A', 'V', 'E' });
|
||||
// fmt chunk
|
||||
w.Write(new[] { 'f', 'm', 't', ' ' });
|
||||
w.Write(16); // PCM fmt chunk size
|
||||
w.Write((short)1); // audio format = PCM
|
||||
w.Write(channels);
|
||||
w.Write(sampleRate);
|
||||
w.Write(byteRate);
|
||||
w.Write(blockAlign);
|
||||
w.Write(bitsPerSample);
|
||||
// data chunk
|
||||
w.Write(new[] { 'd', 'a', 't', 'a' });
|
||||
w.Write(pcm.Length);
|
||||
w.Write(pcm);
|
||||
w.Flush();
|
||||
|
||||
var projectPath = Root + "/" + name;
|
||||
File.WriteAllBytes(ToAbsolute(projectPath), stream.ToArray());
|
||||
AssetDatabase.ImportAsset(projectPath, ImportAssetOptions.ForceSynchronousImport);
|
||||
return projectPath;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureRoot()
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder(Root))
|
||||
AssetDatabase.CreateFolder("Assets", "__CLI212ImportTest");
|
||||
}
|
||||
|
||||
private static string ToAbsolute(string projectRelative) =>
|
||||
$"{ProjectPaths.ProjectRoot.Replace('\\', '/')}/{projectRelative}";
|
||||
|
||||
// ------------------------------------------------------------------ texture: read
|
||||
|
||||
[Test]
|
||||
public void GetImportSettings_Texture_ReturnsStructuredDefaultsAndOverrideBlock()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
var result = AssetImportCommands.GetImportSettings(PathRef(path), platform: "Android");
|
||||
|
||||
Assert.AreEqual("TextureImporter", result.ImporterType);
|
||||
Assert.AreEqual("Android", result.Platform);
|
||||
Assert.IsNotNull(result.Settings);
|
||||
Assert.IsTrue(result.Settings.ContainsKey("textureType"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("isReadable"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("mipmapEnabled"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("wrapMode"));
|
||||
Assert.IsNotNull(result.PlatformOverride, "Texture should expose a platform override block");
|
||||
Assert.IsTrue(result.PlatformOverride.ContainsKey("overridden"));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ texture: default round-trip
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_TextureDefault_AppliesAndReadBackReflects()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
var settings = new JObject
|
||||
{
|
||||
["isReadable"] = true,
|
||||
["textureType"] = "NormalMap"
|
||||
};
|
||||
var set = AssetImportCommands.SetImportSettings(PathRef(path), settings);
|
||||
|
||||
CollectionAssert.Contains(set.Applied, "isReadable");
|
||||
CollectionAssert.Contains(set.Applied, "textureType");
|
||||
CollectionAssert.IsEmpty(set.Unknown);
|
||||
|
||||
var read = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
Assert.AreEqual(true, read.Settings["isReadable"]);
|
||||
Assert.AreEqual("NormalMap", read.Settings["textureType"]);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ texture: platform override set + clear
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_TextureAndroidOverride_SetsThenClears()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
// Default platform maxTextureSize before the override (should be unchanged afterwards).
|
||||
var defaultBefore = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
var defaultMaxBefore = defaultBefore.Settings["maxTextureSize"];
|
||||
|
||||
var set = AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["maxTextureSize"] = 1024, ["format"] = "ASTC_6x6", ["overridden"] = true },
|
||||
platform: "Android");
|
||||
CollectionAssert.Contains(set.Applied, "maxTextureSize");
|
||||
CollectionAssert.Contains(set.Applied, "format");
|
||||
|
||||
var afterSet = AssetImportCommands.GetImportSettings(PathRef(path), platform: "Android");
|
||||
Assert.AreEqual(true, afterSet.PlatformOverride["overridden"]);
|
||||
Assert.AreEqual(1024, afterSet.PlatformOverride["maxTextureSize"]);
|
||||
Assert.AreEqual("ASTC_6x6", afterSet.PlatformOverride["format"]);
|
||||
|
||||
// Default platform unchanged by the Android override.
|
||||
var defaultAfter = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
Assert.AreEqual(defaultMaxBefore, defaultAfter.Settings["maxTextureSize"],
|
||||
"Default platform settings must be unchanged by a platform override");
|
||||
|
||||
// Clear the override.
|
||||
AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["overridden"] = false },
|
||||
platform: "Android");
|
||||
|
||||
var afterClear = AssetImportCommands.GetImportSettings(PathRef(path), platform: "Android");
|
||||
Assert.AreEqual(false, afterClear.PlatformOverride["overridden"], "Override should be cleared");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ audio: read
|
||||
|
||||
[Test]
|
||||
public void GetImportSettings_Audio_ReturnsStructuredDefaultsAndOverrideBlock()
|
||||
{
|
||||
var path = CreateAudio();
|
||||
|
||||
var result = AssetImportCommands.GetImportSettings(PathRef(path), platform: "Android");
|
||||
|
||||
Assert.AreEqual("AudioImporter", result.ImporterType);
|
||||
Assert.AreEqual("Android", result.Platform);
|
||||
Assert.IsNotNull(result.Settings);
|
||||
Assert.IsTrue(result.Settings.ContainsKey("loadType"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("compressionFormat"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("quality"));
|
||||
Assert.IsNotNull(result.PlatformOverride, "Audio should expose a platform override block");
|
||||
Assert.IsTrue(result.PlatformOverride.ContainsKey("overridden"));
|
||||
// No override has been written yet.
|
||||
Assert.AreEqual(false, result.PlatformOverride["overridden"]);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ audio: platform override set + clear
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_AudioAndroidOverride_SetsThenClears()
|
||||
{
|
||||
var path = CreateAudio();
|
||||
|
||||
// Default-platform compressionFormat before the override (must be unchanged afterwards).
|
||||
var defaultBefore = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
var defaultCompressionBefore = defaultBefore.Settings["compressionFormat"];
|
||||
|
||||
var set = AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["compressionFormat"] = "Vorbis", ["loadType"] = "CompressedInMemory", ["overridden"] = true },
|
||||
platform: "Android");
|
||||
CollectionAssert.Contains(set.Applied, "compressionFormat");
|
||||
CollectionAssert.Contains(set.Applied, "loadType");
|
||||
CollectionAssert.Contains(set.Applied, "overridden");
|
||||
CollectionAssert.IsEmpty(set.Unknown);
|
||||
|
||||
var afterSet = AssetImportCommands.GetImportSettings(PathRef(path), platform: "Android");
|
||||
Assert.AreEqual(true, afterSet.PlatformOverride["overridden"], "Override should be present after a platform write");
|
||||
Assert.AreEqual("Vorbis", afterSet.PlatformOverride["compressionFormat"]);
|
||||
Assert.AreEqual("CompressedInMemory", afterSet.PlatformOverride["loadType"]);
|
||||
|
||||
// Default platform unchanged by the Android override.
|
||||
var defaultAfter = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
Assert.AreEqual(defaultCompressionBefore, defaultAfter.Settings["compressionFormat"],
|
||||
"Default platform settings must be unchanged by a platform override");
|
||||
|
||||
// Clear the override.
|
||||
AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["overridden"] = false },
|
||||
platform: "Android");
|
||||
|
||||
var afterClear = AssetImportCommands.GetImportSettings(PathRef(path), platform: "Android");
|
||||
Assert.AreEqual(false, afterClear.PlatformOverride["overridden"], "Override should be cleared");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ model: read + default write
|
||||
|
||||
[Test]
|
||||
public void GetImportSettings_Model_ReturnsNullPlatformOverride()
|
||||
{
|
||||
var path = CreateModel();
|
||||
|
||||
var result = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
|
||||
Assert.AreEqual("ModelImporter", result.ImporterType);
|
||||
Assert.IsTrue(result.Settings.ContainsKey("meshCompression"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("animationType"));
|
||||
Assert.IsTrue(result.Settings.ContainsKey("materialImportMode"));
|
||||
Assert.IsNull(result.PlatformOverride, "ModelImporter has no per-platform overrides");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_ModelDefault_AppliesAndReadBack()
|
||||
{
|
||||
var path = CreateModel();
|
||||
|
||||
var set = AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["meshCompression"] = "High", ["importNormals"] = "Calculate" });
|
||||
|
||||
CollectionAssert.Contains(set.Applied, "meshCompression");
|
||||
CollectionAssert.Contains(set.Applied, "importNormals");
|
||||
|
||||
var read = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
Assert.AreEqual("High", read.Settings["meshCompression"]);
|
||||
Assert.AreEqual("Calculate", read.Settings["importNormals"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_ModelWithPlatform_ReturnsNoPlatformOverrides_AndWritesNothing()
|
||||
{
|
||||
var path = CreateModel();
|
||||
var before = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
var meshBefore = before.Settings["meshCompression"];
|
||||
|
||||
var ex = Assert.Throws<System.ArgumentException>(() =>
|
||||
AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["meshCompression"] = "High" },
|
||||
platform: "iOS"));
|
||||
StringAssert.Contains("no_platform_overrides", ex.Message);
|
||||
|
||||
var after = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
Assert.AreEqual(meshBefore, after.Settings["meshCompression"], "A rejected platform write must change nothing");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ dry_run
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_DryRun_ReportsButDoesNotChange()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
var before = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
var readableBefore = (bool)before.Settings["isReadable"];
|
||||
|
||||
var result = AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["isReadable"] = !readableBefore },
|
||||
dryRun: true);
|
||||
|
||||
CollectionAssert.Contains(result.Applied, "isReadable");
|
||||
|
||||
var after = AssetImportCommands.GetImportSettings(PathRef(path));
|
||||
Assert.AreEqual(readableBefore, (bool)after.Settings["isReadable"], "dry_run must not change settings");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ unknown key handling
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_UnknownKey_GoesToUnknown()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
var result = AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["isReadable"] = true, ["definitelyNotAMember"] = 42 });
|
||||
|
||||
CollectionAssert.Contains(result.Applied, "isReadable");
|
||||
CollectionAssert.Contains(result.Unknown, "definitelyNotAMember");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_AllUnknown_Throws()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
Assert.Throws<System.ArgumentException>(() =>
|
||||
AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["definitelyNotAMember"] = 42 }));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ platform validation
|
||||
|
||||
[Test]
|
||||
public void SetImportSettings_UnknownPlatform_Throws()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
var ex = Assert.Throws<System.ArgumentException>(() =>
|
||||
AssetImportCommands.SetImportSettings(
|
||||
PathRef(path),
|
||||
new JObject { ["maxTextureSize"] = 512 },
|
||||
platform: "PlayStation"));
|
||||
StringAssert.Contains("unknown_platform", ex.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetImportSettings_UnknownPlatform_Throws()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
Assert.Throws<System.ArgumentException>(() =>
|
||||
AssetImportCommands.GetImportSettings(PathRef(path), platform: "PlayStation"));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ sandbox guard
|
||||
|
||||
[Test]
|
||||
public void GetImportSettings_OutsideAuthoringRoot_Throws()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
// Confine the authoring root to a sub-folder that does NOT contain the asset, so the asset's
|
||||
// resolved path falls outside the sandbox.
|
||||
ProjectPaths.AuthoringRoot = Root + "/Nested";
|
||||
|
||||
Assert.Throws<System.ArgumentException>(() =>
|
||||
AssetImportCommands.GetImportSettings(PathRef(path)));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ via client (wire) smoke test
|
||||
|
||||
[Test]
|
||||
public void GetImportSettings_ViaClient_Succeeds()
|
||||
{
|
||||
var path = CreateTexture();
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("get_import_settings", new
|
||||
{
|
||||
asset = new { path },
|
||||
platform = "Android"
|
||||
});
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"get_import_settings should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48fae49d74ce4b61941f9c5ff541034f
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for create_asset's Material support (CLI-221), exercised directly and via the isolated
|
||||
/// <see cref="PipelineTestServer"/>. Verifies that a .mat is created with a real (non-null) shader,
|
||||
/// that an unknown shader name fails clearly (no null Material / null-ref), and that the created
|
||||
/// Material is loadable and assignable to a Renderer's material slot.
|
||||
/// </summary>
|
||||
public class MaterialAssetCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI221_222Test";
|
||||
|
||||
// A shader that is guaranteed to exist on whatever pipeline this project runs (built-in or URP).
|
||||
private string m_KnownShader;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
m_KnownShader = PickKnownShader();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pick a shader name that resolves on this project's active pipeline.</summary>
|
||||
private static string PickKnownShader()
|
||||
{
|
||||
if (Shader.Find("Standard") != null)
|
||||
return "Standard";
|
||||
if (Shader.Find("Universal Render Pipeline/Lit") != null)
|
||||
return "Universal Render Pipeline/Lit";
|
||||
// Sprites/Default ships with every pipeline as a hard fallback.
|
||||
return "Sprites/Default";
|
||||
}
|
||||
|
||||
#region Direct
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_WithShader_CreatesValidMaterial()
|
||||
{
|
||||
var path = Root + "/Mats/Bar.mat";
|
||||
var result = AssetCommands.CreateAsset(path, "Material", shader: m_KnownShader);
|
||||
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
|
||||
var loaded = AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
Assert.IsNotNull(loaded, "Created .mat should load as a Material");
|
||||
Assert.IsNotNull(loaded.shader, "Material's shader must not be null");
|
||||
Assert.AreEqual(Shader.Find(m_KnownShader), loaded.shader, "Material should use the requested shader");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_DefaultShader_IsNonNull()
|
||||
{
|
||||
// No explicit shader: should fall back to the active pipeline's lit shader (or Standard).
|
||||
var path = Root + "/Mats/Default.mat";
|
||||
AssetCommands.CreateAsset(path, "Material");
|
||||
|
||||
var loaded = AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
Assert.IsNotNull(loaded, "Created .mat should load as a Material");
|
||||
Assert.IsNotNull(loaded.shader, "Default-shader Material must have a non-null shader");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_InvalidShader_ThrowsClearError()
|
||||
{
|
||||
var path = Root + "/Mats/Broken.mat";
|
||||
|
||||
var ex = Assert.Throws<System.ArgumentException>(
|
||||
() => AssetCommands.CreateAsset(path, "Material", shader: "No/Such/Shader__CLI221"),
|
||||
"An unknown shader name should fail with a clear ArgumentException");
|
||||
StringAssert.Contains("not found", ex.Message);
|
||||
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(path), "No asset should be written on a shader failure");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_DryRun_InvalidShader_ThrowsAndWritesNothing()
|
||||
{
|
||||
// dry_run is documented as "validate inputs", so an unknown shader must fail fast even in
|
||||
// dry_run (rather than reporting a would-be success) and still write nothing.
|
||||
var path = Root + "/Mats/DryRunBroken.mat";
|
||||
|
||||
var ex = Assert.Throws<System.ArgumentException>(
|
||||
() => AssetCommands.CreateAsset(path, "Material", shader: "No/Such/Shader__CLI221", dryRun: true),
|
||||
"dry_run should resolve/validate the shader and fail on an unknown one");
|
||||
StringAssert.Contains("not found", ex.Message);
|
||||
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(path), "dry_run must never write an asset");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_DryRun_ValidShader_WritesNothing()
|
||||
{
|
||||
// A valid dry_run resolves the shader (no throw) but writes no asset.
|
||||
var path = Root + "/Mats/DryRunOk.mat";
|
||||
var result = AssetCommands.CreateAsset(path, "Material", shader: m_KnownShader, dryRun: true);
|
||||
|
||||
Assert.AreEqual(path, result.AssetPath);
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(path), "dry_run must never write an asset");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_AssignableToRendererSlot()
|
||||
{
|
||||
var path = Root + "/Mats/Assignable.mat";
|
||||
AssetCommands.CreateAsset(path, "Material", shader: m_KnownShader);
|
||||
var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
|
||||
var go = new GameObject("__CLI221_Renderer");
|
||||
try
|
||||
{
|
||||
var renderer = go.AddComponent<MeshRenderer>();
|
||||
renderer.sharedMaterial = mat;
|
||||
|
||||
Assert.AreEqual(mat, renderer.sharedMaterial, "Created Material should be assignable to a Renderer slot");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(go);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViaClient
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_ViaClient_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var path = Root + "/ViaClient/Wall.mat";
|
||||
var response = server.Execute("create_asset", new { path, type = "Material", shader = m_KnownShader });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"create_asset (Material) should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
|
||||
var loaded = AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
Assert.IsNotNull(loaded, "Material should exist on disk");
|
||||
Assert.IsNotNull(loaded.shader, "Material's shader must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_Material_ViaClient_InvalidShader_Fails()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
// The server logs the failure as a Unity [Error]; expect it so the test does not fail on
|
||||
// the unexpected log. Matches the message thrown by ResolveShader ("Shader '...' not found.").
|
||||
LogAssert.Expect(LogType.Error, new Regex("not found"));
|
||||
|
||||
var path = Root + "/ViaClient/Broken.mat";
|
||||
var response = server.Execute("create_asset", new { path, type = "Material", shader = "No/Such/Shader__CLI221" });
|
||||
|
||||
Assert.IsFalse(response.IsSuccess, "An unknown shader should fail over the wire");
|
||||
Assert.IsNull(AssetDatabase.LoadMainAssetAtPath(path), "No asset should be written on a shader failure");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d514449899cc0f439d287ab64433a22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
using PipelinePhysicsMaterial = UnityEngine.PhysicsMaterial;
|
||||
#else
|
||||
using PipelinePhysicsMaterial = UnityEngine.PhysicMaterial;
|
||||
#endif
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for create_asset's PhysicsMaterial support (CLI-222), exercised directly and via the
|
||||
/// isolated <see cref="PipelineTestServer"/>. Verifies that BOTH the Unity 6 type name
|
||||
/// "PhysicsMaterial" and the legacy "PhysicMaterial" resolve to <see cref="PhysicsMaterial"/>, that
|
||||
/// the created asset carries the documented defaults (bounciness 0, frictions 0.6), is discoverable
|
||||
/// via find_assets, and is assignable to a Collider's material slot.
|
||||
///
|
||||
/// NOTE: Unity 6 renamed the class to PhysicsMaterial but the on-disk asset extension is still
|
||||
/// ".physicMaterial" (AssetDatabase.CreateAsset rejects ".physicsMaterial"). create_asset normalizes
|
||||
/// a ".physicsMaterial" request to ".physicMaterial", so the created path is asserted via the
|
||||
/// command result rather than the requested path.
|
||||
/// </summary>
|
||||
public class PhysicsMaterialAssetCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI221_222Test";
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertDefaults(PipelinePhysicsMaterial pm)
|
||||
{
|
||||
Assert.IsNotNull(pm, "Created physics material should load as a PhysicsMaterial");
|
||||
Assert.AreEqual(0f, pm.bounciness, 1e-4f, "bounciness default should be 0");
|
||||
Assert.AreEqual(0.6f, pm.dynamicFriction, 1e-4f, "dynamicFriction default should be 0.6");
|
||||
Assert.AreEqual(0.6f, pm.staticFriction, 1e-4f, "staticFriction default should be 0.6");
|
||||
}
|
||||
|
||||
#region Direct
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_CreatesWithDefaults()
|
||||
{
|
||||
var result = AssetCommands.CreateAsset(Root + "/Phys/Ball.physicMaterial", "PhysicsMaterial");
|
||||
|
||||
StringAssert.EndsWith(".physicMaterial", result.AssetPath);
|
||||
AssertDefaults(AssetDatabase.LoadAssetAtPath<PipelinePhysicsMaterial>(result.AssetPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_NormalizesModernExtension()
|
||||
{
|
||||
// CLI-222: an agent may pass the modern ".physicsMaterial" extension; it must succeed,
|
||||
// normalized to the ".physicMaterial" file Unity actually writes.
|
||||
var result = AssetCommands.CreateAsset(Root + "/Phys/Modern.physicsMaterial", "PhysicsMaterial");
|
||||
|
||||
StringAssert.EndsWith(".physicMaterial", result.AssetPath);
|
||||
AssertDefaults(AssetDatabase.LoadAssetAtPath<PipelinePhysicsMaterial>(result.AssetPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_LegacyTypeName_ResolvesToSameType()
|
||||
{
|
||||
// CLI-222: the legacy "PhysicMaterial" spelling must resolve to UnityEngine.PhysicsMaterial.
|
||||
var result = AssetCommands.CreateAsset(Root + "/Phys/Legacy.physicMaterial", "PhysicMaterial");
|
||||
|
||||
var loaded = AssetDatabase.LoadMainAssetAtPath(result.AssetPath);
|
||||
Assert.IsInstanceOf<PipelinePhysicsMaterial>(loaded, "Legacy 'PhysicMaterial' type name should resolve to PhysicsMaterial");
|
||||
AssertDefaults(loaded as PipelinePhysicsMaterial);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_FoundByFindAssets()
|
||||
{
|
||||
var result = AssetCommands.CreateAsset(Root + "/Phys/Findable.physicMaterial", "PhysicsMaterial");
|
||||
|
||||
var found = AssetCommands.FindAssets(type: "PhysicsMaterial", searchIn: Root);
|
||||
|
||||
Assert.GreaterOrEqual(found.Count, 1, "Should find the created PhysicsMaterial by type");
|
||||
CollectionAssert.Contains(
|
||||
System.Linq.Enumerable.Select(found.Assets, a => a.AssetPath),
|
||||
result.AssetPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_AssignableToColliderSlot()
|
||||
{
|
||||
var result = AssetCommands.CreateAsset(Root + "/Phys/Assignable.physicMaterial", "PhysicsMaterial");
|
||||
var pm = AssetDatabase.LoadAssetAtPath<PipelinePhysicsMaterial>(result.AssetPath);
|
||||
|
||||
var go = new GameObject("__CLI222_Collider");
|
||||
try
|
||||
{
|
||||
var collider = go.AddComponent<BoxCollider>();
|
||||
collider.sharedMaterial = pm;
|
||||
|
||||
Assert.AreEqual(pm, collider.sharedMaterial, "Created PhysicsMaterial should be assignable to a Collider slot");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(go);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViaClient
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_ViaClient_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("create_asset",
|
||||
new { path = Root + "/ViaClient/Ball.physicMaterial", type = "PhysicsMaterial" });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"create_asset (PhysicsMaterial) should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
|
||||
var created = (string)response.JsonResponse["result"]["assetPath"];
|
||||
AssertDefaults(AssetDatabase.LoadAssetAtPath<PipelinePhysicsMaterial>(created));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAsset_PhysicsMaterial_ViaClient_LegacyName_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("create_asset",
|
||||
new { path = Root + "/ViaClient/Legacy.physicMaterial", type = "PhysicMaterial" });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"create_asset (legacy PhysicMaterial) should succeed: {response.Error}");
|
||||
|
||||
var created = (string)response.JsonResponse["result"]["assetPath"];
|
||||
Assert.IsInstanceOf<PipelinePhysicsMaterial>(AssetDatabase.LoadMainAssetAtPath(created),
|
||||
"Legacy 'PhysicMaterial' should resolve to PhysicsMaterial over the wire");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af05410e747b85c47ab38e0bf2db7fd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for read_text_file / write_text_file (CLI-191), direct and via the isolated
|
||||
/// <see cref="PipelineTestServer"/>. Covers round-tripping content, the overwrite-confirm guard, and
|
||||
/// the sandbox guard (out-of-project writes rejected).
|
||||
/// </summary>
|
||||
public class TextFileCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI191TextTest";
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
#region Direct
|
||||
|
||||
[Test]
|
||||
public void WriteThenRead_RoundTrips()
|
||||
{
|
||||
var path = Root + "/Notes/hello.txt";
|
||||
const string content = "hello cli-191";
|
||||
|
||||
var write = TextFileCommands.WriteTextFile(path, content);
|
||||
Assert.AreEqual(path, write.AssetPath);
|
||||
|
||||
var read = TextFileCommands.ReadTextFile(path);
|
||||
Assert.AreEqual(content, read.Contents);
|
||||
Assert.AreEqual(System.Text.Encoding.UTF8.GetByteCount(content), read.Bytes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteTextFile_OverwriteWithoutConfirm_Throws()
|
||||
{
|
||||
var path = Root + "/Notes/dup.txt";
|
||||
TextFileCommands.WriteTextFile(path, "first");
|
||||
|
||||
Assert.Throws<System.ArgumentException>(
|
||||
() => TextFileCommands.WriteTextFile(path, "second"),
|
||||
"Overwriting an existing file without confirm should be rejected");
|
||||
|
||||
var ok = TextFileCommands.WriteTextFile(path, "second", confirm: true);
|
||||
Assert.AreEqual(path, ok.AssetPath);
|
||||
Assert.AreEqual("second", TextFileCommands.ReadTextFile(path).Contents);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteTextFile_OutOfProject_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(() => TextFileCommands.WriteTextFile("../escape.txt", "x"));
|
||||
Assert.Throws<System.ArgumentException>(() => TextFileCommands.WriteTextFile("Assets/../escape.txt", "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadTextFile_Missing_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(() => TextFileCommands.ReadTextFile(Root + "/does-not-exist.txt"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViaClient
|
||||
|
||||
[Test]
|
||||
public void WriteTextFile_ViaClient_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var path = Root + "/ViaClient/wire.txt";
|
||||
var response = server.Execute("write_text_file", new { path, contents = "over the wire" });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"write_text_file should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.AreEqual("over the wire", TextFileCommands.ReadTextFile(path).Contents);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadTextFile_ViaClient_Succeeds()
|
||||
{
|
||||
var path = Root + "/ViaClient/readable.txt";
|
||||
TextFileCommands.WriteTextFile(path, "readable content");
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("read_text_file", new { path });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"read_text_file should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ede31badcf640f6acc3c5baf0fc8ef3
|
||||
Reference in New Issue
Block a user