Skip to content

Instantly share code, notes, and snippets.

@AlexMerzlikin
Created March 2, 2025 12:49
Show Gist options
  • Save AlexMerzlikin/67bce261b58062650916925a08645f39 to your computer and use it in GitHub Desktop.
Save AlexMerzlikin/67bce261b58062650916925a08645f39 to your computer and use it in GitHub Desktop.
Example of CustomEcsTestsFixture without the reference to Unity.Entities testables
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