Created
March 2, 2025 12:49
-
-
Save AlexMerzlikin/67bce261b58062650916925a08645f39 to your computer and use it in GitHub Desktop.
Example of CustomEcsTestsFixture without the reference to Unity.Entities testables
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using NUnit.Framework; | |
using Unity.Burst; | |
using Unity.Collections; | |
using Unity.Entities; | |
using Unity.Jobs.LowLevel.Unsafe; | |
using UnityEngine.LowLevel; | |
namespace Roguelite.Tests.Infrastructure | |
{ | |
public class CustomEcsTestsFixture : ECSTestsFixture | |
{ | |
private const int EntityCount = 1000; | |
/// <summary> | |
/// A proxy property just for the sake of following my codestyle regarding naming in my tests | |
/// </summary> | |
protected EntityManager Manager => m_Manager; | |
protected void UpdateSystem<T>() where T : unmanaged, ISystem | |
{ | |
World.GetExistingSystem<T>().Update(World.Unmanaged); | |
} | |
protected SystemHandle CreateSystem<T>() where T : unmanaged, ISystem => World.CreateSystem<T>(); | |
protected Entity CreateEntity(params ComponentType[] types) => Manager.CreateEntity(types); | |
protected void CreateEntities(ComponentType[] types, int entityCount = EntityCount) | |
{ | |
for (var i = 0; i < entityCount; i++) | |
{ | |
Manager.CreateEntity(types); | |
} | |
} | |
protected void CreateEntityCommandBufferSystem() | |
{ | |
World.CreateSystem<EndSimulationEntityCommandBufferSystem>(); | |
} | |
} | |
// If ENABLE_UNITY_COLLECTIONS_CHECKS is not defined we will ignore the test | |
// When using this attribute, consider it to logically AND with any other TestRequiresxxxx attrubute | |
#if ENABLE_UNITY_COLLECTIONS_CHECKS | |
internal class TestRequiresCollectionChecks : System.Attribute | |
{ | |
public TestRequiresCollectionChecks(string msg = null) { } | |
} | |
#else | |
internal class TestRequiresCollectionChecks : IgnoreAttribute | |
{ | |
public TestRequiresCollectionChecks(string msg = null) : base($"Test requires ENABLE_UNITY_COLLECTION_CHECKS which is not defined{(msg == null ? "." : $": {msg}")}") { } | |
} | |
#endif | |
// If ENABLE_UNITY_COLLECTIONS_CHECKS and UNITY_DOTS_DEBUG is not defined we will ignore the test | |
// conversely if either of them are defined the test will be run. | |
// When using this attribute, consider it to logically AND with any other TestRequiresxxxx attrubute | |
#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG | |
internal class TestRequiresDotsDebugOrCollectionChecks: System.Attribute | |
{ | |
public TestRequiresDotsDebugOrCollectionChecks(string msg = null) { } | |
} | |
#else | |
internal class TestRequiresDotsDebugOrCollectionChecks : IgnoreAttribute | |
{ | |
public TestRequiresDotsDebugOrCollectionChecks(string msg = null) : base($"Test requires UNITY_DOTS_DEBUG || ENABLE_UNITY_COLLECTION_CHECKS which neither are defined{(msg == null ? "." : $": {msg}")}") { } | |
} | |
#endif | |
// Ignores te test when in an il2cpp build only. Please make use of the 'msg' string | |
// to tell others why this test should be ignored | |
#if !ENABLE_IL2CPP | |
internal class IgnoreTest_IL2CPP: System.Attribute | |
{ | |
public IgnoreTest_IL2CPP(string msg = null) { } | |
} | |
#else | |
internal class IgnoreTest_IL2CPP : IgnoreAttribute | |
{ | |
public IgnoreTest_IL2CPP(string msg = null) : base($"Test ignored on IL2CPP builds{(msg == null ? "." : $": {msg}")}") { } | |
} | |
#endif | |
public partial class EmptySystem : SystemBase | |
{ | |
protected override void OnUpdate() {} | |
public new EntityQuery GetEntityQuery(params EntityQueryDesc[] queriesDesc) | |
{ | |
return base.GetEntityQuery(queriesDesc); | |
} | |
public new EntityQuery GetEntityQuery(params ComponentType[] componentTypes) | |
{ | |
return base.GetEntityQuery(componentTypes); | |
} | |
public new EntityQuery GetEntityQuery(NativeArray<ComponentType> componentTypes) | |
{ | |
return base.GetEntityQuery(componentTypes); | |
} | |
} | |
[BurstCompile(CompileSynchronously = true)] | |
public class ECSTestsCommonBase | |
{ | |
[SetUp] | |
public virtual void Setup() | |
{ | |
#if UNITY_DOTSRUNTIME | |
Unity.Runtime.TempMemoryScope.EnterScope(); | |
UnityEngine.TestTools.LogAssert.ExpectReset(); | |
#endif | |
} | |
[TearDown] | |
public virtual void TearDown() | |
{ | |
#if UNITY_DOTSRUNTIME | |
Unity.Runtime.TempMemoryScope.ExitScope(); | |
#endif | |
} | |
[BurstDiscard] | |
static public void TestBurstCompiled(ref bool falseIfNot) | |
{ | |
falseIfNot = false; | |
} | |
[BurstCompile(CompileSynchronously = true)] | |
static public bool IsBurstEnabled() | |
{ | |
bool burstCompiled = true; | |
TestBurstCompiled(ref burstCompiled); | |
return burstCompiled; | |
} | |
} | |
public abstract class ECSTestsFixture : ECSTestsCommonBase | |
{ | |
protected World m_PreviousWorld; | |
protected World World; | |
#if !UNITY_DOTSRUNTIME | |
protected PlayerLoopSystem m_PreviousPlayerLoop; | |
#endif | |
protected EntityManager m_Manager; | |
protected EntityManager.EntityManagerDebug m_ManagerDebug; | |
protected int StressTestEntityCount = 1000; | |
private bool JobsDebuggerWasEnabled; | |
[SetUp] | |
public override void Setup() | |
{ | |
base.Setup(); | |
#if !UNITY_DOTSRUNTIME | |
// unit tests preserve the current player loop to restore later, and start from a blank slate. | |
m_PreviousPlayerLoop = PlayerLoop.GetCurrentPlayerLoop(); | |
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop()); | |
#endif | |
m_PreviousWorld = World.DefaultGameObjectInjectionWorld; | |
World = World.DefaultGameObjectInjectionWorld = new World("Test World"); | |
World.UpdateAllocatorEnableBlockFree = true; | |
m_Manager = World.EntityManager; | |
m_ManagerDebug = new EntityManager.EntityManagerDebug(m_Manager); | |
// Many ECS tests will only pass if the Jobs Debugger enabled; | |
// force it enabled for all tests, and restore the original value at teardown. | |
JobsDebuggerWasEnabled = JobsUtility.JobDebuggerEnabled; | |
JobsUtility.JobDebuggerEnabled = true; | |
#if (UNITY_EDITOR || DEVELOPMENT_BUILD) && !DISABLE_ENTITIES_JOURNALING | |
// In case entities journaling is initialized, clear it | |
EntitiesJournaling.Clear(); | |
#endif | |
} | |
[TearDown] | |
public override void TearDown() | |
{ | |
if (World != null && World.IsCreated) | |
{ | |
// Clean up systems before calling CheckInternalConsistency because we might have filters etc | |
// holding on SharedComponentData making checks fail | |
while (World.Systems.Count > 0) | |
{ | |
World.DestroySystemManaged(World.Systems[0]); | |
} | |
m_ManagerDebug.CheckInternalConsistency(); | |
World.Dispose(); | |
World = null; | |
World.DefaultGameObjectInjectionWorld = m_PreviousWorld; | |
m_PreviousWorld = null; | |
m_Manager = default; | |
} | |
JobsUtility.JobDebuggerEnabled = JobsDebuggerWasEnabled; | |
#if !UNITY_DOTSRUNTIME | |
PlayerLoop.SetPlayerLoop(m_PreviousPlayerLoop); | |
#endif | |
base.TearDown(); | |
} | |
public void AssertDoesNotExist(Entity entity) | |
{ | |
global::Unity.Assertions.Assert.IsFalse(m_Manager.Exists(entity)); | |
} | |
public void AssertComponentData(Entity entity, int index) | |
{ | |
global::Unity.Assertions.Assert.IsTrue(m_Manager.Exists(entity)); | |
} | |
public void AssertSameChunk(Entity e0, Entity e1) | |
{ | |
global::Unity.Assertions.Assert.AreEqual(m_Manager.GetChunk(e0), m_Manager.GetChunk(e1)); | |
} | |
public void AssetHasChangeVersion<T>(Entity e, uint version) where T : | |
#if UNITY_DISABLE_MANAGED_COMPONENTS | |
struct, | |
#endif | |
IComponentData | |
{ | |
var type = m_Manager.GetComponentTypeHandle<T>(true); | |
var chunk = m_Manager.GetChunk(e); | |
global::Unity.Assertions.Assert.AreEqual(version, chunk.GetChangeVersion(ref type)); | |
global::Unity.Assertions.Assert.IsFalse(chunk.DidChange(ref type, version)); | |
global::Unity.Assertions.Assert.IsTrue(chunk.DidChange(ref type, version - 1)); | |
} | |
public void AssetHasChunkOrderVersion(Entity e, uint version) | |
{ | |
var chunk = m_Manager.GetChunk(e); | |
global::Unity.Assertions.Assert.AreEqual(version, chunk.GetOrderVersion()); | |
} | |
public void AssetHasBufferChangeVersion<T>(Entity e, uint version) where T : unmanaged, IBufferElementData | |
{ | |
var type = m_Manager.GetBufferTypeHandle<T>(true); | |
var chunk = m_Manager.GetChunk(e); | |
global::Unity.Assertions.Assert.AreEqual(version, chunk.GetChangeVersion(ref type)); | |
global::Unity.Assertions.Assert.IsFalse(chunk.DidChange(ref type, version)); | |
global::Unity.Assertions.Assert.IsTrue(chunk.DidChange(ref type, version - 1)); | |
} | |
public void AssetHasSharedChangeVersion<T>(Entity e, uint version) where T : unmanaged, ISharedComponentData | |
{ | |
var type = m_Manager.GetSharedComponentTypeHandle<T>(); | |
var chunk = m_Manager.GetChunk(e); | |
global::Unity.Assertions.Assert.AreEqual(version, chunk.GetChangeVersion(type)); | |
global::Unity.Assertions.Assert.IsFalse(chunk.DidChange(type, version)); | |
global::Unity.Assertions.Assert.IsTrue(chunk.DidChange(type, version - 1)); | |
} | |
partial class EntityForEachSystem : SystemBase | |
{ | |
protected override void OnUpdate() {} | |
} | |
public EmptySystem EmptySystem | |
{ | |
get | |
{ | |
return World.DefaultGameObjectInjectionWorld.GetOrCreateSystemManaged<EmptySystem>(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment