#nullable enable using System.Collections.Generic; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Validation; using Robust.UnitTesting; namespace Content.IntegrationTests.Tests.PrototypeTests; public sealed class PrototypeTests { /// /// This test writes all known server prototypes as yaml files, then validates that the result is valid yaml. /// Can help prevent instances where prototypes have bad C# default values. /// [Test] public async Task TestAllServerPrototypesAreSerializable() { await using var pair = await PoolManager.GetServerClient(); var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveThenValidatePrototype(pair.Server, "server", context); await pair.CleanReturnAsync(); } /// /// This test writes all known client prototypes as yaml files, then validates that the result is valid yaml. /// Can help prevent instances where prototypes have bad C# default values. /// [Test] public async Task TestAllClientPrototypesAreSerializable() { await using var pair = await PoolManager.GetServerClient(); var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveThenValidatePrototype(pair.Client, "client", context); await pair.CleanReturnAsync(); } public async Task SaveThenValidatePrototype(RobustIntegrationTest.IntegrationInstance instance, string instanceId, PrototypeSaveTest.TestEntityUidContext ctx) { var protoMan = instance.ResolveDependency(); Dictionary>> errors = default!; await instance.WaitPost(() => errors = protoMan.ValidateAllPrototypesSerializable(ctx)); if (errors.Count == 0) return; Assert.Multiple(() => { foreach (var (kind, ids) in errors) { foreach (var (id, nodes) in ids) { var msg = $"Error when validating {instanceId} prototype ({kind.Name}, {id}). Errors: \n"; foreach (var errorNode in nodes) { msg += $" - {errorNode.ErrorReason}\n"; } Assert.Fail(msg); } } }); } /// /// This test writes all known prototypes as yaml files, reads them again, then serializes them again. /// [Test] public async Task ServerPrototypeSaveLoadSaveTest() { await using var pair = await PoolManager.GetServerClient(); var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveLoadSavePrototype(pair.Server, context); await pair.CleanReturnAsync(); } /// /// This test writes all known prototypes as yaml files, reads them again, then serializes them again. /// [Test] public async Task ClientPrototypeSaveLoadSaveTest() { await using var pair = await PoolManager.GetServerClient(); var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveLoadSavePrototype(pair.Client, context); await pair.CleanReturnAsync(); } private async Task SaveLoadSavePrototype( RobustIntegrationTest.IntegrationInstance instance, PrototypeSaveTest.TestEntityUidContext ctx) { var protoMan = instance.ResolveDependency(); var seriMan = instance.ResolveDependency(); await instance.WaitAssertion(() => { Assert.Multiple(() => { foreach (var kind in protoMan.EnumeratePrototypeKinds()) { foreach (var proto in protoMan.EnumeratePrototypes(kind)) { var noException = TrySaveLoadSavePrototype( seriMan, protoMan, kind, proto, ctx); // This will probably throw an exception for each prototype of this kind. // We want to avoid having tests crash because they run out of time. if (!noException) break; } } }); }); } /// False if an exception was caught private bool TrySaveLoadSavePrototype( ISerializationManager seriMan, IPrototypeManager protoMan, Type kind, IPrototype proto, PrototypeSaveTest.TestEntityUidContext ctx) { DataNode first; DataNode second; try { first = seriMan.WriteValue(kind, proto, alwaysWrite: true, context:ctx); } catch (Exception e) { protoMan.TryGetMapping(kind, proto.ID, out var mapping); Assert.Fail($"Caught exception while writing {kind.Name} prototype {proto.ID}. Exception:\n{e}"); return false; } object? obj; try { obj = seriMan.Read(kind, first, context:ctx); } catch (Exception e) { protoMan.TryGetMapping(kind, proto.ID, out var mapping); Assert.Fail($"Caught exception while re-reading {kind.Name} prototype {proto.ID}." + $"\nException:\n{e}" + $"\n\nOriginal yaml:\n{mapping}" + $"\n\nWritten yaml:\n{first}"); return false; } Assert.That(obj?.GetType(), Is.EqualTo(proto.GetType())); var deserialized = (IPrototype) obj!; try { second = seriMan.WriteValue(kind, deserialized, alwaysWrite: true, context:ctx); } catch (Exception e) { protoMan.TryGetMapping(kind, proto.ID, out var mapping); Assert.Fail($"Caught exception while re-writing {kind.Name} prototype {proto.ID}." + $"\nException:\n{e}" + $"\n\nOriginal yaml:\n{mapping}" + $"\n\nWritten yaml:\n{first}"); return false; } var diff = first.Except(second); if (diff == null || diff.IsEmpty) return true; protoMan.TryGetMapping(kind, proto.ID, out var orig); Assert.Fail($"Re-written {kind.Name} prototype {proto.ID} differs." + $"\nYaml diff:\n{diff}" + $"\n\nOriginal yaml:\n{orig}" + $"\n\nWritten yaml:\n{first}" + $"\n\nRe-written Yaml:\n{second}"); return true; } }