EntityAssertionQuery : Query ECS data for assertion in 1 line

This is a utility class I have been using in my ECS testing to get a data for assertion. It combines EntityQuery creation, value query, value filtering, and finally disposing all native continers used before returning you the value in a managed array that you don’t have to dispose, in one line. Queries are built with permutation of generic types and arguments, favoring readable test code over performance.

You can copy the file EntityAssertionQuery.cs and EntityAssertionQuery.gen.cs (this generated code is almost 20000 lines minus comments, GitHub can’t show it) from GitHub - 5argon/EcsTesting: Utilities to help writing tests for Unity Entities. or just use UPM pull. Also see more details on how to use it in the repo’s readme. Happy new year.

var eaq = new EntityAssertionQuery(world);

// When using methods that returns component data, the first type is the return type.
// All others are tags to further filter the result.
eaq.GetSingleton<CD1, CD2>(); //returns CD1
eaq.Components<CD1, CD2, CD3>(); //returns CD1

// Methods that returns `Entity` you can order however you like.
eaq.GetSingletonEntity<CD1, CD2, CD3>();
eaq.EntityCount<CD1>();
eaq.Entities<CD1, CD2>();

// With SCD and a value filter
eaq.Components<CD1, SCD1>(scd1Value);

// With SCD but do not want to filter, all values allowed as long as it is a chunk with this SCD type.
eaq.Components<CD1, SCD1>(nf: true);
eaq.EntityCount<CD1, CD2, SCD1>(nf: true);


// You can replace just one half with no-filter. Replace from left to right.
eaq.Entities<CD1, CD2, SCD1, SCD2>(scd1Value, scd2Value);
eaq.Entities<CD1, CD2, SCD1, SCD2>(nf1: true, scd2Value);
eaq.Entities<CD1, CD2, SCD1, SCD2>(nf1: true, nf2:true);

// Typing where: is optional, but it did make the test more readable.
eaq.Entities<CD1, CD2, CD3>( where: cd1 => cd1.value % 2 == 0 );

// You can add more up to total `IComponentData` you specified.
// It is then filtering different components of the same entity.
// You cannot do like (cd1, cd3) in the lambda, it must be from left to right as listed in generic type argument.
eaq.GetSingletonEntity<CD1, CD2, CD3, CD4>( where: (cd1, cd2) => cd1.value % 2 == 0 && cd1.value + cd2.value = 555; );

// It is possible to use WHERE CD filter together with SCD value filter, just make sure SCD filter comes later.
eaq.EntityCount<CD1, CD2, CD3, SCD1, SCD2>( where: cd1 => cd1.value % 2 == 0, scd1Value, nf1: true);
3 Likes

I don’t get those singleton queries, why do you query singletons by multiple types?

Hi, I think that name is no good. In my test there are a lot of query that I [0] on the returned array. In real environment there are much more so it is not really a singleton. I just want to not type [0] and want the test to fail if it returns 0 or >1 with better message not index out of range. Maybe I’ll change it to GetSingleEntity.

And this implemetation:

public CD1 GetSingleton<CD1, CD2>()
    where CD1 : struct, IComponentData
    where CD2 : struct, IComponentData
{
    using (var eq = em.CreateEntityQuery(
        ComponentType.ReadOnly<CD1>(),
        ComponentType.ReadOnly<CD2>()
    ))
    {
        return eq.GetSingleton<CD1>();
    }
}

will probably fail on this:

public unsafe T GetSingleton<T>() where T : struct, IComponentData
{
  if (this.GetIndexInEntityQuery(TypeManager.GetTypeIndex<T>()) != 1)
    throw new InvalidOperationException(string.Format("GetSingleton<{0}>() requires that {1} is the only component type in its archetype.", (object) typeof (T), (object) typeof (T)));
  int entityCount = this.CalculateEntityCount();
  if (entityCount != 1)
    throw new InvalidOperationException(string.Format("GetSingleton<{0}>() requires that exactly one {1} exists but there are {2}.", (object) typeof (T), (object) typeof (T), (object) entityCount));
  this.CompleteDependency();
  ArchetypeChunkIterator archetypeChunkIterator = this.GetArchetypeChunkIterator();
  archetypeChunkIterator.MoveNext();
  T output;
  UnsafeUtility.CopyPtrToStructure<T>(archetypeChunkIterator.GetCurrentChunkComponentDataPtr(false, 1), out output);
  return output;
}

particulary this:

if (this.GetIndexInEntityQuery(TypeManager.GetTypeIndex<T>()) != 1)
    throw new InvalidOperationException(string.Format("GetSingleton<{0}>() requires that {1} is the only component type in its archetype.", (object) typeof (T), (object) typeof (T)));

Anyways, I think that builder pattern will be more suitable for such a helper class, i.e.:

eaq.With<CD1>().With<SCD1>().With<BED1>().CountEntities();

It has been fixed to GetSingle / GetSingleEntity and now not relying on GetSingleton / GetSingletonEntity.