using System.Diagnostics.CodeAnalysis; using System.Reflection; using Pcg; using PkmnLib.Dynamic.Models; using PkmnLib.Static; using PkmnLib.Static.Species; using PkmnLib.Static.Utils; using PkmnLib.Tests.Integration; namespace PkmnLib.Tests.Static; public class DeepCloneTests { [SuppressMessage("ReSharper", "UnusedMember.Local")] [SuppressMessage("ReSharper", "ValueParameterNotUsed")] private class TestClass : IDeepCloneable { public int Value { get; set; } public int Field; private int PrivateValue { get; set; } #pragma warning disable CS0169 // Field is never used private int _privateField; #pragma warning restore CS0169 // Field is never used private int OnlyGetter => 0; private int OnlySetter { set { } } public TestClass? Self { get; set; } } [Test] public async Task DeepCloneTestProperty() { var obj = new TestClass { Value = 1 }; var clone = obj.DeepClone(); await Assert.That(clone).IsNotEqualTo(obj); await Assert.That(clone.Value).IsEqualTo(1); } [Test] public async Task DeepCloneTestField() { var obj = new TestClass { Field = 1 }; var clone = obj.DeepClone(); await Assert.That(clone).IsNotEqualTo(obj); await Assert.That(clone.Field).IsEqualTo(1); } [Test] public async Task DeepCloneTestPrivateProperty() { var obj = new TestClass(); obj.GetType().GetProperty("PrivateValue", BindingFlags.NonPublic | BindingFlags.Instance)!.SetValue(obj, 1); var clone = obj.DeepClone(); await Assert.That(clone).IsNotEqualTo(obj); var clonePrivateValue = clone.GetType().GetProperty("PrivateValue", BindingFlags.NonPublic | BindingFlags.Instance)! .GetValue(clone); await Assert.That(clonePrivateValue).IsEqualTo(1); } [Test] public async Task DeepCloneTestPrivateField() { var obj = new TestClass(); obj.GetType().GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance)!.SetValue(obj, 1); var clone = obj.DeepClone(); await Assert.That(clone).IsNotEqualTo(obj); var clonePrivateField = clone.GetType().GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance)! .GetValue(clone); await Assert.That(clonePrivateField).IsEqualTo(1); } [Test] public async Task DeepCloneTestRecursion() { var obj = new TestClass(); obj.Self = obj; var clone = obj.DeepClone(); await Assert.That(clone).IsNotEqualTo(obj); await Assert.That(clone.Self).IsNotEqualTo(obj); await Assert.That(clone.Self).IsEqualTo(clone); } [Test] public async Task DeepCloneIntegrationTestsBattle() { var library = LibraryHelpers.LoadLibrary(); await Assert.That(library.StaticLibrary.Species.TryGet("bulbasaur", out var bulbasaur)).IsTrue(); await Assert.That(library.StaticLibrary.Species.TryGet("charmander", out var charmander)).IsTrue(); var party1 = new PokemonParty(6); party1.SwapInto(new PokemonImpl(library, bulbasaur!, bulbasaur!.GetDefaultForm(), new AbilityIndex { IsHidden = false, Index = 0, }, 50, 0, Gender.Male, 0, "hardy"), 0); var party2 = new PokemonParty(6); party2.SwapInto(new PokemonImpl(library, charmander!, charmander!.GetDefaultForm(), new AbilityIndex { IsHidden = false, Index = 0, }, 50, 0, Gender.Male, 0, "hardy"), 0); var parties = new[] { new BattlePartyImpl(party1, [new ResponsibleIndex(0, 0)]), new BattlePartyImpl(party2, [new ResponsibleIndex(1, 0)]), }; var battle = new BattleImpl(library, parties, false, 2, 3, randomSeed: 0); battle.Sides[0].SwapPokemon(0, party1[0]); battle.Sides[1].SwapPokemon(0, party2[0]); party1[0]!.ChangeStatBoost(Statistic.Defense, 2, true); await Assert.That(party1[0]!.StatBoost.Defense).IsEqualTo((sbyte)2); var clone = battle.DeepClone(); await Assert.That(clone).IsNotEqualTo(battle); await Assert.That(clone.Sides[0].Pokemon[0]).IsNotEqualTo(battle.Sides[0].Pokemon[0]); await Assert.That(clone.Sides[1].Pokemon[0]).IsNotEqualTo(battle.Sides[1].Pokemon[0]); await Assert.That(clone.Sides[0].Pokemon[0]!.Species).IsEqualTo(battle.Sides[0].Pokemon[0]!.Species); await Assert.That(clone.Sides[1].Pokemon[0]!.Species).IsEqualTo(battle.Sides[1].Pokemon[0]!.Species); await Assert.That(clone.Library).IsEqualTo(battle.Library); var pokemon = clone.Sides[0].Pokemon[0]!; await Assert.That(pokemon.BattleData).IsNotNull(); await Assert.That(pokemon.BattleData).IsNotEqualTo(battle.Sides[0].Pokemon[0]!.BattleData); await Assert.That(pokemon.BattleData!.Battle).IsEqualTo(clone); await Assert.That(pokemon.BattleData!.SeenOpponents).Contains(clone.Sides[1].Pokemon[0]!); await Assert.That(pokemon.BattleData!.SeenOpponents).DoesNotContain(battle.Sides[1].Pokemon[0]!); await Assert.That(pokemon.StatBoost.Defense).IsEqualTo((sbyte)2); } /// /// We have custom handling for the random number generator within the deep cloning handling. We need to ensure that /// the random number generator is cloned correctly, so that the state of the random number generator is the same /// in the clone as it is in the original, while still being a different instance. /// [Test] public async Task DeepCloneIntegrationTestsBattleRandom() { var battleRandom = new BattleRandomImpl(0); battleRandom.GetInt(); var clone = battleRandom.DeepClone(); await Assert.That(clone).IsNotEqualTo(battleRandom); // We hack out way into the private fields of the random number generator to ensure that the state is the same // in the clone as it is in the original. None of this is part of the public API, so we use reflection to // access the private fields. var pcgRandomField = typeof(RandomImpl).GetField("_random", BindingFlags.NonPublic | BindingFlags.Instance)!; var pcgRandom = (PcgRandom)pcgRandomField.GetValue(battleRandom)!; var clonePcgRandom = (PcgRandom)pcgRandomField.GetValue(clone)!; await Assert.That(clonePcgRandom).IsNotEqualTo(pcgRandom); var pcgRngField = typeof(PcgRandom).GetField("_rng", BindingFlags.NonPublic | BindingFlags.Instance)!; var pcgRng = pcgRngField.GetValue(pcgRandom)!; var clonePcgRng = pcgRngField.GetValue(clonePcgRandom)!; var pcgStateField = pcgRng.GetType().GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance)!; var pcgState = pcgStateField.GetValue(pcgRng); var clonePcgState = pcgStateField.GetValue(clonePcgRng); await Assert.That(clonePcgState).IsEqualTo(pcgState); var randomNumber = battleRandom.GetInt(); var cloneRandomNumber = clone.GetInt(); await Assert.That(cloneRandomNumber).IsEqualTo(randomNumber); } }