I’ve got it. And yes, it’s a bug.
The problem is that there are two levels of string.Format going on here.
The first level of formatting is something like:
string template = string.Format("Expected: {0}; Actual: {1}; Message: {2}",
expected, actual, message);
Then we use string.Format with the parameters you’ve supplied:
string finalMessage = string.Format(template, parameters);
(Obviously there’s cultures being provided, and some sort of sanitization… but not enough.)
That looks fine – unless the expected and actual values themselves end up with braces in, after being converted to a string – which they do for Size. For example, your first size ends up being converted to:
{Width=0, Height=0}
So the second level of formatting is something like:
string.Format("Expected: {Width=0, Height=0}; Actual: {Width=1, Height=1 }; " +
"Message = Failed expected {0} actually is {1}", struct1, struct2);
… and that’s what’s failing. Ouch.
Indeed, we can prove this really easily by fooling the formatting to use our parameters for the expected and actual parts:
var x = "{0}";
var y = "{1}";
Assert.AreEqual<object>(x, y, "What a surprise!", "foo", "bar");
The result is:
Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!
Clearly broken, as we weren’t expecting foo nor was the actual value bar!
Basically this is like a SQL injection attack, but in the rather less scary context of string.Format.
As a workaround, you can use string.Formatas StriplingWarrior suggests. That avoids the second level of formatting being performed on the result of formatting with the actual/expected values.