Почему это утверждение бросает исключение формата при сравнении структур?

Я пытаюсь утвердить равенство двух структур System.Drawing.Size, и я получаю исключение формата вместо ожидаемого отказа assert.

[TestMethod]
public void AssertStructs()
{
    var struct1 = new Size(0, 0);
    var struct2 = new Size(1, 1);

    //This throws a format exception, "System.FormatException: Input string was not in a correct format."
    Assert.AreEqual(struct1, struct2, "Failed. Expected {0}, actually it is {1}", struct1, struct2); 

    //This assert fails properly, "Failed. Expected {Width=0, Height=0}, actually it is {Width=1, Height=1}".
    Assert.AreEqual(struct1, struct2, "Failed. Expected " + struct1 + ", actually it is " + struct2); 
}

Является ли это предполагаемым поведением? Я здесь что-то не так?

Ответ 1

У меня это есть. И да, это ошибка.

Проблема в том, что здесь есть два уровня string.Format.

Первый уровень форматирования - это что-то вроде:

string template  = string.Format("Expected: {0}; Actual: {1}; Message: {2}",
                                 expected, actual, message);

Затем мы используем string.Format с параметрами, которые вы предоставили:

string finalMessage = string.Format(template, parameters);

(Очевидно, что существуют культуры, и какая-то дезинфекция... но не достаточно.)

Это выглядит хорошо - если только ожидаемые и фактические значения не заканчиваются фигурными скобками, после преобразования в строку, что они делают для Size. Например, ваш первый размер заканчивается преобразованием в:

{Width=0, Height=0}

Итак, второй уровень форматирования - это что-то вроде:

string.Format("Expected: {Width=0, Height=0}; Actual: {Width=1, Height=1 }; " +
              "Message = Failed expected {0} actually is {1}", struct1, struct2);

... и что сбой. Уч.

В самом деле, мы можем доказать это очень просто, обманув форматирование, чтобы использовать наши параметры для ожидаемых и фактических частей:

var x = "{0}";
var y = "{1}";
Assert.AreEqual<object>(x, y, "What a surprise!", "foo", "bar");

Результат:

Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!

Явно сломан, так как мы не ожидали foo и не было фактического значения bar!

В основном это похоже на атаку SQL-инъекций, но в менее страшном контексте string.Format.

В качестве обходного пути вы можете использовать string.Format, как предлагает StriplingWarrior. Это позволяет избежать второго уровня форматирования, выполняемого в результате форматирования с фактическими/ожидаемыми значениями.

Ответ 2

Я думаю, что вы нашли ошибку.

Это работает (генерирует исключение assert):

var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

И это работает (выводит сообщение):

var a = new{c=1};
var b = new{c=2};
Console.WriteLine(string.Format("Not equal {0} {1}", a, b));

Но это не работает (выбрасывает a FormatException):

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

Я не могу придумать никаких причин, по которым это ожидалось. Я бы отправил отчет об ошибке. Тем временем, обходной путь:

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, string.Format("Not equal {0} {1}", a, b));

Ответ 3

Я согласен с @StriplingWarrior, что это действительно является ошибкой с методом Assert.AreEqual() по крайней мере в 2 перегрузках. Как уже указывал StiplingWarrior, следующее сбой:

var a = new { c = 1 };
var b = new { c = 2 };
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

Я уже немного экспериментировал в этом, будучи немного более явным в использовании кода. Не работает и следующее:

// specify variable data type rather than "var"...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

и

// specify variable data type and name the type on the generic overload of AreEqual()...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual<Size>(a, b, "Not equal {0} {1}", a, b);

Это заставило меня задуматься. System.Drawing.Size - это структура. Как насчет объектов? В списке параметров указано, что список после сообщения string params object[]. Технически, да, структуры - это объекты... но специальные типы объектов, т.е. Типы значений. Я думаю, что это где ошибка. Если мы используем наш собственный объект с аналогичным использованием и структурой с Size, на самом деле работает:

private class MyClass
{
    public MyClass(int width, int height)
        : base()
    { Width = width; Height = height; }

    public int Width { get; set; }
    public int Height { get; set; }
}

[TestMethod]
public void TestMethod1()
{
    var test1 = new MyClass(0, 0);
    var test2 = new MyClass(1, 1);
    Assert.AreEqual(test1, test2, "Show me A [{0}] and B [{1}]", test1, test2);
}

Ответ 4

Я думаю, что первое утверждение неверно.

Используйте это вместо:

Assert.AreEqual(struct1, 
                struct2, 
                string.Format("Failed expected {0} actually is {1}", struct1, struct2));