A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

257 lines
13KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using DataLoader;
  6. using Svelto.Tasks;
  7. using Unity.Mathematics;
  8. using TechbloxModdingAPI.Tests;
  9. using TechbloxModdingAPI.Utility;
  10. namespace TechbloxModdingAPI.Blocks
  11. {
  12. #if TEST
  13. /// <summary>
  14. /// Block test cases. Not accessible in release versions.
  15. /// </summary>
  16. [APITestClass]
  17. public static class BlockTests
  18. {
  19. [APITestCase(TestType.Game)] //At least one block must be placed for simulation to work
  20. public static void TestPlaceNew()
  21. {
  22. Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero);
  23. 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.");
  24. }
  25. [APITestCase(TestType.EditMode)]
  26. public static void TestInitProperty()
  27. {
  28. Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero + 2);
  29. 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;
  30. //Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful.");
  31. }
  32. [APITestCase(TestType.EditMode)]
  33. public static void TestBlockIDCoverage()
  34. {
  35. Assert.Equal(
  36. FullGameFields._dataDb.GetValues<CubeListData>().Keys.Select(ushort.Parse).OrderBy(id => id)
  37. .SequenceEqual(Enum.GetValues(typeof(BlockIDs)).Cast<ushort>().OrderBy(id => id)
  38. .Except(new[] {(ushort) BlockIDs.Invalid})), true,
  39. "Block ID enum is different than the known block types, update needed.",
  40. "Block ID enum matches the known block types.");
  41. }
  42. private static Block[] blocks; // Store placed blocks as some blocks are already present as the workshop and the game save
  43. [APITestCase(TestType.EditMode)]
  44. public static void TestBlockIDs()
  45. {
  46. float3 pos = new float3();
  47. var values = Enum.GetValues(typeof(BlockIDs));
  48. blocks = new Block[values.Length - 1]; // Minus the invalid ID
  49. int i = 0;
  50. foreach (BlockIDs id in values)
  51. {
  52. if (id == BlockIDs.Invalid) continue;
  53. try
  54. {
  55. blocks[i++] = Block.PlaceNew(id, pos);
  56. pos += 0.2f;
  57. }
  58. catch (Exception e)
  59. { //Only print failed case
  60. Assert.Fail($"Failed to place block type {id}: {e}");
  61. return;
  62. }
  63. }
  64. Assert.Pass("Placing all possible block types succeeded.");
  65. }
  66. [APITestCase(TestType.EditMode)]
  67. public static IEnumerator<TaskContract> TestBlockProperties()
  68. { //Uses the result of the previous test case
  69. yield return Yield.It;
  70. if (blocks is null)
  71. yield break;
  72. for (var index = 0; index < blocks.Length; index++)
  73. {
  74. var block = blocks[index];
  75. if (!block.Exists) continue;
  76. foreach (var property in block.GetType().GetProperties())
  77. {
  78. //Includes specialised block properties
  79. if (property.SetMethod == null) continue;
  80. bool3 Float3Compare(float3 a, float3 b)
  81. { // From Unity reference code
  82. return math.abs(b - a) < math.max(
  83. 0.000001f * math.max(math.abs(a), math.abs(b)),
  84. float.Epsilon * 8
  85. );
  86. }
  87. bool4 Float4Compare(float4 a, float4 b)
  88. { // From Unity reference code
  89. return math.abs(b - a) < math.max(
  90. 0.000001f * math.max(math.abs(a), math.abs(b)),
  91. float.Epsilon * 8
  92. );
  93. }
  94. var testValues = new (Type, object, Predicate<(object Value, object Default)>)[]
  95. {
  96. //(type, default value, predicate or null for equality)
  97. (typeof(long), 3, null),
  98. (typeof(int), 4, null),
  99. (typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon),
  100. (typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon),
  101. (typeof(bool), true, t => (bool) t.Value),
  102. (typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check
  103. (typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))),
  104. (typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null),
  105. (typeof(float4), (float4) 5, t => math.all(Float4Compare((float4)t.Value, (float4)t.Default)))
  106. };
  107. var propType = property.PropertyType;
  108. if (!propType.IsValueType) continue;
  109. (object valueToUse, Predicate<(object Value, object Default)> predicateToUse) = (null, null);
  110. foreach (var (type, value, predicate) in testValues)
  111. {
  112. if (type.IsAssignableFrom(propType))
  113. {
  114. valueToUse = value;
  115. predicateToUse = predicate ?? (t => Equals(t.Value, t.Default));
  116. break;
  117. }
  118. }
  119. if (propType.IsEnum)
  120. {
  121. var values = propType.GetEnumValues();
  122. valueToUse = values.GetValue(values.Length / 2);
  123. predicateToUse = t => Equals(t.Value, t.Default);
  124. }
  125. if (valueToUse == null)
  126. {
  127. Assert.Fail($"Property {block.GetType().Name}.{property.Name} has an unknown type {propType}, test needs fixing.");
  128. yield break;
  129. }
  130. try
  131. {
  132. property.SetValue(block, valueToUse);
  133. }
  134. catch (Exception e)
  135. {
  136. Assert.Fail($"Failed to set property {block.GetType().Name}.{property.Name} to {valueToUse}\n{e}");
  137. }
  138. object got;
  139. try
  140. {
  141. got = property.GetValue(block);
  142. }
  143. catch (Exception e)
  144. {
  145. Assert.Fail($"Failed to get property {block.GetType().Name}.{property.Name}\n{e}");
  146. continue;
  147. }
  148. var attr = property.GetCustomAttribute<TestValueAttribute>();
  149. if (!predicateToUse((got, valueToUse)) && (attr == null || !Equals(attr.PossibleValue, got)))
  150. {
  151. Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
  152. yield break;
  153. }
  154. }
  155. }
  156. Assert.Pass("Setting all possible properties of all registered API block types succeeded.");
  157. }
  158. [APITestCase(TestType.EditMode)]
  159. public static IEnumerator<TaskContract> TestDefaultValue()
  160. {
  161. for (int i = 0; i < 2; i++)
  162. { //Tests shared defaults
  163. var block = Block.PlaceNew(BlockIDs.Cube, 1);
  164. while (!block.Exists)
  165. yield return Yield.It;
  166. block.Remove();
  167. while (block.Exists)
  168. yield return Yield.It;
  169. if(!Assert.Equal(block.Position, default,
  170. $"Block position default value {block.Position} is incorrect, should be 0.",
  171. $"Block position default value {block.Position} matches default."))
  172. yield break;
  173. block.Position = 4;
  174. }
  175. }
  176. [APITestCase(TestType.EditMode)]
  177. public static void TestDampedSpring()
  178. {
  179. Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1);
  180. DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
  181. Assert.Errorless(() => { b = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue.");
  182. 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;
  183. 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;
  184. 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;
  185. }
  186. /*[APITestCase(TestType.Game)]
  187. public static void TestMusicBlock1()
  188. {
  189. Block newBlock = Block.PlaceNew(BlockIDs.MusicBlock, Unity.Mathematics.float3.zero + 2);
  190. MusicBlock b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
  191. Assert.Errorless(() => { b = newBlock.Specialise<MusicBlock>(); }, "Block.Specialize<MusicBlock>() raised an exception: ", "Block.Specialize<MusicBlock>() completed without issue.");
  192. if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
  193. 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;
  194. 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;
  195. _musicBlock = b;
  196. }
  197. private static MusicBlock _musicBlock;
  198. [APITestCase(TestType.EditMode)]
  199. public static void TestMusicBlock2()
  200. {
  201. //Block newBlock = Block.GetLastPlacedBlock();
  202. var b = _musicBlock;
  203. if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
  204. b.IsPlaying = true; // play sfx
  205. 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;
  206. 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;
  207. //Assert.Log(b.Track.ToString());
  208. 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;
  209. }
  210. [APITestCase(TestType.EditMode)]
  211. public static void TestLogicGate()
  212. {
  213. Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1);
  214. LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
  215. Assert.Errorless(() => { b = newBlock.Specialise<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue.");
  216. if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
  217. 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;
  218. 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;
  219. if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
  220. //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;
  221. LogicGate target = null;
  222. if (!Assert.Errorless(() => { target = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return;
  223. Wire newWire = null;
  224. if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return;
  225. if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return;
  226. }*/
  227. /*[APITestCase(TestType.EditMode)]
  228. public static void TestSpecialiseError()
  229. {
  230. Block newBlock = Block.PlaceNew(BlockIDs.Bench, new float3(1, 1, 1));
  231. if (Assert.Errorful<BlockTypeException>(() => newBlock.Specialise<MusicBlock>(), "Block.Specialise<MusicBlock>() was expected to error on a bench block.", "Block.Specialise<MusicBlock>() errored as expected for a bench block.")) return;
  232. }*/
  233. }
  234. #endif
  235. }