Parsing a message template whose alignment is "-2147483648" (int.MinValue) threw System.OverflowException from Math.Abs, violating the documented contract that syntactically invalid templates yield text tokens rather than throwing. Treat that alignment as text, matching how other alignment values that cannot form a valid width are already handled.
477 lines
15 KiB
C#
477 lines
15 KiB
C#
using PropertyToken = Serilog.Parsing.PropertyToken;
|
|
|
|
namespace Serilog.Tests.Parsing;
|
|
|
|
public class MessageTemplateParserTests
|
|
{
|
|
static MessageTemplateToken[] Parse(string messageTemplate)
|
|
{
|
|
return new MessageTemplateParser().Parse(messageTemplate).Tokens.ToArray();
|
|
}
|
|
|
|
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
|
|
static void AssertParsedAs(string message, params MessageTemplateToken[] messageTemplateTokens)
|
|
{
|
|
var parsed = Parse(message);
|
|
Assert.Equal(
|
|
messageTemplateTokens,
|
|
parsed);
|
|
}
|
|
|
|
[Fact]
|
|
public void MessageTemplateIsRequired()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() => Parse(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void AnEmptyMessageIsASingleTextToken()
|
|
{
|
|
var ts = Parse("");
|
|
var mt = Assert.Single(ts);
|
|
var tt = Assert.IsType<TextToken>(mt);
|
|
Assert.Equal("", tt.Text);
|
|
Assert.Equal("", tt.ToString());
|
|
Assert.Equal(0, tt.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void AnEmptyPropertyIsIsParsedAsText()
|
|
{
|
|
var ts = Parse("{}");
|
|
var mt = Assert.Single(ts);
|
|
var tt = Assert.IsType<TextToken>(mt);
|
|
Assert.Equal("{}", tt.Text);
|
|
Assert.Equal("{}", tt.ToString());
|
|
Assert.Equal(2, tt.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void AMessageWithoutPropertiesIsASingleTextToken()
|
|
{
|
|
AssertParsedAs("Hello, world!",
|
|
new TextToken("Hello, world!"));
|
|
}
|
|
|
|
[Fact]
|
|
public void AMessageWithPropertyOnlyIsASinglePropertyToken()
|
|
{
|
|
AssertParsedAs("{Hello}",
|
|
new PropertyToken("Hello", "{Hello}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DoubledLeftBracketsAreParsedAsASingleBracket()
|
|
{
|
|
AssertParsedAs("{{ Hi! }",
|
|
new TextToken("{ Hi! }"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DoubledLeftBracketsAreParsedAsASingleBracketInsideText()
|
|
{
|
|
AssertParsedAs("Well, {{ Hi!",
|
|
new TextToken("Well, { Hi!"));
|
|
|
|
AssertParsedAs("Hello, {{worl@d}!",
|
|
new TextToken("Hello, {worl@d}!"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DoubledRightBracketsAreParsedAsASingleBracket()
|
|
{
|
|
AssertParsedAs("Nice }}-: mo",
|
|
new TextToken("Nice }-: mo"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DoubledRightBracketsAfterOneLeftIsParsedAPropertyTokenAndATextToken()
|
|
{
|
|
AssertParsedAs("{World}}!",
|
|
new PropertyToken("World", "{World}"), new TextToken("}!"));
|
|
|
|
AssertParsedAs("Hello, {World}}!",
|
|
new TextToken("Hello, "), new PropertyToken("World", "{World}"), new TextToken("}!"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DoubledBracketsAreParsedAsASingleBracket()
|
|
{
|
|
AssertParsedAs("{{Hi}}",
|
|
new TextToken("{Hi}"));
|
|
|
|
AssertParsedAs("Hello, {{worl@d}}!",
|
|
new TextToken("Hello, {worl@d}!"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("{test.name}")]
|
|
[InlineData("{te.st.na.me}")]
|
|
public void DashedAndDottedNamesAreAccepted(string template)
|
|
{
|
|
var parser = new MessageTemplateParser();
|
|
var token = Assert.Single(parser.Parse(template).Tokens);
|
|
var propertyToken = Assert.IsType<PropertyToken>(token);
|
|
var expected = template.Trim('{', '}');
|
|
Assert.Equal(expected, propertyToken.PropertyName);
|
|
Assert.Null(propertyToken.Alignment);
|
|
Assert.Null(propertyToken.Format);
|
|
Assert.False(propertyToken.IsPositional);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("{0 space}")]
|
|
[InlineData("{0 space")]
|
|
[InlineData("{0_space")]
|
|
[InlineData("{0_{{space}")]
|
|
[InlineData("{0_{{space")]
|
|
[InlineData("{.testname}")]
|
|
[InlineData("{testname.}")]
|
|
[InlineData("{.}")]
|
|
[InlineData("{..}")]
|
|
[InlineData("{test..name}")]
|
|
[InlineData("{10.name}")]
|
|
[InlineData("{0_}")]
|
|
[InlineData("{0a}")]
|
|
public void AMalformedPropertyTagIsParsedAsText(string template)
|
|
{
|
|
AssertParsedAs(template, new TextToken(template));
|
|
}
|
|
|
|
[Fact]
|
|
public void ATrailingUnmatchedBracketIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{0}}space}", new PropertyToken("0", "{0}"), new TextToken("}space}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void AMessageWithAMalformedPropertyTagIsParsedAsManyTextTokens()
|
|
{
|
|
AssertParsedAs("Hello, {w@rld}",
|
|
new TextToken("Hello, "), new TextToken("{w@rld}"));
|
|
|
|
AssertParsedAs("Hello, {w@rld",
|
|
new TextToken("Hello, "), new TextToken("{w@rld"));
|
|
|
|
AssertParsedAs("Hello, {w{{rld",
|
|
new TextToken("Hello, "), new TextToken("{w{{rld"));
|
|
|
|
AssertParsedAs("Hello{{, {w{{rld",
|
|
new TextToken("Hello{, "), new TextToken("{w{{rld"));
|
|
|
|
AssertParsedAs("Hello, {w@rld}, HI!",
|
|
new TextToken("Hello, "), new TextToken("{w@rld}"), new TextToken(", HI!"));
|
|
|
|
AssertParsedAs("{w@rld} Hi!",
|
|
new TextToken("{w@rld}"), new TextToken(" Hi!"));
|
|
|
|
AssertParsedAs("{H&llo}, {w@rld}",
|
|
new TextToken("{H&llo}"), new TextToken(", "), new TextToken("{w@rld}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ASingleIntegerPropertyNameIsParsedAsPositionalProperty()
|
|
{
|
|
var parsed = (PropertyToken)Parse("{0}").Single();
|
|
Assert.Equal("0", parsed.PropertyName);
|
|
Assert.Equal("{0}", parsed.RawText);
|
|
Assert.Equal(parsed.RawText, parsed.ToString());
|
|
Assert.True(parsed.IsPositional);
|
|
}
|
|
|
|
[Fact]
|
|
public void ManyIntegerPropertyNameIsParsedAsPositionalProperty()
|
|
{
|
|
var parsed = Parse("{0}, {1}, {2}");
|
|
|
|
var prop1 = (PropertyToken)parsed[0];
|
|
Assert.Equal("0", prop1.PropertyName);
|
|
Assert.Equal("{0}", prop1.RawText);
|
|
Assert.True(prop1.IsPositional);
|
|
Assert.Equal(3, prop1.Length);
|
|
|
|
var prop2 = (TextToken)parsed[1];
|
|
Assert.Equal(", ", prop2.Text);
|
|
Assert.Equal(2, prop2.Length);
|
|
|
|
var prop3 = (PropertyToken)parsed[2];
|
|
Assert.Equal("1", prop3.PropertyName);
|
|
Assert.Equal("{1}", prop3.RawText);
|
|
Assert.True(prop3.IsPositional);
|
|
Assert.Equal(3, prop3.Length);
|
|
|
|
var prop4 = (TextToken)parsed[3];
|
|
Assert.Equal(", ", prop4.Text);
|
|
Assert.Equal(2, prop4.Length);
|
|
|
|
var prop5 = (PropertyToken)parsed[4];
|
|
Assert.Equal("2", prop5.PropertyName);
|
|
Assert.Equal("{2}", prop5.RawText);
|
|
Assert.True(prop5.IsPositional);
|
|
Assert.Equal(3, prop5.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void InvalidIntegerPropertyNameIsParsedAsText()
|
|
{
|
|
var parsed = Parse("{-1}{-0}{0}{1}{3.1415}");
|
|
|
|
var prop1 = (TextToken)parsed[0];
|
|
Assert.Equal("{-1}", prop1.Text);
|
|
|
|
var prop2 = (TextToken)parsed[1];
|
|
Assert.Equal("{-0}", prop2.Text);
|
|
|
|
var prop3 = (PropertyToken)parsed[2];
|
|
Assert.Equal("0", prop3.PropertyName);
|
|
Assert.Equal("{0}", prop3.RawText);
|
|
Assert.True(prop3.IsPositional);
|
|
|
|
var prop4 = (PropertyToken)parsed[3];
|
|
Assert.Equal("1", prop4.PropertyName);
|
|
Assert.Equal("{1}", prop4.RawText);
|
|
Assert.True(prop4.IsPositional);
|
|
|
|
var prop5 = (TextToken)parsed[4];
|
|
Assert.Equal("{3.1415}", prop5.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatsCanContainColons()
|
|
{
|
|
var parsed = (PropertyToken)Parse("{Time:hh:mm}").Single();
|
|
Assert.Equal("hh:mm", parsed.Format);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatsCanContainSymbolsAndOperators()
|
|
{
|
|
AssertParsedAs("{Hello:HH$MM}",
|
|
new PropertyToken("Hello", "{Hello:HH$MM}", "HH$MM"));
|
|
|
|
AssertParsedAs("{Hello:HH<MM}",
|
|
new PropertyToken("Hello", "{Hello:HH<MM}", "HH<MM"));
|
|
|
|
AssertParsedAs("{Hello:HH#MM}",
|
|
new PropertyToken("Hello", "{Hello:HH#MM}", "HH#MM"));
|
|
|
|
AssertParsedAs("{Hello:HH+MM}",
|
|
new PropertyToken("Hello", "{Hello:HH+MM}", "HH+MM"));
|
|
|
|
AssertParsedAs("{Hello:HH`MM}",
|
|
new PropertyToken("Hello", "{Hello:HH`MM}", "HH`MM"));
|
|
}
|
|
|
|
[Fact]
|
|
public void PropertiesCanHaveLeftAlignment()
|
|
{
|
|
var prop1 = (PropertyToken)Parse("{Hello,-5}").Single();
|
|
Assert.Equal("Hello", prop1.PropertyName);
|
|
Assert.Equal("{Hello,-5}", prop1.RawText);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Left, 5), prop1.Alignment);
|
|
|
|
var prop2 = (PropertyToken)Parse("{Hello,-50}").Single();
|
|
Assert.Equal("Hello", prop2.PropertyName);
|
|
Assert.Equal("{Hello,-50}", prop2.RawText);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Left, 50), prop2.Alignment);
|
|
}
|
|
|
|
[Fact]
|
|
public void PropertiesCanHaveRightAlignment()
|
|
{
|
|
var prop1 = (PropertyToken)Parse("{Hello,5}").Single();
|
|
Assert.Equal("Hello", prop1.PropertyName);
|
|
Assert.Equal("{Hello,5}", prop1.RawText);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Right, 5), prop1.Alignment);
|
|
|
|
var prop2 = (PropertyToken)Parse("{Hello,50}").Single();
|
|
Assert.Equal("Hello", prop2.PropertyName);
|
|
Assert.Equal("{Hello,50}", prop2.RawText);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Right, 50), prop2.Alignment);
|
|
}
|
|
|
|
[Fact]
|
|
public void PropertiesCanHaveAlignmentAndFormat()
|
|
{
|
|
var prop1 = (PropertyToken)Parse("{Hello,-5:000}").Single();
|
|
Assert.Equal("Hello", prop1.PropertyName);
|
|
Assert.Equal("{Hello,-5:000}", prop1.RawText);
|
|
Assert.Equal("000", prop1.Format);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Left, 5), prop1.Alignment);
|
|
|
|
var prop2 = (PropertyToken)Parse("{Hello,-50:000}").Single();
|
|
Assert.Equal("Hello", prop2.PropertyName);
|
|
Assert.Equal("{Hello,-50:000}", prop2.RawText);
|
|
Assert.Equal("000", prop2.Format);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Left, 50), prop2.Alignment);
|
|
|
|
var prop3 = (PropertyToken)Parse("{Hello,5:000}").Single();
|
|
Assert.Equal("Hello", prop3.PropertyName);
|
|
Assert.Equal("{Hello,5:000}", prop3.RawText);
|
|
Assert.Equal("000", prop3.Format);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Right, 5), prop3.Alignment);
|
|
|
|
var prop4 = (PropertyToken)Parse("{Hello,50:000}").Single();
|
|
Assert.Equal("Hello", prop4.PropertyName);
|
|
Assert.Equal("{Hello,50:000}", prop4.RawText);
|
|
Assert.Equal("000", prop4.Format);
|
|
Assert.Equal(new Alignment(AlignmentDirection.Right, 50), prop4.Alignment);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatInFrontOfAlignmentWillHaveTheAlignmentBeConsideredPartOfTheFormat()
|
|
{
|
|
var prop1 = (PropertyToken)Parse("{Hello:000,-5}").Single();
|
|
Assert.Equal("Hello", prop1.PropertyName);
|
|
Assert.Equal("{Hello:000,-5}", prop1.RawText);
|
|
Assert.Equal("000,-5", prop1.Format);
|
|
Assert.Null(prop1.Alignment);
|
|
|
|
var prop2 = (PropertyToken)Parse("{Hello:000,-50}").Single();
|
|
Assert.Equal("Hello", prop2.PropertyName);
|
|
Assert.Equal("{Hello:000,-50}", prop2.RawText);
|
|
Assert.Equal("000,-50", prop2.Format);
|
|
Assert.Null(prop2.Alignment);
|
|
|
|
var prop3 = (PropertyToken)Parse("{Hello:000,5}").Single();
|
|
Assert.Equal("Hello", prop3.PropertyName);
|
|
Assert.Equal("{Hello:000,5}", prop3.RawText);
|
|
Assert.Equal("000,5", prop3.Format);
|
|
Assert.Null(prop3.Alignment);
|
|
|
|
var prop4 = (PropertyToken)Parse("{Hello:000,50}").Single();
|
|
Assert.Equal("Hello", prop4.PropertyName);
|
|
Assert.Equal("{Hello:000,50}", prop4.RawText);
|
|
Assert.Equal("000,50", prop4.Format);
|
|
Assert.Null(prop4.Alignment);
|
|
}
|
|
|
|
[Fact]
|
|
public void ZeroValuesAlignmentIsParsedAsProperty()
|
|
{
|
|
AssertParsedAs("{Hello,-0}",
|
|
new PropertyToken("Hello", "{Hello,-0}", alignment: new Alignment(AlignmentDirection.Right, 0)));
|
|
|
|
AssertParsedAs("{Hello,0}",
|
|
new PropertyToken("Hello", "{Hello,0}", alignment: new Alignment(AlignmentDirection.Left, 0)));
|
|
}
|
|
|
|
[Fact]
|
|
public void AlignmentWithPositiveSignParsedAsText()
|
|
{
|
|
AssertParsedAs("{Hello,+10}",
|
|
new TextToken("{Hello,+10}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void NonNumberAlignmentIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{Hello,-aa}",
|
|
new TextToken("{Hello,-aa}"));
|
|
|
|
AssertParsedAs("{Hello,aa}",
|
|
new TextToken("{Hello,aa}"));
|
|
|
|
AssertParsedAs("{Hello,-10-1}",
|
|
new TextToken("{Hello,-10-1}"));
|
|
|
|
AssertParsedAs("{Hello,10-1}",
|
|
new TextToken("{Hello,10-1}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void AlignmentOfIntMinValueIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{Hello,-2147483648}",
|
|
new TextToken("{Hello,-2147483648}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void EmptyAlignmentIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{Hello,}",
|
|
new TextToken("{Hello,}"));
|
|
|
|
AssertParsedAs("{Hello,:format}",
|
|
new TextToken("{Hello,:format}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void MultipleTokensHasCorrectIndexes()
|
|
{
|
|
AssertParsedAs("{Greeting}, {Name}!",
|
|
new PropertyToken("Greeting", "{Greeting}"),
|
|
new TextToken(", "),
|
|
new PropertyToken("Name", "{Name}"),
|
|
new TextToken("!"));
|
|
}
|
|
|
|
[Fact]
|
|
public void MissingRightBracketIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{Hello",
|
|
new TextToken("{Hello"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DestructureHintIsParsedCorrectly()
|
|
{
|
|
var parsed = (PropertyToken)Parse("{@Hello}").Single();
|
|
Assert.Equal(Destructuring.Destructure, parsed.Destructuring);
|
|
}
|
|
|
|
[Fact]
|
|
public void StringifyHintIsParsedCorrectly()
|
|
{
|
|
var parsed = (PropertyToken)Parse("{$Hello}").Single();
|
|
Assert.Equal(Destructuring.Stringify, parsed.Destructuring);
|
|
}
|
|
|
|
[Fact]
|
|
public void DestructureWithInvalidHintsIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{@$}",
|
|
new TextToken("{@$}"));
|
|
|
|
AssertParsedAs("{$@}",
|
|
new TextToken("{$@}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DestructuringWithEmptyPropertyNameIsParsedAsText()
|
|
{
|
|
AssertParsedAs("{@}",
|
|
new TextToken("{@}"));
|
|
|
|
AssertParsedAs("{$}",
|
|
new TextToken("{$}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void UnderscoresAreValidInPropertyNames()
|
|
{
|
|
AssertParsedAs("{_123_Hello}", new PropertyToken("_123_Hello", "{_123_Hello}"));
|
|
}
|
|
|
|
[Fact]
|
|
public void IndexOutOfRangeExceptionBugHasNotRegressed()
|
|
{
|
|
var parser = new MessageTemplateParser();
|
|
parser.Parse("{,,}");
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatCanContainMultipleSections()
|
|
{
|
|
var parsed = (PropertyToken)Parse("{Number:##.0;-##.0;zero}").Single();
|
|
Assert.Equal("##.0;-##.0;zero", parsed.Format);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatCanContainPlusSign()
|
|
{
|
|
var parsed = (PropertyToken)Parse("{Number:+##.0}").Single();
|
|
Assert.Equal("+##.0", parsed.Format);
|
|
}
|
|
}
|