Add prototype serialization tests. (#18458)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2023-08-06 14:47:45 +12:00
committed by GitHub
parent b97be440dd
commit 28a5e32f5e
18 changed files with 138 additions and 53 deletions

View File

@@ -40,7 +40,7 @@ public sealed class PrototypeSaveTest
[Test] [Test]
public async Task UninitializedSaveTest() public async Task UninitializedSaveTest()
{ {
// Apparently SpawnTest fails to clean up properly. Due to the similarities, I'll assume this also fails. // Apparently SpawnTest fails to clean up properly. Due to the similarities, I'll assume this also fails.
await using var pairTracker = await PoolManager.GetServerClient(); await using var pairTracker = await PoolManager.GetServerClient();
var server = pairTracker.Pair.Server; var server = pairTracker.Pair.Server;
@@ -190,14 +190,14 @@ public sealed class PrototypeSaveTest
await pairTracker.CleanReturnAsync(); await pairTracker.CleanReturnAsync();
} }
private sealed class TestEntityUidContext : ISerializationContext, public sealed class TestEntityUidContext : ISerializationContext,
ITypeSerializer<EntityUid, ValueDataNode> ITypeSerializer<EntityUid, ValueDataNode>
{ {
public SerializationManager.SerializerProvider SerializerProvider { get; } public SerializationManager.SerializerProvider SerializerProvider { get; }
public bool WritingReadingPrototypes { get; set; } public bool WritingReadingPrototypes { get; set; }
public string WritingComponent = string.Empty; public string WritingComponent = string.Empty;
public EntityPrototype Prototype = default!; public EntityPrototype? Prototype;
public TestEntityUidContext() public TestEntityUidContext()
{ {
@@ -215,7 +215,7 @@ public sealed class PrototypeSaveTest
IDependencyCollection dependencies, bool alwaysWrite = false, IDependencyCollection dependencies, bool alwaysWrite = false,
ISerializationContext? context = null) ISerializationContext? context = null)
{ {
if (WritingComponent != "Transform" && !Prototype.NoSpawn) if (WritingComponent != "Transform" && (Prototype?.NoSpawn == false))
{ {
// Maybe this will be necessary in the future, but at the moment it just indicates that there is some // Maybe this will be necessary in the future, but at the moment it just indicates that there is some
// issue, like a non-nullable entityUid data-field. If a component MUST have an entity uid to work with, // issue, like a non-nullable entityUid data-field. If a component MUST have an entity uid to work with,

View File

@@ -0,0 +1,52 @@
using Robust.Shared.Prototypes;
using Robust.UnitTesting;
namespace Content.IntegrationTests.Tests.PrototypeTests;
public sealed class PrototypeTests
{
/// <summary>
/// This test writes all known prototypes as yaml files, then validates that the result is valid yaml.
/// Can help prevent instances where prototypes have bad C# default values.
/// </summary>
[Test]
public async Task TestAllPrototypesAreSerializable()
{
await using var pairTracker = await PoolManager.GetServerClient();
var context = new PrototypeSaveTest.TestEntityUidContext();
Assert.Multiple(() =>
{
Validate(pairTracker.Pair.Server, "server", context);
// TODO fix client serialization
//Validate(pairTracker.Pair.Client, "client", context);
});
await pairTracker.CleanReturnAsync();
}
public void Validate(RobustIntegrationTest.IntegrationInstance instance, string instanceId,
PrototypeSaveTest.TestEntityUidContext ctx)
{
var protoMan = instance.ResolveDependency<IPrototypeManager>();
var 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);
}
}
});
}
}

View File

@@ -3,8 +3,6 @@ using Robust.Shared.Audio;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
// TODO: ECS.
namespace Content.Server.Arcade.SpaceVillain; namespace Content.Server.Arcade.SpaceVillain;
[RegisterComponent] [RegisterComponent]
@@ -93,16 +91,7 @@ public sealed class SpaceVillainArcadeComponent : SharedSpaceVillainArcadeCompon
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("possibleRewards", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))] [DataField("possibleRewards", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> PossibleRewards = new() public List<string> PossibleRewards = new();
{
"ToyMouse", "ToyAi", "ToyNuke", "ToyAssistant", "ToyGriffin", "ToyHonk", "ToyIan",
"ToyMarauder", "ToyMauler", "ToyGygax", "ToyOdysseus", "ToyOwlman", "ToyDeathRipley",
"ToyPhazon", "ToyFireRipley", "ToyReticence", "ToyRipley", "ToySeraph", "ToyDurand", "ToySkeleton",
"FoamCrossbow", "RevolverCapGun", "PlushieHampter", "PlushieLizard", "PlushieAtmosian", "PlushieSpaceLizard",
"PlushieNuke", "PlushieCarp", "PlushieRatvar", "PlushieNar", "PlushieSnake", "Basketball", "Football",
"PlushieRouny", "PlushieBee", "PlushieSlime", "BalloonCorgi", "ToySword", "CrayonBox", "BoxDonkSoftBox", "BoxCartridgeCap",
"HarmonicaInstrument", "OcarinaInstrument", "RecorderInstrument", "GunpetInstrument", "BirdToyInstrument", "PlushieXeno", "BeachBall"
};
/// <summary> /// <summary>
/// The minimum number of prizes the arcade machine can have. /// The minimum number of prizes the arcade machine can have.

View File

@@ -1,12 +0,0 @@
using Content.Server.Holiday.Interfaces;
namespace Content.Server.Holiday.Celebrate
{
public sealed class DefaultHolidayCelebrate : IHolidayCelebrate
{
public void Celebrate(HolidayPrototype holiday)
{
// Nada.
}
}
}

View File

@@ -2,6 +2,7 @@ using Content.Server.Holiday.Interfaces;
namespace Content.Server.Holiday.Greet namespace Content.Server.Holiday.Greet
{ {
[DataDefinition]
public sealed class DefaultHolidayGreet : IHolidayGreet public sealed class DefaultHolidayGreet : IHolidayGreet
{ {
public string Greet(HolidayPrototype holiday) public string Greet(HolidayPrototype holiday)

View File

@@ -1,4 +1,3 @@
using Content.Server.Holiday.Celebrate;
using Content.Server.Holiday.Greet; using Content.Server.Holiday.Greet;
using Content.Server.Holiday.Interfaces; using Content.Server.Holiday.Interfaces;
using Content.Server.Holiday.ShouldCelebrate; using Content.Server.Holiday.ShouldCelebrate;
@@ -40,7 +39,7 @@ namespace Content.Server.Holiday
private readonly IHolidayGreet _greet = new DefaultHolidayGreet(); private readonly IHolidayGreet _greet = new DefaultHolidayGreet();
[DataField("celebrate")] [DataField("celebrate")]
private readonly IHolidayCelebrate _celebrate = new DefaultHolidayCelebrate(); private readonly IHolidayCelebrate? _celebrate = null;
public bool ShouldCelebrate(DateTime date) public bool ShouldCelebrate(DateTime date)
{ {
@@ -57,7 +56,7 @@ namespace Content.Server.Holiday
/// </summary> /// </summary>
public void Celebrate() public void Celebrate()
{ {
_celebrate.Celebrate(this); _celebrate?.Celebrate(this);
} }
} }
} }

View File

@@ -2,7 +2,7 @@ using Content.Server.Holiday.Interfaces;
namespace Content.Server.Holiday.ShouldCelebrate namespace Content.Server.Holiday.ShouldCelebrate
{ {
[Virtual] [Virtual, DataDefinition]
public class DefaultHolidayShouldCelebrate : IHolidayShouldCelebrate public class DefaultHolidayShouldCelebrate : IHolidayShouldCelebrate
{ {
public virtual bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) public virtual bool ShouldCelebrate(DateTime date, HolidayPrototype holiday)

View File

@@ -4,6 +4,7 @@ using Content.Server.Nutrition.Components;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition;
using Microsoft.VisualBasic; using Microsoft.VisualBasic;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -102,9 +103,8 @@ public sealed class FlavorProfileSystem : EntitySystem
continue; continue;
} }
var flavor = proto.Flavor; if (proto.Flavor != null)
flavors.Add(proto.Flavor);
flavors.Add(flavor);
} }
return flavors; return flavors;

View File

@@ -1,4 +1,6 @@
using Robust.Shared.Prototypes; using System.Linq;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Alert namespace Content.Shared.Alert
{ {
@@ -14,22 +16,25 @@ namespace Content.Shared.Alert
public string ID { get; } = default!; public string ID { get; } = default!;
[DataField("order")] [DataField("order")]
private List<(string type, string alert)> Order private (string type, string alert)[] Order
{ {
// why would paul do this to me.
get get
{ {
var res = new List<(string, string)>(_typeToIdx.Count + _categoryToIdx.Count); var res = new (string, string)[_typeToIdx.Count + _categoryToIdx.Count];
foreach (var (type, id) in _typeToIdx) foreach (var (type, id) in _typeToIdx)
{ {
res.Insert(id, ("alertType", type.ToString())); res[id] = ("alertType", type.ToString());
} }
foreach (var (category, id) in _categoryToIdx) foreach (var (category, id) in _categoryToIdx)
{ {
res.Insert(id, ("category", category.ToString())); res[id] = ("category", category.ToString());
} }
DebugTools.Assert(res.All(x => x != default));
return res; return res;
} }
set set

View File

@@ -19,6 +19,7 @@ public sealed class BodyPrototypeSerializer : ITypeReader<BodyPrototype, Mapping
{ {
var nodes = new List<ValidationNode>(); var nodes = new List<ValidationNode>();
var prototypes = dependencies.Resolve<IPrototypeManager>(); var prototypes = dependencies.Resolve<IPrototypeManager>();
var factory = dependencies.Resolve<IComponentFactory>();
var connections = new List<string>(); var connections = new List<string>();
if (slot.TryGet("connections", out SequenceDataNode? connectionsNode)) if (slot.TryGet("connections", out SequenceDataNode? connectionsNode))
@@ -57,7 +58,7 @@ public sealed class BodyPrototypeSerializer : ITypeReader<BodyPrototype, Mapping
continue; continue;
} }
if (!organPrototype.HasComponent<OrganComponent>()) if (!organPrototype.HasComponent<OrganComponent>(factory))
{ {
nodes.Add(new ErrorNode(value, $"Organ {organ.Value} does not have a body component")); nodes.Add(new ErrorNode(value, $"Organ {organ.Value} does not have a body component"));
} }

View File

@@ -6,6 +6,7 @@ using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Nutrition;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -60,8 +61,8 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("recognizable")] [DataField("recognizable")]
public bool Recognizable = false; public bool Recognizable = false;
[DataField("flavor")] [DataField("flavor", customTypeSerializer:typeof(PrototypeIdSerializer<FlavorPrototype>))]
public string Flavor { get; } = default!; public string? Flavor;
/// <summary> /// <summary>
/// There must be at least this much quantity in a solution to be tasted. /// There must be at least this much quantity in a solution to be tasted.

View File

@@ -1,6 +1,6 @@
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.Nutrition; namespace Content.Shared.Nutrition;
[Prototype("flavor")] [Prototype("flavor")]
public sealed class FlavorPrototype : IPrototype public sealed class FlavorPrototype : IPrototype

View File

@@ -261,7 +261,7 @@
- type: FlavorProfile - type: FlavorProfile
flavors: flavors:
- meaty - meaty
- acidic - acid
- type: Sprite - type: Sprite
layers: layers:
- state: tin - state: tin
@@ -277,7 +277,7 @@
- type: FlavorProfile - type: FlavorProfile
flavors: flavors:
- meaty - meaty
- acidic - acid
- type: Sprite - type: Sprite
layers: layers:
- state: plate-small - state: plate-small

View File

@@ -797,7 +797,7 @@
flavors: flavors:
- bun - bun
- meaty - meaty
- acidic - acid
- type: Sprite - type: Sprite
state: x state: x
- type: SolutionContainerManager - type: SolutionContainerManager

View File

@@ -489,7 +489,7 @@
- type: FlavorProfile - type: FlavorProfile
flavors: flavors:
- meaty - meaty
- acidic - acid
- type: Tag - type: Tag
tags: tags:
- Raw - Raw
@@ -1080,7 +1080,7 @@
- type: FlavorProfile - type: FlavorProfile
flavors: flavors:
- meaty - meaty
- acidic - acid
- type: Tag - type: Tag
tags: tags:
- Raw - Raw

View File

@@ -35,6 +35,54 @@
components: components:
- type: SpaceVillainArcade - type: SpaceVillainArcade
rewardAmount: 0 rewardAmount: 0
possibleRewards:
- ToyMouse
- ToyAi
- ToyNuke
- ToyGriffin
- ToyHonk
- ToyIan
- ToyMarauder
- ToyMauler
- ToyGygax
- ToyOdysseus
- ToyOwlman
- ToyDeathRipley
- ToyPhazon
- ToyFireRipley
- ToyReticence
- ToyRipley
- ToySeraph
- ToyDurand
- ToySkeleton
- FoamCrossbow
- RevolverCapGun
- PlushieHampter
- PlushieLizard
- PlushieAtmosian
- PlushieSpaceLizard
- PlushieNuke
- PlushieCarp
- PlushieRatvar
- PlushieNar
- PlushieSnake
- Basketball
- Football
- PlushieRouny
- PlushieBee
- PlushieSlime
- BalloonCorgi
- ToySword
- CrayonBox
- BoxDonkSoftBox
- BoxCartridgeCap
- HarmonicaInstrument
- OcarinaInstrument
- RecorderInstrument
- GunpetInstrument
- BirdToyInstrument
- PlushieXeno
- BeachBall
- type: WiresPanel - type: WiresPanel
- type: Wires - type: Wires
LayoutId: Arcade LayoutId: Arcade

View File

@@ -73,7 +73,8 @@
group: Foods group: Foods
desc: reagent-desc-ketchunaise desc: reagent-desc-ketchunaise
physicalDesc: reagent-physical-desc-saucey physicalDesc: reagent-physical-desc-saucey
flavor: piquant #I love it when people just make up fake prototypes.
#flavor: piquant
color: "#fba399" color: "#fba399"
recognizable: true recognizable: true

View File

@@ -100,7 +100,7 @@
group: Toxins group: Toxins
desc: reagent-desc-polytrinic-acid desc: reagent-desc-polytrinic-acid
physicalDesc: reagent-physical-desc-strong-smelling physicalDesc: reagent-physical-desc-strong-smelling
flavor: acidic flavor: acid
color: "#a1000b" color: "#a1000b"
boilingPoint: 78.2 # This isn't a real chemical... boilingPoint: 78.2 # This isn't a real chemical...
meltingPoint: -19.4 meltingPoint: -19.4
@@ -143,7 +143,7 @@
group: Toxins group: Toxins
desc: reagent-desc-fluorosulfuric-acid desc: reagent-desc-fluorosulfuric-acid
physicalDesc: reagent-physical-desc-strong-smelling physicalDesc: reagent-physical-desc-strong-smelling
flavor: acidic flavor: acid
color: "#5050ff" color: "#5050ff"
boilingPoint: 165 boilingPoint: 165
meltingPoint: -87 meltingPoint: -87
@@ -179,7 +179,7 @@
group: Toxins group: Toxins
desc: reagent-desc-sulfuric-acid desc: reagent-desc-sulfuric-acid
physicalDesc: reagent-physical-desc-oily physicalDesc: reagent-physical-desc-oily
flavor: acidic flavor: acid
color: "#BF8C00" color: "#BF8C00"
recognizable: true recognizable: true
boilingPoint: 337.0 boilingPoint: 337.0