Add tests for FieldLoader and FieldSaver
Test coverage for these classes will help prevent regressions. Major breaking changes are avoided since config files may already rely on various aspects of the behaviour, but some small breaking changes are made: - Use `value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)` consistently for all comma-separated parsing. - Rename LoadField to LoadFieldOrProperty for clarity - since this is the one method that cares about properties in a class otherwise focused only on fields. - Use TryParse instead of Parse and ensure to call InvalidValueAction from all parsers so we throw the intended exception when badly formatted data is encountered. - BooleanExpression/IntegerExpression exception message updated to align with the generic one. - Remove FromYamlKey, and update the only user SkirmishLogic to perform this logic manually instead. - Remove unused includePrivateByDefault parameter on GetTypeLoadInfo. - Remove unused AllowEmptyEntriesAttribute.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
7d0340ad41
commit
bc1a901f54
22
OpenRA.Game/DescAttribute.cs
Normal file
22
OpenRA.Game/DescAttribute.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
// Mirrors DescriptionAttribute from System.ComponentModel but we don't want to have to use that everywhere.
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public sealed class DescAttribute(params string[] lines) : Attribute
|
||||
{
|
||||
public readonly string[] Lines = lines;
|
||||
}
|
||||
}
|
||||
@@ -548,7 +548,7 @@ namespace OpenRA
|
||||
|
||||
public static bool TryParseFloatOrPercentInvariant(string s, out float f)
|
||||
{
|
||||
if (float.TryParse(s.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out f))
|
||||
if (float.TryParse(s?.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out f))
|
||||
{
|
||||
f *= s.Contains('%') ? 0.01f : 1f;
|
||||
return true;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA
|
||||
{
|
||||
public static class FieldLoader
|
||||
{
|
||||
const char SplitComma = ',';
|
||||
const char Comma = ',';
|
||||
|
||||
public class MissingFieldsException : YamlException
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
|
||||
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}` ");
|
||||
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}`");
|
||||
|
||||
public static Action<string, Type> UnknownFieldAction = (s, f) =>
|
||||
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`");
|
||||
@@ -59,7 +59,7 @@ namespace OpenRA
|
||||
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
|
||||
new(expression => new IntegerExpression(expression));
|
||||
|
||||
static readonly Dictionary<Type, Func<string, Type, string, MemberInfo, object>> TypeParsers =
|
||||
static readonly Dictionary<Type, Func<string, Type, string, object>> TypeParsers =
|
||||
new()
|
||||
{
|
||||
{ typeof(int), ParseInt },
|
||||
@@ -83,7 +83,6 @@ namespace OpenRA
|
||||
{ typeof(CVec[]), ParseCVecArray },
|
||||
{ typeof(BooleanExpression), ParseBooleanExpression },
|
||||
{ typeof(IntegerExpression), ParseIntegerExpression },
|
||||
{ typeof(Enum), ParseEnum },
|
||||
{ typeof(bool), ParseBool },
|
||||
{ typeof(int2[]), ParseInt2Array },
|
||||
{ typeof(Size), ParseSize },
|
||||
@@ -94,7 +93,7 @@ namespace OpenRA
|
||||
{ typeof(DateTime), ParseDateTime }
|
||||
};
|
||||
|
||||
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>> GenericTypeParsers =
|
||||
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, object>> GenericTypeParsers =
|
||||
new()
|
||||
{
|
||||
{ typeof(HashSet<>), ParseHashSetOrList },
|
||||
@@ -108,7 +107,7 @@ namespace OpenRA
|
||||
static readonly object BoxedFalse = false;
|
||||
static readonly object[] BoxedInts = Exts.MakeArray(33, i => (object)i);
|
||||
|
||||
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseInt(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||
{
|
||||
@@ -120,48 +119,48 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseUShort(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseUShort(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
if (Exts.TryParseUshortInvariant(value, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseLong(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseLong(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
if (Exts.TryParseInt64Invariant(value, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseFloat(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseFloat(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01f : 1f);
|
||||
if (Exts.TryParseFloatOrPercentInvariant(value, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseDecimal(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseDecimal(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01m : 1m);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseString(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseString(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
static object ParseColor(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseColor(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
if (Color.TryParse(value, out var color))
|
||||
return color;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseHotkey(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseHotkey(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (Hotkey.TryParse(value, out var res))
|
||||
return res;
|
||||
@@ -169,12 +168,12 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseHotkeyReference(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseHotkeyReference(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
return Game.ModData.Hotkeys[value];
|
||||
}
|
||||
|
||||
static object ParseWDist(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseWDist(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (WDist.TryParse(value, out var res))
|
||||
return res;
|
||||
@@ -182,11 +181,11 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWVec(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseWVec(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 3
|
||||
&& WDist.TryParse(parts[0], out var rx)
|
||||
&& WDist.TryParse(parts[1], out var ry)
|
||||
@@ -197,11 +196,11 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseWVecArray(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
if (parts.Length % 3 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -214,6 +213,8 @@ namespace OpenRA
|
||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
vecs[i] = new WVec(rx, ry, rz);
|
||||
else
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
@@ -222,11 +223,11 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWPos(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseWPos(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 3
|
||||
&& WDist.TryParse(parts[0], out var rx)
|
||||
&& WDist.TryParse(parts[1], out var ry)
|
||||
@@ -237,18 +238,18 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseWAngle(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||
return new WAngle(res);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWRot(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseWRot(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 3
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
|
||||
@@ -259,27 +260,31 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCPos(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseCPos(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
return new CPos(
|
||||
Exts.ParseInt32Invariant(parts[0]),
|
||||
Exts.ParseInt32Invariant(parts[1]),
|
||||
Exts.ParseByteInvariant(parts[2]));
|
||||
return new CPos(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 3
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var y)
|
||||
&& Exts.TryParseByteInvariant(parts[2], out var layer))
|
||||
return new CPos(x, y, layer);
|
||||
|
||||
if (parts.Length == 2
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out x)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out y))
|
||||
return new CPos(x, y);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCPosArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseCPosArray(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -287,9 +292,11 @@ namespace OpenRA
|
||||
var vecs = new CPos[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[2 * i], out var rx)
|
||||
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||
if (Exts.TryParseInt32Invariant(parts[2 * i], out var rx)
|
||||
&& Exts.TryParseInt32Invariant(parts[2 * i + 1], out var ry))
|
||||
vecs[i] = new CPos(rx, ry);
|
||||
else
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
@@ -298,22 +305,25 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCVec(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseCVec(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new CVec(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 2
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var y))
|
||||
return new CVec(x, y);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseCVecArray(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -321,9 +331,11 @@ namespace OpenRA
|
||||
var vecs = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[2 * i], out var rx)
|
||||
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||
if (Exts.TryParseInt32Invariant(parts[2 * i], out var rx)
|
||||
&& Exts.TryParseInt32Invariant(parts[2 * i + 1], out var ry))
|
||||
vecs[i] = new CVec(rx, ry);
|
||||
else
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
@@ -332,7 +344,7 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseBooleanExpression(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseBooleanExpression(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
@@ -342,14 +354,14 @@ namespace OpenRA
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
throw new YamlException($"FieldLoader: Cannot parse `{value}` into `{fieldName}.{fieldType}`: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseIntegerExpression(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseIntegerExpression(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
@@ -359,44 +371,50 @@ namespace OpenRA
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
throw new YamlException($"FieldLoader: Cannot parse `{value}` into `{fieldName}.{fieldType}`: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseEnum(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseEnum(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
try
|
||||
// Will allow numeric values that fit the underlying type of the enum, even if they aren't defined enumeration members.
|
||||
if (Enum.TryParse(fieldType, value, true, out var enumValue))
|
||||
{
|
||||
return Enum.Parse(fieldType, value, true);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseBool(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||
if (bool.TryParse(value, out var result))
|
||||
return result ? BoxedTrue : BoxedFalse;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseInt2Array(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseInt2Array(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var ints = new int2[parts.Length / 2];
|
||||
|
||||
for (var i = 0; i < ints.Length; i++)
|
||||
ints[i] = new int2(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
|
||||
{
|
||||
if (Exts.TryParseInt32Invariant(parts[2 * i], out var x)
|
||||
&& Exts.TryParseInt32Invariant(parts[2 * i + 1], out var y))
|
||||
ints[i] = new int2(x, y);
|
||||
else
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return ints;
|
||||
}
|
||||
@@ -404,109 +422,112 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseSize(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseSize(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Size(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 2
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var width)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var height))
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseInt2(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseInt2(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
return new int2(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 2
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var y))
|
||||
return new int2(x, y);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseFloat2(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseFloat2(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
float xx = 0;
|
||||
float yy = 0;
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
|
||||
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
|
||||
return new float2(xx, yy);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 2
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[0], out var x)
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[1], out var y))
|
||||
return new float2(x, y);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseFloat3(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseFloat3(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
|
||||
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 3
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[0], out var x)
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[1], out var y)
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[2], out var z))
|
||||
return new float3(x, y, z);
|
||||
|
||||
// z component is optional for compatibility with older float2 definitions
|
||||
float z = 0;
|
||||
if (parts.Length > 2)
|
||||
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
|
||||
|
||||
return new float3(x, y, z);
|
||||
if (parts.Length == 2
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[0], out x)
|
||||
&& Exts.TryParseFloatOrPercentInvariant(parts[1], out y))
|
||||
return new float3(x, y, 0);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseRectangle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseRectangle(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Rectangle(
|
||||
Exts.ParseInt32Invariant(parts[0]),
|
||||
Exts.ParseInt32Invariant(parts[1]),
|
||||
Exts.ParseInt32Invariant(parts[2]),
|
||||
Exts.ParseInt32Invariant(parts[3]));
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 4
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var y)
|
||||
&& Exts.TryParseInt32Invariant(parts[2], out var width)
|
||||
&& Exts.TryParseInt32Invariant(parts[3], out var height))
|
||||
return new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseDateTime(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object ParseDateTime(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
||||
return dt;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
||||
{
|
||||
if (value == null)
|
||||
return Activator.CreateInstance(fieldType);
|
||||
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var set = Activator.CreateInstance(fieldType, parts.Length);
|
||||
var arguments = fieldType.GetGenericArguments();
|
||||
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
|
||||
var addArgs = new object[1];
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
addArgs[0] = GetValue(fieldName, arguments[0], parts[i].Trim(), field);
|
||||
addArgs[0] = GetValue(fieldName, arguments[0], parts[i]);
|
||||
addMethod.Invoke(set, addArgs);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
||||
{
|
||||
if (yaml == null)
|
||||
return Activator.CreateInstance(fieldType);
|
||||
@@ -517,33 +538,36 @@ namespace OpenRA
|
||||
var addArgs = new object[2];
|
||||
foreach (var node in yaml.Nodes)
|
||||
{
|
||||
addArgs[0] = GetValue(fieldName, arguments[0], node.Key, field);
|
||||
addArgs[1] = GetValue(fieldName, arguments[1], node.Value, field);
|
||||
addArgs[0] = GetValue(fieldName, arguments[0], node.Key);
|
||||
addArgs[1] = GetValue(fieldName, arguments[1], node.Value);
|
||||
addMethod.Invoke(dict, addArgs);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var ctor = fieldType.GetConstructor([typeof(string[])]);
|
||||
return ctor.Invoke([parts.Select(p => p.Trim()).ToArray()]);
|
||||
return ctor.Invoke([parts]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ctor = fieldType.GetConstructor([typeof(string[])]);
|
||||
return ctor.Invoke([Array.Empty<string>()]);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var innerType = fieldType.GetGenericArguments()[0];
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value);
|
||||
return fieldType.GetConstructor([innerType]).Invoke([innerValue]);
|
||||
}
|
||||
|
||||
@@ -598,7 +622,7 @@ namespace OpenRA
|
||||
if (!md.TryGetValue(yamlName, out var yaml))
|
||||
return false;
|
||||
|
||||
ret = GetValue(field.Name, field.FieldType, yaml, field);
|
||||
ret = GetValue(field.Name, field.FieldType, yaml);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -609,7 +633,7 @@ namespace OpenRA
|
||||
return t;
|
||||
}
|
||||
|
||||
public static void LoadField(object target, string key, string value)
|
||||
public static void LoadFieldOrProperty(object target, string key, string value)
|
||||
{
|
||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
@@ -618,18 +642,14 @@ namespace OpenRA
|
||||
var field = target.GetType().GetField(key, Flags);
|
||||
if (field != null)
|
||||
{
|
||||
var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
|
||||
if (!sa.FromYamlKey)
|
||||
field.SetValue(target, GetValue(field.Name, field.FieldType, value, field));
|
||||
field.SetValue(target, GetValue(field.Name, field.FieldType, value));
|
||||
return;
|
||||
}
|
||||
|
||||
var prop = target.GetType().GetProperty(key, Flags);
|
||||
if (prop != null)
|
||||
{
|
||||
var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
|
||||
if (!sa.FromYamlKey)
|
||||
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), null);
|
||||
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value), null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -641,48 +661,44 @@ namespace OpenRA
|
||||
return (T)GetValue(field, typeof(T), value, null);
|
||||
}
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, string value)
|
||||
static object GetValue(string fieldName, Type fieldType, string value)
|
||||
{
|
||||
return GetValue(fieldName, fieldType, value, null);
|
||||
}
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
static object GetValue(string fieldName, Type fieldType, MiniYaml yaml)
|
||||
{
|
||||
return GetValue(fieldName, fieldType, value, null, field);
|
||||
return GetValue(fieldName, fieldType, yaml.Value, yaml);
|
||||
}
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
return GetValue(fieldName, fieldType, yaml.Value, yaml, field);
|
||||
}
|
||||
|
||||
static object GetValue(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
static object GetValue(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
||||
{
|
||||
value = value?.Trim();
|
||||
if (fieldType.IsGenericType)
|
||||
{
|
||||
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
|
||||
return parseFuncGeneric(fieldName, fieldType, value, yaml, field);
|
||||
return parseFuncGeneric(fieldName, fieldType, value, yaml);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TypeParsers.TryGetValue(fieldType, out var parseFunc))
|
||||
return parseFunc(fieldName, fieldType, value, field);
|
||||
return parseFunc(fieldName, fieldType, value);
|
||||
|
||||
if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
|
||||
{
|
||||
if (value == null)
|
||||
return Array.CreateInstance(fieldType.GetElementType(), 0);
|
||||
|
||||
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
|
||||
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
|
||||
var parts = value.Split(SplitComma, options);
|
||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
|
||||
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i]), i);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fieldType.IsEnum)
|
||||
return ParseEnum(fieldName, fieldType, value);
|
||||
}
|
||||
|
||||
var conv = TypeDescriptor.GetConverter(fieldType);
|
||||
@@ -706,21 +722,20 @@ namespace OpenRA
|
||||
{
|
||||
public readonly FieldInfo Field;
|
||||
public readonly SerializeAttribute Attribute;
|
||||
public readonly string YamlName;
|
||||
public readonly Func<MiniYaml, object> Loader;
|
||||
public string YamlName => Field.Name;
|
||||
|
||||
internal FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func<MiniYaml, object> loader = null)
|
||||
public FieldLoadInfo(FieldInfo field, SerializeAttribute attr, Func<MiniYaml, object> loader = null)
|
||||
{
|
||||
Field = field;
|
||||
Attribute = attr;
|
||||
YamlName = yamlName;
|
||||
Loader = loader;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type, bool includePrivateByDefault = false)
|
||||
public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type)
|
||||
{
|
||||
return TypeLoadInfo[type].Where(fli => includePrivateByDefault || fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault));
|
||||
return TypeLoadInfo[type].Where(fli => fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault));
|
||||
}
|
||||
|
||||
static FieldLoadInfo[] BuildTypeLoadInfo(Type type)
|
||||
@@ -735,13 +750,9 @@ namespace OpenRA
|
||||
if (!sa.Serialize)
|
||||
continue;
|
||||
|
||||
var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName;
|
||||
|
||||
var loader = sa.GetLoader(type);
|
||||
if (loader == null && sa.FromYamlKey)
|
||||
loader = yaml => GetValue(yamlName, field.FieldType, yaml, field);
|
||||
|
||||
var fli = new FieldLoadInfo(field, sa, yamlName, loader);
|
||||
var fli = new FieldLoadInfo(field, sa, loader);
|
||||
ret.Add(fli);
|
||||
}
|
||||
|
||||
@@ -752,31 +763,21 @@ namespace OpenRA
|
||||
public sealed class IgnoreAttribute : SerializeAttribute
|
||||
{
|
||||
public IgnoreAttribute()
|
||||
: base(false) { }
|
||||
: base(serialize: false) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class RequireAttribute : SerializeAttribute
|
||||
{
|
||||
public RequireAttribute()
|
||||
: base(true, true) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class AllowEmptyEntriesAttribute : SerializeAttribute
|
||||
{
|
||||
public AllowEmptyEntriesAttribute()
|
||||
: base(allowEmptyEntries: true) { }
|
||||
: base(serialize: true, required: true) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class LoadUsingAttribute : SerializeAttribute
|
||||
{
|
||||
public LoadUsingAttribute(string loader, bool required = false)
|
||||
{
|
||||
Loader = loader;
|
||||
Required = required;
|
||||
}
|
||||
: base(serialize: true, required, loader) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
@@ -787,17 +788,14 @@ namespace OpenRA
|
||||
public bool IsDefault => this == Default;
|
||||
|
||||
public readonly bool Serialize;
|
||||
public string YamlName;
|
||||
public string Loader;
|
||||
public bool FromYamlKey;
|
||||
public bool Required;
|
||||
public bool AllowEmptyEntries;
|
||||
public readonly bool Required;
|
||||
public readonly string Loader;
|
||||
|
||||
public SerializeAttribute(bool serialize = true, bool required = false, bool allowEmptyEntries = false)
|
||||
protected SerializeAttribute(bool serialize = true, bool required = false, string loader = null)
|
||||
{
|
||||
Serialize = serialize;
|
||||
Required = required;
|
||||
AllowEmptyEntries = allowEmptyEntries;
|
||||
Loader = loader;
|
||||
}
|
||||
|
||||
internal Func<MiniYaml, object> GetLoader(Type type)
|
||||
@@ -817,20 +815,4 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
|
||||
{
|
||||
public FieldFromYamlKeyAttribute()
|
||||
{
|
||||
FromYamlKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors DescriptionAttribute from System.ComponentModel but we don't want to have to use that everywhere.
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public sealed class DescAttribute(params string[] lines) : Attribute
|
||||
{
|
||||
public readonly string[] Lines = lines;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,11 @@ namespace OpenRA
|
||||
{
|
||||
public static class FieldSaver
|
||||
{
|
||||
public static MiniYaml Save(object o, bool includePrivateByDefault = false)
|
||||
public static MiniYaml Save(object o)
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
string root = null;
|
||||
|
||||
foreach (var fieldInfo in FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault))
|
||||
foreach (var fieldInfo in FieldLoader.GetTypeLoadInfo(o.GetType()))
|
||||
{
|
||||
if (fieldInfo.Field.FieldType.IsGenericType && fieldInfo.Field.FieldType.IsAssignableTo(typeof(System.Collections.IDictionary)))
|
||||
{
|
||||
@@ -42,21 +41,19 @@ namespace OpenRA
|
||||
|
||||
nodes.Add(new MiniYamlNode(fieldInfo.YamlName, "", dictNodes));
|
||||
}
|
||||
else if (fieldInfo.Attribute.FromYamlKey)
|
||||
root = FormatValue(o, fieldInfo.Field);
|
||||
else
|
||||
nodes.Add(new MiniYamlNode(fieldInfo.YamlName, FormatValue(o, fieldInfo.Field)));
|
||||
}
|
||||
|
||||
return new MiniYaml(root, nodes);
|
||||
return new MiniYaml(null, nodes);
|
||||
}
|
||||
|
||||
public static MiniYaml SaveDifferences(object o, object from, bool includePrivateByDefault = false)
|
||||
public static MiniYaml SaveDifferences(object o, object from)
|
||||
{
|
||||
if (o.GetType() != from.GetType())
|
||||
throw new InvalidOperationException("FieldLoader: can't diff objects of different types");
|
||||
throw new InvalidOperationException("FieldSaver: can't diff objects of different types");
|
||||
|
||||
var fields = FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault)
|
||||
var fields = FieldLoader.GetTypeLoadInfo(o.GetType())
|
||||
.Where(info => FormatValue(o, info.Field) != FormatValue(from, info.Field));
|
||||
|
||||
return new MiniYaml(
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace OpenRA
|
||||
else if (type == Type.MiniYaml)
|
||||
field.SetValue(map, node.Value);
|
||||
else
|
||||
FieldLoader.LoadField(map, fieldName, node.Value.Value);
|
||||
FieldLoader.LoadFieldOrProperty(map, fieldName, node.Value.Value);
|
||||
}
|
||||
|
||||
if (property != null)
|
||||
@@ -121,7 +121,7 @@ namespace OpenRA
|
||||
else if (type == Type.MiniYaml)
|
||||
property.SetValue(map, node.Value, null);
|
||||
else
|
||||
FieldLoader.LoadField(map, fieldName, node.Value.Value);
|
||||
FieldLoader.LoadFieldOrProperty(map, fieldName, node.Value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,9 @@ namespace OpenRA.Primitives
|
||||
public static bool TryParse(string value, out Color color)
|
||||
{
|
||||
color = default;
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
value = value.Trim();
|
||||
if (value.Length != 6 && value.Length != 8)
|
||||
return false;
|
||||
|
||||
@@ -369,7 +369,7 @@ namespace OpenRA
|
||||
foreach (var kv in Sections)
|
||||
foreach (var f in kv.Value.GetType().GetFields())
|
||||
if (args.Contains(kv.Key + "." + f.Name))
|
||||
FieldLoader.LoadField(kv.Value, f.Name, args.GetValue(kv.Key + "." + f.Name, ""));
|
||||
FieldLoader.LoadFieldOrProperty(kv.Value, f.Name, args.GetValue(kv.Key + "." + f.Name, ""));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace OpenRA
|
||||
|
||||
foreach (var f in GetType().GetFields())
|
||||
if (args.Contains("Launch." + f.Name))
|
||||
FieldLoader.LoadField(this, f.Name, args.GetValue("Launch." + f.Name, ""));
|
||||
FieldLoader.LoadFieldOrProperty(this, f.Name, args.GetValue("Launch." + f.Name, ""));
|
||||
}
|
||||
|
||||
public ConnectionTarget GetConnectEndPoint()
|
||||
|
||||
@@ -581,6 +581,8 @@ namespace OpenRA.Support
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public override string ToString() => Expression;
|
||||
|
||||
Expression Build(ExpressionType resultType)
|
||||
{
|
||||
var tokens = new List<Token>();
|
||||
|
||||
@@ -55,11 +55,11 @@ namespace OpenRA
|
||||
parent?.AddChild(widget);
|
||||
|
||||
if (node.Key.Contains('@'))
|
||||
FieldLoader.LoadField(widget, "Id", node.Key.Split('@')[1]);
|
||||
FieldLoader.LoadFieldOrProperty(widget, "Id", node.Key.Split('@')[1]);
|
||||
|
||||
foreach (var child in node.Value.Nodes)
|
||||
if (child.Key != "Children")
|
||||
FieldLoader.LoadField(widget, child.Key, child.Value.Value);
|
||||
FieldLoader.LoadFieldOrProperty(widget, child.Key, child.Value.Value);
|
||||
|
||||
widget.Initialize(args);
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace OpenRA.Mods.Common.Server
|
||||
{
|
||||
sealed class SkirmishSlot
|
||||
{
|
||||
[FieldLoader.Serialize(FromYamlKey = true)]
|
||||
static string LoadSlot(MiniYaml yaml) => yaml.Value;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadSlot))]
|
||||
public readonly string Slot;
|
||||
public readonly Color Color;
|
||||
public readonly string Faction;
|
||||
@@ -54,6 +56,12 @@ namespace OpenRA.Mods.Common.Server
|
||||
c.Team = s.Team;
|
||||
c.Handicap = s.Handicap;
|
||||
}
|
||||
|
||||
public MiniYaml ToYaml()
|
||||
{
|
||||
var yaml = FieldSaver.Save(this);
|
||||
return yaml.WithValue(Slot).WithNodes(yaml.Nodes.RemoveAll(n => n.Key == nameof(Slot)));
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryInitializeFromFile(S server, string path, Connection conn)
|
||||
@@ -156,9 +164,9 @@ namespace OpenRA.Mods.Common.Server
|
||||
new("Map", server.LobbyInfo.GlobalSettings.Map),
|
||||
new("Options", new MiniYaml("", server.LobbyInfo.GlobalSettings.LobbyOptions
|
||||
.Select(kv => new MiniYamlNode(kv.Key, kv.Value.Value)))),
|
||||
new("Player", FieldSaver.Save(new SkirmishSlot(playerClient))),
|
||||
new("Player", new SkirmishSlot(playerClient).ToYaml()),
|
||||
new("Bots", new MiniYaml("", server.LobbyInfo.Clients.Where(c => c.IsBot)
|
||||
.Select(b => new MiniYamlNode(b.Bot, FieldSaver.Save(new SkirmishSlot(b))))))
|
||||
.Select(b => new MiniYamlNode(b.Bot, new SkirmishSlot(b).ToYaml()))))
|
||||
}.WriteToFile(path);
|
||||
}
|
||||
|
||||
|
||||
719
OpenRA.Test/OpenRA.Game/FieldLoaderTest.cs
Normal file
719
OpenRA.Test/OpenRA.Game/FieldLoaderTest.cs
Normal file
@@ -0,0 +1,719 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Internal;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Test
|
||||
{
|
||||
[TestFixture]
|
||||
sealed class FieldLoaderTest
|
||||
{
|
||||
sealed class TypeInfo
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable CA1823 // Avoid unused private fields
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
#pragma warning disable IDE0051 // Remove unused private members
|
||||
#pragma warning disable RCS1170 // Use read-only auto-implemented property
|
||||
int privateField;
|
||||
public int PublicField;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
int privateIgnoreField;
|
||||
[FieldLoader.Ignore]
|
||||
public int PublicIgnoreField;
|
||||
|
||||
[FieldLoader.Require]
|
||||
int privateRequiredField;
|
||||
[FieldLoader.Require]
|
||||
public int PublicRequiredField;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadInt32))]
|
||||
int privateLoadUsingField;
|
||||
[FieldLoader.LoadUsing(nameof(LoadInt32))]
|
||||
public int PublicLoadUsingField;
|
||||
|
||||
int PrivateProperty { get; set; }
|
||||
public int PublicProperty { get; set; }
|
||||
|
||||
static int privateStaticField;
|
||||
public static int PublicStaticField;
|
||||
|
||||
static object LoadInt32(MiniYaml _) => 123;
|
||||
#pragma warning restore RCS1170 // Use read-only auto-implemented property
|
||||
#pragma warning restore IDE0051 // Remove unused private members
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
#pragma warning restore CA1823 // Avoid unused private fields
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning restore CS0169
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTypeLoadInfo()
|
||||
{
|
||||
var infos = FieldLoader.GetTypeLoadInfo(typeof(TypeInfo)).ToList();
|
||||
|
||||
Assert.That(infos, Has.Count.EqualTo(5));
|
||||
|
||||
Assert.That(infos[0].Field, Is.EqualTo(typeof(TypeInfo).GetField(nameof(TypeInfo.PublicField))));
|
||||
Assert.That(infos[0].Attribute, Is.EqualTo(FieldLoader.SerializeAttribute.Default));
|
||||
Assert.That(infos[0].YamlName, Is.EqualTo(nameof(TypeInfo.PublicField)));
|
||||
Assert.That(infos[0].Loader, Is.Null);
|
||||
|
||||
Assert.That(infos[1].Field, Is.EqualTo(typeof(TypeInfo).GetField("privateRequiredField", BindingFlags.NonPublic | BindingFlags.Instance)));
|
||||
Assert.That(infos[1].Attribute, Is.EqualTo(new FieldLoader.RequireAttribute()));
|
||||
Assert.That(infos[1].YamlName, Is.EqualTo("privateRequiredField"));
|
||||
Assert.That(infos[1].Loader, Is.Null);
|
||||
|
||||
Assert.That(infos[2].Field, Is.EqualTo(typeof(TypeInfo).GetField(nameof(TypeInfo.PublicRequiredField))));
|
||||
Assert.That(infos[2].Attribute, Is.EqualTo(new FieldLoader.RequireAttribute()));
|
||||
Assert.That(infos[2].YamlName, Is.EqualTo(nameof(TypeInfo.PublicRequiredField)));
|
||||
Assert.That(infos[2].Loader, Is.Null);
|
||||
|
||||
Assert.That(infos[3].Field, Is.EqualTo(typeof(TypeInfo).GetField("privateLoadUsingField", BindingFlags.NonPublic | BindingFlags.Instance)));
|
||||
Assert.That(infos[3].Attribute, Is.EqualTo(new FieldLoader.LoadUsingAttribute("LoadInt32")));
|
||||
Assert.That(infos[3].YamlName, Is.EqualTo("privateLoadUsingField"));
|
||||
Assert.That(infos[3].Loader(new MiniYaml(null)), Is.EqualTo(123));
|
||||
|
||||
Assert.That(infos[4].Field, Is.EqualTo(typeof(TypeInfo).GetField(nameof(TypeInfo.PublicLoadUsingField))));
|
||||
Assert.That(infos[4].Attribute, Is.EqualTo(new FieldLoader.LoadUsingAttribute("LoadInt32")));
|
||||
Assert.That(infos[4].YamlName, Is.EqualTo(nameof(TypeInfo.PublicLoadUsingField)));
|
||||
Assert.That(infos[4].Loader(new MiniYaml(null)), Is.EqualTo(123));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_UnknownField()
|
||||
{
|
||||
static void Act() => FieldLoader.GetValue<object>("field", "test");
|
||||
|
||||
Assert.That(Act, Throws.TypeOf<NotImplementedException>().And.Message.EqualTo("FieldLoader: Missing field `[Type] test` on `Object`"));
|
||||
}
|
||||
|
||||
static IEnumerable<TestCaseData> GetValue_InvalidValue_TestCases()
|
||||
{
|
||||
return
|
||||
[
|
||||
new TestCaseData(null) { TypeArgs = [typeof(int)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(int)] },
|
||||
new TestCaseData("1.2") { TypeArgs = [typeof(int)] },
|
||||
new TestCaseData((int.MaxValue + 1L).ToString(CultureInfo.InvariantCulture)) { TypeArgs = [typeof(int)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(ushort)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(ushort)] },
|
||||
new TestCaseData("1.2") { TypeArgs = [typeof(ushort)] },
|
||||
new TestCaseData((ushort.MaxValue + 1L).ToString(CultureInfo.InvariantCulture)) { TypeArgs = [typeof(ushort)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(long)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(long)] },
|
||||
new TestCaseData("1.2") { TypeArgs = [typeof(long)] },
|
||||
new TestCaseData((long.MaxValue + 1UL).ToString(CultureInfo.InvariantCulture)) { TypeArgs = [typeof(long)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(float)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(float)] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(float)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(decimal)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(decimal)] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(decimal)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(Color)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(Color)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(Hotkey)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(Hotkey)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(WDist)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(WDist)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(WVec)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(WVec)] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(WVec)] },
|
||||
new TestCaseData("1,test,3") { TypeArgs = [typeof(WVec)] },
|
||||
new TestCaseData("1,2,3,4") { TypeArgs = [typeof(WVec)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(WVec[])] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(WVec[])] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(WVec[])] },
|
||||
new TestCaseData("1,test,3") { TypeArgs = [typeof(WVec[])] },
|
||||
new TestCaseData("1,2,3,4") { TypeArgs = [typeof(WVec[])] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(WPos)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(WPos)] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(WPos)] },
|
||||
new TestCaseData("1,test,3") { TypeArgs = [typeof(WPos)] },
|
||||
new TestCaseData("1,2,3,4") { TypeArgs = [typeof(WPos)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(WAngle)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(WAngle)] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(WAngle)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(WRot)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(WRot)] },
|
||||
new TestCaseData("1,2") { TypeArgs = [typeof(WRot)] },
|
||||
new TestCaseData("1,test,3") { TypeArgs = [typeof(WRot)] },
|
||||
new TestCaseData("1,2,3,4") { TypeArgs = [typeof(WRot)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(CPos)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(CPos)] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(CPos)] },
|
||||
new TestCaseData("1,test,3") { TypeArgs = [typeof(CPos)] },
|
||||
new TestCaseData("1,2,3,4") { TypeArgs = [typeof(CPos)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(CPos[])] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(CPos[])] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(CPos[])] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(CPos[])] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(CPos[])] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(CVec)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(CVec)] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(CVec)] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(CVec)] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(CVec)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(CVec[])] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(CVec[])] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(CVec[])] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(CVec[])] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(CVec[])] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(BooleanExpression)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(IntegerExpression)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(MapGridType)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(bool)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(bool)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(int2[])] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(int2[])] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(int2[])] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(int2[])] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(int2[])] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(Size)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(Size)] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(Size)] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(Size)] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(Size)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(int2)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(int2)] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(int2)] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(int2)] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(int2)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(float2)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(float2)] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(float2)] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(float2)] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(float2)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(float3)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(float3)] },
|
||||
new TestCaseData("1") { TypeArgs = [typeof(float3)] },
|
||||
new TestCaseData("1,test") { TypeArgs = [typeof(float3)] },
|
||||
new TestCaseData("1,2,3,4") { TypeArgs = [typeof(float3)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(Rectangle)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(Rectangle)] },
|
||||
new TestCaseData("1,2,3") { TypeArgs = [typeof(Rectangle)] },
|
||||
new TestCaseData("1,test,3,4") { TypeArgs = [typeof(Rectangle)] },
|
||||
new TestCaseData("1,2,3,4,5") { TypeArgs = [typeof(Rectangle)] },
|
||||
new TestCaseData(null) { TypeArgs = [typeof(DateTime)] },
|
||||
new TestCaseData("test") { TypeArgs = [typeof(DateTime)] },
|
||||
new TestCaseData("2000-01-01") { TypeArgs = [typeof(DateTime)] },
|
||||
];
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetValue_InvalidValue_TestCases))]
|
||||
public void GetValue_InvalidValue<T>(string input)
|
||||
{
|
||||
void Act() => FieldLoader.GetValue<T>("field", input);
|
||||
|
||||
Assert.That(Act, Throws.TypeOf<YamlException>().And.Message.EqualTo($"FieldLoader: Cannot parse `{input}` into `field.{typeof(T).FullName}`"));
|
||||
}
|
||||
|
||||
[TestCase(TypeArgs = [typeof(BooleanExpression)])]
|
||||
[TestCase(TypeArgs = [typeof(IntegerExpression)])]
|
||||
public void GetValue_InvalidValue<T>()
|
||||
{
|
||||
static void Act() => FieldLoader.GetValue<T>("field", "");
|
||||
|
||||
Assert.That(Act, Throws.TypeOf<YamlException>().And.Message.EqualTo($"FieldLoader: Cannot parse `` into `field.{typeof(T).FullName}`: Empty expression"));
|
||||
}
|
||||
|
||||
static IEnumerable<TestCaseData> GetValue_Primitive_TestCases()
|
||||
{
|
||||
return
|
||||
[
|
||||
new TestCaseData(123),
|
||||
new TestCaseData((ushort)123),
|
||||
new TestCaseData(123L),
|
||||
new TestCaseData(123.4f),
|
||||
new TestCaseData(123m),
|
||||
new TestCaseData("test"),
|
||||
new TestCaseData(Color.CornflowerBlue),
|
||||
new TestCaseData(new Hotkey(Keycode.A, Modifiers.Shift)),
|
||||
new TestCaseData(new WDist(123)),
|
||||
new TestCaseData(new WVec(123, 456, 789)),
|
||||
new TestCaseData(new WPos(123, 456, 789)),
|
||||
new TestCaseData(new WAngle(123)),
|
||||
new TestCaseData(new WRot(new WAngle(123), new WAngle(456), new WAngle(789))),
|
||||
new TestCaseData(new CPos(123, 456)),
|
||||
new TestCaseData(new CPos(123, 456, 78)),
|
||||
new TestCaseData(new CVec(123, 456)),
|
||||
new TestCaseData(MapGridType.RectangularIsometric),
|
||||
new TestCaseData((MapGridType)byte.MaxValue),
|
||||
new TestCaseData(SystemActors.World | SystemActors.EditorWorld),
|
||||
new TestCaseData(true),
|
||||
new TestCaseData(new Size(123, 456)),
|
||||
new TestCaseData(new int2(123, 456)),
|
||||
new TestCaseData(new float2(123, 456)),
|
||||
new TestCaseData(new float3(123, 456, 789)),
|
||||
new TestCaseData(new Rectangle(123, 456, 789, 123)),
|
||||
];
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetValue_Primitive_TestCases))]
|
||||
public void GetValue_Primitive<T>(T expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<T>("field", $" {expected} ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_BooleanExpression()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<BooleanExpression>("field", " true ");
|
||||
|
||||
Assert.That(actual.Expression, Is.EqualTo("true"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_IntegerExpression()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<IntegerExpression>("field", " 1 + 2 ");
|
||||
|
||||
Assert.That(actual.Expression, Is.EqualTo("1 + 2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_DateTime()
|
||||
{
|
||||
var expected = new DateTime(2000, 1, 1);
|
||||
var input = expected.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
|
||||
var actual = FieldLoader.GetValue<DateTime>("field", $" {input} ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase("123%", 1.23f)]
|
||||
[TestCase("%123", 1.23f)]
|
||||
[TestCase("123.456%", 1.23456f)]
|
||||
[TestCase("%123.456", 1.23456f)]
|
||||
public void GetValue_FloatPercentage(string input, float expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<float>("field", $" {input} ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
static IEnumerable<TestCaseData> GetValue_DecimalPercentage_TestCases()
|
||||
{
|
||||
return
|
||||
[
|
||||
new TestCaseData("123%", 1.23m),
|
||||
new TestCaseData("%123", 1.23m),
|
||||
new TestCaseData("123.456%", 1.23456m),
|
||||
new TestCaseData("%123.456", 1.23456m),
|
||||
];
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetValue_DecimalPercentage_TestCases))]
|
||||
public void GetValue_DecimalPercentage(string input, decimal expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<decimal>("field", $" {input} ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_float3_TwoElements()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<float3>("field", "123,456");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(new float3(123, 456, 0)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_WVecArray()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<WVec[]>("field", " 1 , 2 , 3 , 4 , 5 , 6 , , ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(new WVec[] { new(1, 2, 3), new(4, 5, 6) }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_CPosArray()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<CPos[]>("field", " 1 , 2 , 3 , 4 , 5 , 6 , , ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(new CPos[] { new(1, 2), new(3, 4), new(5, 6) }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_CVecArray()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<CVec[]>("field", " 1 , 2 , 3 , 4 , 5 , 6 , , ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(new CVec[] { new(1, 2), new(3, 4), new(5, 6) }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_int2Array()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<int2[]>("field", " 1 , 2 , 3 , 4 , 5 , 6 , , ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(new int2[] { new(1, 2), new(3, 4), new(5, 6) }));
|
||||
}
|
||||
|
||||
[TestCase(null, null)]
|
||||
[TestCase("", null)]
|
||||
[TestCase("123", 123)]
|
||||
public void GetValue_Nullable(string input, int? expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<int?>("field", $" {input} ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null, new int[] { })]
|
||||
[TestCase("", new int[] { })]
|
||||
[TestCase("1", new int[] { 1 })]
|
||||
[TestCase("1,2,3", new int[] { 1, 2, 3 })]
|
||||
[TestCase("1,,3", new int[] { 1, 3 })]
|
||||
[TestCase(" 1 , 2 , 3 ", new int[] { 1, 2, 3 })]
|
||||
[TestCase(" 1 , , 3 ", new int[] { 1, 3 })]
|
||||
[TestCase("1,1,2,2,3", new int[] { 1, 1, 2, 2, 3 })]
|
||||
[TestCase("1,1,1,1", new int[] { 1, 1, 1, 1 })]
|
||||
public void GetValue_Array(string input, int[] expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<int[]>("field", input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null, new int[] { })]
|
||||
[TestCase("", new int[] { })]
|
||||
[TestCase("1", new int[] { 1 })]
|
||||
[TestCase("1,2,3", new int[] { 1, 2, 3 })]
|
||||
[TestCase("1,,3", new int[] { 1, 3 })]
|
||||
[TestCase(" 1 , 2 , 3 ", new int[] { 1, 2, 3 })]
|
||||
[TestCase(" 1 , , 3 ", new int[] { 1, 3 })]
|
||||
[TestCase("1,1,2,2,3", new int[] { 1, 1, 2, 2, 3 })]
|
||||
[TestCase("1,1,1,1", new int[] { 1, 1, 1, 1 })]
|
||||
public void GetValue_List(string input, int[] expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<List<int>>("field", input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null, new int[] { })]
|
||||
[TestCase("", new int[] { })]
|
||||
[TestCase("1", new int[] { 1 })]
|
||||
[TestCase("1,2,3", new int[] { 1, 2, 3 })]
|
||||
[TestCase("1,,3", new int[] { 1, 3 })]
|
||||
[TestCase(" 1 , 2 , 3 ", new int[] { 1, 2, 3 })]
|
||||
[TestCase(" 1 , , 3 ", new int[] { 1, 3 })]
|
||||
[TestCase("1,1,2,2,3", new int[] { 1, 2, 3 })]
|
||||
[TestCase("1,1,1,1", new int[] { 1 })]
|
||||
public void GetValue_HashSet(string input, int[] expected)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<HashSet<int>>("field", input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase("1")]
|
||||
[TestCase("1,2,3")]
|
||||
[TestCase("1,,3")]
|
||||
[TestCase(" 1 , 2 , 3 ")]
|
||||
[TestCase(" 1 , , 3 ")]
|
||||
[TestCase("1,1,2,2,3")]
|
||||
[TestCase("1,1,1,1")]
|
||||
public void GetValue_BitSet(string input)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<BitSet<FieldLoaderTest>>("field", input);
|
||||
var expected = input == null
|
||||
? new BitSet<FieldLoaderTest>([])
|
||||
: new BitSet<FieldLoaderTest>(input.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase("1")]
|
||||
[TestCase("1,2")]
|
||||
public void GetValue_Dictionary(string input)
|
||||
{
|
||||
var actual = FieldLoader.GetValue<Dictionary<int, int>>("field", input);
|
||||
|
||||
Assert.That(actual, Is.Empty);
|
||||
}
|
||||
|
||||
[TestCase(TypeArgs = [typeof(int[][])])]
|
||||
[TestCase(TypeArgs = [typeof(List<List<int>>)])]
|
||||
[TestCase(TypeArgs = [typeof(HashSet<HashSet<int>>)])]
|
||||
public void GetValue_NestedCollections<T>()
|
||||
{
|
||||
var actual = FieldLoader.GetValue<T>("field", "1,2,3");
|
||||
|
||||
Assert.That(actual, Is.EquivalentTo(new int[][] { [1], [2], [3] }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_TypeConverter()
|
||||
{
|
||||
// We don't have hardcoded handling for sbyte, but a TypeConverter for it does exist.
|
||||
var actual = FieldLoader.GetValue<sbyte>("field", " 123 ");
|
||||
|
||||
Assert.That(actual, Is.EqualTo(123));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_TypeConverter_Invalid()
|
||||
{
|
||||
static void Act() => FieldLoader.GetValue<sbyte>("field", " test ");
|
||||
|
||||
Assert.That(Act, Throws.TypeOf<YamlException>().And.Message.EqualTo($"FieldLoader: Cannot parse `test` into `field.{typeof(sbyte).FullName}`"));
|
||||
}
|
||||
|
||||
sealed class LoadFieldOrPropertyTarget
|
||||
{
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
#pragma warning disable RCS1170 // Use read-only auto-implemented property
|
||||
int privateIntField;
|
||||
int PrivateIntProp { get; set; }
|
||||
|
||||
public int PublicIntField;
|
||||
public int PublicIntProp { get; set; }
|
||||
|
||||
public int GetPrivateIntField() => privateIntField;
|
||||
public int GetPrivateIntProp() => PrivateIntProp;
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
#pragma warning restore RCS1170 // Use read-only auto-implemented property
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadFieldOrProperty()
|
||||
{
|
||||
var target = new LoadFieldOrPropertyTarget();
|
||||
FieldLoader.LoadFieldOrProperty(target, $" {nameof(LoadFieldOrPropertyTarget.PublicIntField)} ", "12");
|
||||
FieldLoader.LoadFieldOrProperty(target, $" {nameof(LoadFieldOrPropertyTarget.PublicIntProp)} ", "34");
|
||||
FieldLoader.LoadFieldOrProperty(target, " privateIntField ", "56");
|
||||
FieldLoader.LoadFieldOrProperty(target, " PrivateIntProp ", "78");
|
||||
void Act() => FieldLoader.LoadFieldOrProperty(target, "unknown", "");
|
||||
|
||||
Assert.That(target.PublicIntField, Is.EqualTo(12));
|
||||
Assert.That(target.PublicIntProp, Is.EqualTo(34));
|
||||
Assert.That(target.GetPrivateIntField(), Is.EqualTo(56));
|
||||
Assert.That(target.GetPrivateIntProp(), Is.EqualTo(78));
|
||||
Assert.That(Act, Throws.TypeOf<NotImplementedException>().And.Message.EqualTo("FieldLoader: Missing field `unknown` on `LoadFieldOrPropertyTarget`"));
|
||||
}
|
||||
|
||||
sealed class LoadTarget
|
||||
{
|
||||
public int Int;
|
||||
public string String;
|
||||
public string Unset;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load()
|
||||
{
|
||||
var target = new LoadTarget() { Unset = "unset" };
|
||||
var yaml = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(nameof(LoadTarget.Int), "123"),
|
||||
new MiniYamlNode(nameof(LoadTarget.String), "test"),
|
||||
]);
|
||||
|
||||
FieldLoader.Load(target, yaml);
|
||||
|
||||
Assert.That(target.Int, Is.EqualTo(123));
|
||||
Assert.That(target.String, Is.EqualTo("test"));
|
||||
Assert.That(target.Unset, Is.EqualTo("unset"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Generic()
|
||||
{
|
||||
var expected = new LoadTarget();
|
||||
var yaml = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(nameof(LoadTarget.Int), "123"),
|
||||
new MiniYamlNode(nameof(LoadTarget.String), "test"),
|
||||
]);
|
||||
FieldLoader.Load(expected, yaml);
|
||||
|
||||
var actual = FieldLoader.Load<LoadTarget>(yaml);
|
||||
|
||||
Assert.That(actual.Int, Is.EqualTo(expected.Int));
|
||||
Assert.That(actual.String, Is.EqualTo(expected.String));
|
||||
Assert.That(actual.Unset, Is.EqualTo(expected.Unset));
|
||||
}
|
||||
|
||||
sealed class LoadDictionaryTarget
|
||||
{
|
||||
public Dictionary<int, int> Dictionary;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Dictionary()
|
||||
{
|
||||
var target = new LoadDictionaryTarget();
|
||||
var yaml = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(
|
||||
nameof(LoadDictionaryTarget.Dictionary),
|
||||
new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode("12", "34"),
|
||||
new MiniYamlNode("56", "78")
|
||||
]))
|
||||
]);
|
||||
|
||||
FieldLoader.Load(target, yaml);
|
||||
|
||||
Assert.That(target.Dictionary, Is.EquivalentTo(new Dictionary<int, int> { { 12, 34 }, { 56, 78 } }));
|
||||
}
|
||||
|
||||
sealed class LoadRequiredTarget
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
public int Int1 = 1;
|
||||
[FieldLoader.Require]
|
||||
public int Int2 = 2;
|
||||
[FieldLoader.Require]
|
||||
public int Int3 = 3;
|
||||
|
||||
public int Int4 = 4;
|
||||
public int Int5 = 5;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Required()
|
||||
{
|
||||
var target = new LoadRequiredTarget();
|
||||
var yaml = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(nameof(LoadRequiredTarget.Int1), "123"),
|
||||
new MiniYamlNode(nameof(LoadRequiredTarget.Int4), "456"),
|
||||
]);
|
||||
|
||||
void Act() => FieldLoader.Load(target, yaml);
|
||||
|
||||
Assert.That(Act,
|
||||
Throws.TypeOf<FieldLoader.MissingFieldsException>().And
|
||||
.Message.EqualTo($"{nameof(LoadRequiredTarget.Int2)}, {nameof(LoadRequiredTarget.Int3)}"));
|
||||
Assert.That(target.Int1, Is.EqualTo(123));
|
||||
Assert.That(target.Int2, Is.EqualTo(2));
|
||||
Assert.That(target.Int3, Is.EqualTo(3));
|
||||
Assert.That(target.Int4, Is.EqualTo(456));
|
||||
Assert.That(target.Int5, Is.EqualTo(5));
|
||||
}
|
||||
|
||||
sealed class LoadIgnoreTarget
|
||||
{
|
||||
[FieldLoader.Ignore]
|
||||
public int Int1 = 1;
|
||||
[FieldLoader.Ignore]
|
||||
public int Int2 = 2;
|
||||
|
||||
public int Int3 = 3;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Ignore()
|
||||
{
|
||||
var target = new LoadIgnoreTarget();
|
||||
var yaml = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(nameof(LoadIgnoreTarget.Int1), "123"),
|
||||
new MiniYamlNode(nameof(LoadIgnoreTarget.Int3), "456"),
|
||||
]);
|
||||
|
||||
FieldLoader.Load(target, yaml);
|
||||
|
||||
Assert.That(target.Int1, Is.EqualTo(1));
|
||||
Assert.That(target.Int2, Is.EqualTo(2));
|
||||
Assert.That(target.Int3, Is.EqualTo(456));
|
||||
}
|
||||
|
||||
sealed class LoadUsingTarget
|
||||
{
|
||||
[FieldLoader.LoadUsing(nameof(LoadInt))]
|
||||
public int Int1 = 1;
|
||||
[FieldLoader.LoadUsing(nameof(LoadInt), true)]
|
||||
public int Int2 = 2;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadInt))]
|
||||
public int Int3 = 3;
|
||||
[FieldLoader.LoadUsing(nameof(LoadInt), true)]
|
||||
public int Int4 = 4;
|
||||
|
||||
public int Int5 = 5;
|
||||
|
||||
static object LoadInt(MiniYaml yaml) => Exts.ParseInt32Invariant(yaml.NodeWithKey("ForLoadUsing").Value.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Using()
|
||||
{
|
||||
var target = new LoadUsingTarget();
|
||||
var yaml = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode("ForLoadUsing", "100"),
|
||||
new MiniYamlNode(nameof(LoadUsingTarget.Int1), "12"),
|
||||
new MiniYamlNode(nameof(LoadUsingTarget.Int2), "34"),
|
||||
new MiniYamlNode(nameof(LoadUsingTarget.Int5), "56"),
|
||||
]);
|
||||
|
||||
void Act() => FieldLoader.Load(target, yaml);
|
||||
|
||||
Assert.That(Act,
|
||||
Throws.TypeOf<FieldLoader.MissingFieldsException>().And
|
||||
.Message.EqualTo(nameof(LoadRequiredTarget.Int4)));
|
||||
Assert.That(target.Int1, Is.EqualTo(100));
|
||||
Assert.That(target.Int2, Is.EqualTo(100));
|
||||
Assert.That(target.Int3, Is.EqualTo(100));
|
||||
Assert.That(target.Int4, Is.EqualTo(4));
|
||||
Assert.That(target.Int5, Is.EqualTo(56));
|
||||
}
|
||||
|
||||
sealed class LoadUsingMissingTarget
|
||||
{
|
||||
[FieldLoader.LoadUsing("unknown")]
|
||||
public int Int = 1;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_UsingMissing()
|
||||
{
|
||||
var target = new LoadUsingMissingTarget();
|
||||
var yaml = new MiniYaml(null);
|
||||
|
||||
void Act() => FieldLoader.Load(target, yaml);
|
||||
|
||||
Assert.That(Act,
|
||||
Throws.TypeOf<InvalidOperationException>().And
|
||||
.Message.EqualTo("LoadUsingMissingTarget does not specify a loader function 'unknown'"));
|
||||
Assert.That(target.Int, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
265
OpenRA.Test/OpenRA.Game/FieldSaverTest.cs
Normal file
265
OpenRA.Test/OpenRA.Game/FieldSaverTest.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Test
|
||||
{
|
||||
[TestFixture]
|
||||
sealed class FieldSaverTest
|
||||
{
|
||||
static IEnumerable<TestCaseData> FormatValue_Primitive_TestCases()
|
||||
{
|
||||
return
|
||||
[
|
||||
new TestCaseData(123),
|
||||
new TestCaseData((ushort)123),
|
||||
new TestCaseData(123L),
|
||||
new TestCaseData(123.4f),
|
||||
new TestCaseData(123m),
|
||||
new TestCaseData("test"),
|
||||
new TestCaseData(Color.CornflowerBlue),
|
||||
new TestCaseData(new Hotkey(Keycode.A, Modifiers.Shift)),
|
||||
new TestCaseData(new WDist(123)),
|
||||
new TestCaseData(new WVec(123, 456, 789)),
|
||||
new TestCaseData(new WPos(123, 456, 789)),
|
||||
new TestCaseData(new WAngle(123)),
|
||||
new TestCaseData(new WRot(new WAngle(123), new WAngle(456), new WAngle(789))),
|
||||
new TestCaseData(new CPos(123, 456)),
|
||||
new TestCaseData(new CPos(123, 456, 78)),
|
||||
new TestCaseData(new CVec(123, 456)),
|
||||
new TestCaseData(new BooleanExpression("true")),
|
||||
new TestCaseData(new IntegerExpression("1 + 2")),
|
||||
new TestCaseData(MapGridType.RectangularIsometric),
|
||||
new TestCaseData(SystemActors.World | SystemActors.EditorWorld),
|
||||
new TestCaseData(true),
|
||||
new TestCaseData(new Size(123, 456)),
|
||||
new TestCaseData(new int2(123, 456)),
|
||||
new TestCaseData(new float2(123, 456)),
|
||||
new TestCaseData(new float3(123, 456, 789)),
|
||||
new TestCaseData(new Rectangle(123, 456, 789, 123)),
|
||||
];
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(FormatValue_Primitive_TestCases))]
|
||||
public void FormatVaue_Primitive<T>(T expected)
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(expected);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected.ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_Null()
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(null);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_DateTime()
|
||||
{
|
||||
var input = new DateTime(2000, 1, 1);
|
||||
|
||||
var actual = FieldSaver.FormatValue(input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(input.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_WVecArray()
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(new WVec[] { new(1, 2, 3), new(4, 5, 6) });
|
||||
|
||||
Assert.That(actual, Is.EqualTo("1,2,3, 4,5,6"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_CPosArray()
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(new CPos[] { new(1, 2), new(3, 4), new(5, 6) });
|
||||
|
||||
Assert.That(actual, Is.EqualTo("1,2, 3,4, 5,6"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_CVecArray()
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(new CVec[] { new(1, 2), new(3, 4), new(5, 6) });
|
||||
|
||||
Assert.That(actual, Is.EqualTo("1,2, 3,4, 5,6"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_int2Array()
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(new int2[] { new(1, 2), new(3, 4), new(5, 6) });
|
||||
|
||||
Assert.That(actual, Is.EqualTo("1,2, 3,4, 5,6"));
|
||||
}
|
||||
|
||||
[TestCase(null, "")]
|
||||
[TestCase(123, "123")]
|
||||
public void FormatValue_Nullable(int? input, string expected)
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null, "")]
|
||||
[TestCase(new int[] { }, "")]
|
||||
[TestCase(new int[] { 1 }, "1")]
|
||||
[TestCase(new int[] { 1, 2, 3 }, "1, 2, 3")]
|
||||
[TestCase(new int[] { 1, 1, 2, 2, 3 }, "1, 1, 2, 2, 3")]
|
||||
[TestCase(new int[] { 1, 1, 1, 1 }, "1, 1, 1, 1")]
|
||||
public void FormatValue_Array(int[] input, string expected)
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null, "")]
|
||||
[TestCase(new int[] { }, "")]
|
||||
[TestCase(new int[] { 1 }, "1")]
|
||||
[TestCase(new int[] { 1, 2, 3 }, "1, 2, 3")]
|
||||
[TestCase(new int[] { 1, 1, 2, 2, 3 }, "1, 1, 2, 2, 3")]
|
||||
[TestCase(new int[] { 1, 1, 1, 1 }, "1, 1, 1, 1")]
|
||||
public void FormatValue_List(int[] input, string expected)
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(input?.ToList());
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null, "")]
|
||||
[TestCase(new int[] { }, "")]
|
||||
[TestCase(new int[] { 1 }, "1")]
|
||||
[TestCase(new int[] { 1, 2, 3 }, "1, 2, 3")]
|
||||
[TestCase(new int[] { 1, 1, 2, 2, 3 }, "1, 2, 3")]
|
||||
[TestCase(new int[] { 1, 1, 1, 1 }, "1")]
|
||||
public void FormatValue_HashSet(int[] input, string expected)
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(input?.ToHashSet());
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase("1")]
|
||||
[TestCase("1,2,3")]
|
||||
[TestCase("1,1,2,2,3")]
|
||||
[TestCase("1,1,1,1")]
|
||||
public void FormatValue_BitSet(string input)
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(new BitSet<FieldSaverTest>(input));
|
||||
|
||||
Assert.That(actual, Is.EqualTo(input));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_Dictionary()
|
||||
{
|
||||
var input = new Dictionary<int, int> { { 12, 34 }, { 56, 78 } };
|
||||
|
||||
var actual = FieldSaver.FormatValue(input);
|
||||
|
||||
Assert.That(actual, Is.EqualTo($"12: 34{Environment.NewLine}56: 78{Environment.NewLine}"));
|
||||
}
|
||||
|
||||
sealed class FieldTarget
|
||||
{
|
||||
public int Int = 123;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatValue_Field()
|
||||
{
|
||||
var actual = FieldSaver.FormatValue(new FieldTarget(), typeof(FieldTarget).GetField(nameof(FieldTarget.Int)));
|
||||
|
||||
Assert.That(actual, Is.EqualTo("123"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveField()
|
||||
{
|
||||
var actual = FieldSaver.SaveField(new FieldTarget(), nameof(FieldTarget.Int));
|
||||
|
||||
var actualString = MiniYamlExts.WriteToString([actual]);
|
||||
var expectedString = MiniYamlExts.WriteToString([new MiniYamlNode(nameof(FieldTarget.Int), "123")]);
|
||||
Assert.That(actualString, Is.EqualTo(expectedString));
|
||||
}
|
||||
|
||||
sealed class SaveTarget
|
||||
{
|
||||
public int Int = 123;
|
||||
public string String = "test";
|
||||
public int[] IntArray = [1, 2, 3];
|
||||
public Dictionary<string, string> StringDictionary = new() { { "a", "b" }, { "c", "d" } };
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Save()
|
||||
{
|
||||
var expected = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(nameof(SaveTarget.Int), "123"),
|
||||
new MiniYamlNode(nameof(SaveTarget.String), "test"),
|
||||
new MiniYamlNode(nameof(SaveTarget.IntArray), "1, 2, 3"),
|
||||
new MiniYamlNode(
|
||||
nameof(SaveTarget.StringDictionary),
|
||||
new MiniYaml(null, [new MiniYamlNode("a", "b"), new MiniYamlNode("c", "d")])),
|
||||
]);
|
||||
|
||||
var actual = FieldSaver.Save(new SaveTarget());
|
||||
|
||||
var actualString = MiniYamlExts.WriteToString(actual.Nodes);
|
||||
var expectedString = MiniYamlExts.WriteToString(expected.Nodes);
|
||||
Assert.That(actual.Value, Is.EqualTo(expected.Value));
|
||||
Assert.That(actualString, Is.EqualTo(expectedString));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveDifferences()
|
||||
{
|
||||
var expected = new MiniYaml(
|
||||
null,
|
||||
[
|
||||
new MiniYamlNode(nameof(SaveTarget.String), ""),
|
||||
new MiniYamlNode(nameof(SaveTarget.IntArray), "1, 2, 4"),
|
||||
]);
|
||||
|
||||
var actual = FieldSaver.SaveDifferences(new SaveTarget { IntArray = [1, 2, 4], String = null }, new SaveTarget());
|
||||
|
||||
var actualString = MiniYamlExts.WriteToString(actual.Nodes);
|
||||
var expectedString = MiniYamlExts.WriteToString(expected.Nodes);
|
||||
Assert.That(actual.Value, Is.EqualTo(expected.Value));
|
||||
Assert.That(actualString, Is.EqualTo(expectedString));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveDifferences_DifferentTypes()
|
||||
{
|
||||
static void Act() => FieldSaver.SaveDifferences(new object(), new SaveTarget());
|
||||
|
||||
Assert.That(Act, Throws.TypeOf<InvalidOperationException>().And.Message.EqualTo("FieldSaver: can't diff objects of different types"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user