using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using DataLoader; using Svelto.Tasks; using Unity.Mathematics; using TechbloxModdingAPI.App; using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.Blocks { #if TEST /// /// Block test cases. Not accessible in release versions. /// [APITestClass] public static class BlockTests { [APITestCase(TestType.Game)] //At least one block must be placed for simulation to work public static void TestPlaceNew() { Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero); Assert.NotNull(newBlock.Id, "Newly placed block is missing Id. This should be populated when the block is placed.", "Newly placed block Id is not null, block successfully placed."); } [APITestCase(TestType.EditMode)] public static void TestInitProperty() { Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero + 2); if (!Assert.CloseTo(newBlock.Position, (float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return; //Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful."); } [APITestCase(TestType.EditMode)] public static void TestBlockIDCoverage() { Assert.Equal( FullGameFields._dataDb.GetValues().Keys.Select(ushort.Parse).OrderBy(id => id) .SequenceEqual(Enum.GetValues(typeof(BlockIDs)).Cast().OrderBy(id => id) .Except(new[] {(ushort) BlockIDs.Invalid})), true, "Block ID enum is different than the known block types, update needed.", "Block ID enum matches the known block types."); } private static Block[] blocks; // Store placed blocks as some blocks are already present as the workshop and the game save [APITestCase(TestType.EditMode)] public static void TestBlockIDs() { float3 pos = new float3(); var values = Enum.GetValues(typeof(BlockIDs)); blocks = new Block[values.Length - 1]; // Minus the invalid ID int i = 0; foreach (BlockIDs id in values) { if (id == BlockIDs.Invalid) continue; try { blocks[i++] = Block.PlaceNew(id, pos); pos += 0.2f; } catch (Exception e) { //Only print failed case Assert.Fail($"Failed to place block type {id}: {e}"); return; } } Assert.Pass("Placing all possible block types succeeded."); } [APITestCase(TestType.EditMode)] public static IEnumerator TestBlockProperties() { //Uses the result of the previous test case yield return Yield.It; if (blocks is null) yield break; for (var index = 0; index < blocks.Length; index++) { if (index % 50 == 0) yield return Yield.It; //The material or flipped status can only be changed 130 times per submission var block = blocks[index]; if (!block.Exists) continue; foreach (var property in block.GetType().GetProperties()) { if (property.Name == "Material" || property.Name == "Flipped") continue; // TODO: Crashes in game //Includes specialised block properties if (property.SetMethod == null) continue; var testValues = new (Type, object, Predicate)[] { //(type, default value, predicate or null for equality) (typeof(long), 3, null), (typeof(int), 4, null), (typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon), (typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon), (typeof(bool), true, obj => (bool) obj), (typeof(string), "Test", obj => (string) obj == "Test"), //String equality check (typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)), (typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null), (typeof(float4), (float4) 5, obj => math.all((float4) obj - 5 < (float4) float.Epsilon)) }; var propType = property.PropertyType; if (!propType.IsValueType) continue; (object valueToUse, Predicate predicateToUse) = (null, null); foreach (var (type, value, predicate) in testValues) { if (type.IsAssignableFrom(propType)) { valueToUse = value; predicateToUse = predicate ?? (obj => Equals(obj, value)); break; } } if (propType.IsEnum) { var values = propType.GetEnumValues(); valueToUse = values.GetValue(values.Length / 2); predicateToUse = val => Equals(val, valueToUse); } if (valueToUse == null) { Assert.Fail($"Property {block.GetType().Name}.{property.Name} has an unknown type {propType}, test needs fixing."); yield break; } try { property.SetValue(block, valueToUse); } catch (Exception e) { Assert.Fail($"Failed to set property {block.GetType().Name}.{property.Name} to {valueToUse}\n{e}"); } object got; try { got = property.GetValue(block); } catch (Exception e) { Assert.Fail($"Failed to get property {block.GetType().Name}.{property.Name}\n{e}"); continue; } var attr = property.GetCustomAttribute(); if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got))) { Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}."); yield break; } } } Assert.Pass("Setting all possible properties of all registered API block types succeeded."); } [APITestCase(TestType.EditMode)] public static IEnumerator TestDefaultValue() { for (int i = 0; i < 2; i++) { //Tests shared defaults var block = Block.PlaceNew(BlockIDs.Cube, 1); while (!block.Exists) yield return Yield.It; block.Remove(); while (block.Exists) yield return Yield.It; if(!Assert.Equal(block.Position, default, $"Block position default value {block.Position} is incorrect, should be 0.", $"Block position default value {block.Position} matches default.")) yield break; block.Position = 4; } } [APITestCase(TestType.EditMode)] public static void TestDampedSpring() { Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1); DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler Assert.Errorless(() => { b = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue."); if (!Assert.CloseTo(b.Stiffness, 1f, $"DampedSpring.Stiffness {b.Stiffness} does not equal default value, possibly because it failed silently.", "DampedSpring.Stiffness is close enough to default.")) return; if (!Assert.CloseTo(b.Damping, 0.1f, $"DampedSpring.Damping {b.Damping} does not equal default value, possibly because it failed silently.", "DampedSpring.Damping is close enough to default.")) return; if (!Assert.CloseTo(b.MaxExtension, 0.3f, $"DampedSpring.MaxExtension {b.MaxExtension} does not equal default value, possibly because it failed silently.", "DampedSpring.MaxExtension is close enough to default.")) return; } /*[APITestCase(TestType.Game)] public static void TestMusicBlock1() { Block newBlock = Block.PlaceNew(BlockIDs.MusicBlock, Unity.Mathematics.float3.zero + 2); MusicBlock b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler Assert.Errorless(() => { b = newBlock.Specialise(); }, "Block.Specialize() raised an exception: ", "Block.Specialize() completed without issue."); if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; if (!Assert.CloseTo(b.Volume, 100f, $"MusicBlock.Volume {b.Volume} does not equal default value, possibly because it failed silently.", "MusicBlock.Volume is close enough to default.")) return; if (!Assert.Equal(b.TrackIndex, 0, $"MusicBlock.TrackIndex {b.TrackIndex} does not equal default value, possibly because it failed silently.", "MusicBlock.TrackIndex is equal to default.")) return; _musicBlock = b; } private static MusicBlock _musicBlock; [APITestCase(TestType.EditMode)] public static void TestMusicBlock2() { //Block newBlock = Block.GetLastPlacedBlock(); var b = _musicBlock; if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; b.IsPlaying = true; // play sfx if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return; if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return; //Assert.Log(b.Track.ToString()); if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return; } [APITestCase(TestType.EditMode)] public static void TestLogicGate() { Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1); LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler Assert.Errorless(() => { b = newBlock.Specialise(); }, "Block.Specialize() raised an exception: ", "Block.Specialize() completed without issue."); if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return; if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return; if (!Assert.NotNull(b, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; //if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return; LogicGate target = null; if (!Assert.Errorless(() => { target = Block.PlaceNew(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return; Wire newWire = null; if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return; if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return; }*/ /*[APITestCase(TestType.EditMode)] public static void TestSpecialiseError() { Block newBlock = Block.PlaceNew(BlockIDs.Bench, new float3(1, 1, 1)); if (Assert.Errorful(() => newBlock.Specialise(), "Block.Specialise() was expected to error on a bench block.", "Block.Specialise() errored as expected for a bench block.")) return; }*/ } #endif }