using Annulus; using Azimuth; using Blacklight.Core.Drawables; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Reflection; using UnitTestSharp; namespace Blacklight.Core.UnitTests.Drawables { public class SpriteBatchTests : TestFixture { public class TestTexture : Texture { } private static SpriteBatch.Entry EntryAt(int x) { return new SpriteBatch.Entry { SourceOrigin = new Point(x, 0) }; } public class EntryCtorTests : TestFixture { static readonly SpriteSource Source = new SpriteSource { Origin = new Point(11, 22), Size = new Size(33, 44), RedChannel = ColorChannel.A, GreenChannel = ColorChannel.B, BlueChannel = ColorChannel.G, AlphaChannel = ColorChannel.R, }; static readonly SpriteDestination Destination = new SpriteDestination { min = new Vector(1, 2), max = new Vector(3, 4), }; public void TakesTheRegionFromTheSource() { var entry = new SpriteBatch.Entry(Source, Destination); CheckEqual(new Point(11, 22), entry.SourceOrigin); CheckEqual(new Size(33, 44), entry.SourceSize); } public void TakesEachChannelFromTheSource() { var entry = new SpriteBatch.Entry(Source, Destination); CheckEqual(ColorChannel.A, entry.RedChannel); CheckEqual(ColorChannel.B, entry.GreenChannel); CheckEqual(ColorChannel.G, entry.BlueChannel); CheckEqual(ColorChannel.R, entry.AlphaChannel); } public void TakesTheDestination() { var entry = new SpriteBatch.Entry(Source, Destination); CheckEqual(new Vector(1, 2), entry.Destination.min); CheckEqual(new Vector(3, 4), entry.Destination.max); } } public class CtorTests : TestFixture { public void StartsEmpty() { CheckEqual(0, new SpriteBatch().Count); } public void Capacity_SizesTheStore() { var spriteBatch = new SpriteBatch(capacity: 40); CheckEqual(0, spriteBatch.Count); CheckEqual(40, spriteBatch.Capacity); } public void ZeroCapacity_StillTakesEntries() { var spriteBatch = new SpriteBatch(capacity: 0); spriteBatch.Add(EntryAt(7)); CheckEqual(1, spriteBatch.Count); CheckEqual(7, spriteBatch.Entries[0].SourceOrigin.X); } public void NegativeCapacity_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SpriteBatch(capacity: -1); } public void HasNoTexturesOrClipWindow() { var spriteBatch = new SpriteBatch(); CheckNull(spriteBatch.Texture); CheckNull(spriteBatch.TintTexture); Check(spriteBatch.ClipWindow == null); } public void CoverageGammaDefaultsToTheNoOpValue() { CheckEqual(1, new SpriteBatch().CoverageGamma); } public void AdditivenessDefaultsToPlainAlphaBlending() { CheckEqual(0, new SpriteBatch().Additiveness); } public void TilingDefaultsToStretchLikeASprite() { var spriteBatch = new SpriteBatch(); CheckEqual(new Sprite().TilingBehaviorX, spriteBatch.TilingBehaviorX); CheckEqual(new Sprite().TilingBehaviorY, spriteBatch.TilingBehaviorY); } public void TintColorDefaultsToWhatASpriteDefaultsTo() { CheckEqual(new Sprite().TintColor, new SpriteBatch().TintColor); } } public class AddTests : TestFixture { public void CountsEachEntry() { var spriteBatch = new SpriteBatch(); spriteBatch.Add(EntryAt(1)); spriteBatch.Add(EntryAt(2)); CheckEqual(2, spriteBatch.Count); } public void KeepsEntriesInTheOrderAdded() { var spriteBatch = new SpriteBatch(); spriteBatch.Add(EntryAt(5)); spriteBatch.Add(EntryAt(3)); spriteBatch.Add(EntryAt(9)); CheckEqual(5, spriteBatch.Entries[0].SourceOrigin.X); CheckEqual(3, spriteBatch.Entries[1].SourceOrigin.X); CheckEqual(9, spriteBatch.Entries[2].SourceOrigin.X); } public void KeepsEveryEntryPastTheStartingCapacity() { var spriteBatch = new SpriteBatch(capacity: 4); for (int i = 0; i < 20; ++i) { spriteBatch.Add(EntryAt(i)); } CheckEqual(20, spriteBatch.Count); for (int i = 0; i < spriteBatch.Count; ++i) { CheckEqual(i, spriteBatch.Entries[i].SourceOrigin.X); } } public void IsSeenThroughEveryCopy() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.Add(EntryAt(3)); CheckEqual(1, copy.Count); CheckEqual(3, copy.Entries[0].SourceOrigin.X); } public void OnNone_Throws() { CheckThrow(typeof(InvalidOperationException)); SpriteBatch.None.Add(EntryAt(1)); } } public class AddEntryTests : TestFixture { public void MutatingTheResultUpdatesTheStoredEntry() { var spriteBatch = new SpriteBatch(); ref SpriteBatch.Entry entry = ref spriteBatch.AddEntry(); entry.SourceOrigin = new Point(5, 5); CheckEqual(5, spriteBatch.Entries[0].SourceOrigin.X); } public void CountsTheEntry() { var spriteBatch = new SpriteBatch(); spriteBatch.AddEntry(); CheckEqual(1, spriteBatch.Count); } public void KeepsEveryEntryPastTheStartingCapacity() { var spriteBatch = new SpriteBatch(capacity: 4); for (int i = 0; i < 20; ++i) { spriteBatch.AddEntry().SourceOrigin = new Point(i, 0); } CheckEqual(20, spriteBatch.Count); for (int i = 0; i < spriteBatch.Count; ++i) { CheckEqual(i, spriteBatch.Entries[i].SourceOrigin.X); } } public void OnNone_Throws() { CheckThrow(typeof(InvalidOperationException)); SpriteBatch.None.AddEntry(); } } public class ClearTests : TestFixture { public void EmptiesTheBatch() { var spriteBatch = new SpriteBatch(); spriteBatch.Add(EntryAt(1)); spriteBatch.Clear(); CheckEqual(0, spriteBatch.Count); } public void KeepsTheCapacity() { var spriteBatch = new SpriteBatch(capacity: 4); for (int i = 0; i < 20; ++i) { spriteBatch.Add(EntryAt(i)); } int grown = spriteBatch.Capacity; spriteBatch.Clear(); CheckEqual(grown, spriteBatch.Capacity); } public void LetsTheBatchBeRefilledFromTheStart() { var spriteBatch = new SpriteBatch(); spriteBatch.Add(EntryAt(1)); spriteBatch.Add(EntryAt(2)); spriteBatch.Clear(); spriteBatch.Add(EntryAt(7)); CheckEqual(1, spriteBatch.Count); CheckEqual(7, spriteBatch.Entries[0].SourceOrigin.X); } public void LeavesTheSharedStateAlone() { var texture = new TestTexture(); var spriteBatch = new SpriteBatch { Texture = texture, CoverageGamma = 2 }; spriteBatch.Clear(); CheckEqual(texture, spriteBatch.Texture); CheckEqual(2, spriteBatch.CoverageGamma); } public void EmptiesEveryCopy() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.Add(EntryAt(1)); spriteBatch.Clear(); CheckEqual(0, copy.Count); } public void OnNone_Throws() { CheckThrow(typeof(InvalidOperationException)); SpriteBatch.None.Clear(); } } public class NoneTests : TestFixture { public void HasNoEntriesOrRoomForAny() { CheckEqual(0, SpriteBatch.None.Count); CheckEqual(0, SpriteBatch.None.Capacity); CheckEqual(0, SpriteBatch.None.Entries.Count); } public void ReadsTheSameSettingsAsANewBatch() { var fresh = new SpriteBatch(); SpriteBatch none = SpriteBatch.None; CheckNull(none.Texture); CheckNull(none.TintTexture); Check(none.ClipWindow == null); CheckEqual(fresh.TintColor, none.TintColor); CheckEqual(fresh.Additiveness, none.Additiveness); CheckEqual(fresh.CoverageGamma, none.CoverageGamma); CheckEqual(fresh.TilingBehaviorX, none.TilingBehaviorX); CheckEqual(fresh.TilingBehaviorY, none.TilingBehaviorY); } public void IsTheDefaultValue() { CheckEqual(default(SpriteBatch), SpriteBatch.None); } } public class IsNoneTests : TestFixture { public void None_IsNone() { CheckTrue(SpriteBatch.None.IsNone); } public void ParameterlessConstruction_IsNotNone() { CheckFalse(new SpriteBatch().IsNone); } public void ConstructionWithACapacity_IsNotNone() { CheckFalse(new SpriteBatch(capacity: 4).IsNone); } } public class OwnerTests : TestFixture { public void Standalone_HasNone() { CheckNull(new SpriteBatch().Owner); } public void None_HasNone() { CheckNull(SpriteBatch.None.Owner); } public void FromAnOwnedStore_IsTheStoresOwner() { var container = new EntityContainer(); CheckEqual(container, new SpriteBatch.Store(owner: container).Create(capacity: 0).Owner); } public void FromBeforeItsStoreWasCleared_Throws() { var store = new SpriteBatch.Store(owner: new EntityContainer()); SpriteBatch earlier = store.Create(capacity: 0); store.Clear(); CheckThrow(typeof(InvalidOperationException)); _ = earlier.Owner; } } public class TextureTests : TestFixture { public void IsSharedByCopies() { var texture = new TestTexture(); var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.Texture = texture; CheckEqual(texture, copy.Texture); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.Texture = new TestTexture(); } } public class TintTextureTests : TestFixture { public void IsSharedByCopies() { var texture = new TestTexture(); var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.TintTexture = texture; CheckEqual(texture, copy.TintTexture); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.TintTexture = new TestTexture(); } } public class TintColorTests : TestFixture { public void IsSharedByCopies() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.TintColor = new TintColor(1, 0, 0); CheckEqual(new TintColor(1, 0, 0), copy.TintColor); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.TintColor = new TintColor(1, 0, 0); } } public class AdditivenessTests : TestFixture { public void IsSharedByCopies() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.Additiveness = 0.5; CheckEqual(0.5, copy.Additiveness); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.Additiveness = 0.5; } } public class CoverageGammaTests : TestFixture { public void IsSharedByCopies() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.CoverageGamma = 2; CheckEqual(2, copy.CoverageGamma); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.CoverageGamma = 2; } } public class ClipWindowTests : TestFixture { static readonly Rect Window = Rect.FromPositionAndSize(new Vector(1, 2), new Vector(3, 4)); public void IsSharedByCopies() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.ClipWindow = Window; CheckEqual(Window, copy.ClipWindow.Value); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.ClipWindow = Window; } } public class TilingBehaviorXTests : TestFixture { public void IsSharedByCopies() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.TilingBehaviorX = TilingBehavior.StretchAndTile; CheckEqual(TilingBehavior.StretchAndTile, copy.TilingBehaviorX); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.TilingBehaviorX = TilingBehavior.StretchAndTile; } } public class TilingBehaviorYTests : TestFixture { public void IsSharedByCopies() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; spriteBatch.TilingBehaviorY = TilingBehavior.StretchAndTile; CheckEqual(TilingBehavior.StretchAndTile, copy.TilingBehaviorY); } public void OnNone_Throws() { SpriteBatch none = SpriteBatch.None; CheckThrow(typeof(InvalidOperationException)); none.TilingBehaviorY = TilingBehavior.StretchAndTile; } } public class EqualsTests : TestFixture { public void Copies_AreEqual() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; CheckTrue(spriteBatch.Equals(copy)); } public void BatchesWithStoresOfTheirOwn_AreNotEqual() { CheckFalse(new SpriteBatch().Equals(new SpriteBatch())); } public void BatchesSharingAStore_AreNotEqual() { var store = new SpriteBatch.Store(); CheckFalse(store.Create(capacity: 0).Equals(store.Create(capacity: 0))); } public void SameSlotAfterAClear_IsNotEqual() { var store = new SpriteBatch.Store(); SpriteBatch before = store.Create(capacity: 0); store.Clear(); CheckFalse(before.Equals(store.Create(capacity: 0))); } public void BoxedCopy_IsEqual() { var spriteBatch = new SpriteBatch(); CheckTrue(spriteBatch.Equals((object)spriteBatch)); } public void ObjectOfAnotherType_IsNotEqual() { CheckFalse(new SpriteBatch().Equals("not a sprite batch")); } } public class EqualityOperatorTests : TestFixture { public void Copies_CompareEqual() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; CheckTrue(spriteBatch == copy); } public void DifferentBatches_CompareUnequal() { CheckFalse(new SpriteBatch() == new SpriteBatch()); } } public class InequalityOperatorTests : TestFixture { public void Copies_AreNotUnequal() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; CheckFalse(spriteBatch != copy); } public void DifferentBatches_AreUnequal() { CheckTrue(new SpriteBatch() != new SpriteBatch()); } } public class GetHashCodeTests : TestFixture { public void Copies_HashTheSame() { var spriteBatch = new SpriteBatch(); SpriteBatch copy = spriteBatch; CheckEqual(spriteBatch.GetHashCode(), copy.GetHashCode()); } public void None_HashesWithoutAStore() { CheckEqual(default(SpriteBatch).GetHashCode(), SpriteBatch.None.GetHashCode()); } } public class SpriteParityTests : TestFixture { private static readonly Type[] Homes = { typeof(SpriteBatch), typeof(SpriteBatch.Entry) }; private static List<(string Name, Type Type)> PublicMembers(Type type) { var returnMe = new List<(string Name, Type Type)>(); foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { returnMe.Add((property.Name, property.PropertyType)); } foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { returnMe.Add((field.Name, field.FieldType)); } return returnMe; } private static Type MemberType(Type owner, string name) { return owner.GetProperty(name)?.PropertyType ?? owner.GetField(name)?.FieldType; } private static bool IsCompound(Type type) { return type.IsValueType && !type.IsPrimitive && !type.IsEnum && Nullable.GetUnderlyingType(type) == null; } private static List<(string Home, Type Type)> NamedMatches(string[] candidateNames) { var returnMe = new List<(string Home, Type Type)>(); foreach (Type home in Homes) { foreach (string candidateName in candidateNames) { Type type = MemberType(home, candidateName); if (type != null) { returnMe.Add(($"{home.Name}.{candidateName}", type)); } } } return returnMe; } // A member's counterpart is named the same, or with its parent's name in front (Source.Origin is // SourceOrigin). A compound member with no counterpart of its own is checked member by member, but only // counts as split up if some of its members find a home; otherwise it's just an unhoused member. private static int FindProblems(Type type, string path, string parentName, List problems) { int membersChecked = 0; foreach ((string name, Type memberType) in PublicMembers(type)) { string memberPath = $"{path}.{name}"; string[] candidateNames = parentName == null ? new[] { name } : new[] { name, parentName + name }; List<(string Home, Type Type)> matches = NamedMatches(candidateNames); List exactHomes = matches.Where(match => match.Type == memberType).Select(match => match.Home).ToList(); if (matches.Count == 0 && parentName == null && IsCompound(memberType)) { var childProblems = new List(); int childrenChecked = FindProblems(memberType, memberPath, parentName: name, childProblems); if (childrenChecked > childProblems.Count) { membersChecked += childrenChecked; problems.AddRange(childProblems); continue; } } ++membersChecked; if (exactHomes.Count > 1) { problems.Add($"{memberPath} has several counterparts: {string.Join(", ", exactHomes)}"); } else if (exactHomes.Count == 0 && matches.Count > 0) { problems.Add($"{memberPath} is {memberType.Name}, " + $"but {matches[0].Home} is {matches[0].Type.Name}"); } else if (exactHomes.Count == 0) { problems.Add($"{memberPath} has no counterpart"); } } return membersChecked; } public void EverySpriteMemberHasExactlyOneCounterpartOfTheSameType() { var problems = new List(); int membersChecked = FindProblems(typeof(Sprite), "Sprite", parentName: null, problems); Check(membersChecked > 0); CheckEqualCommutative(new string[0], problems); } } } }