ラベル バグ情報 の投稿を表示しています。 すべての投稿を表示
ラベル バグ情報 の投稿を表示しています。 すべての投稿を表示

2015年5月16日

Assert.AreEqual に NaN が入るとテストが成功するバグ (VS2010~2012)

Visual Studio 2010 (たぶん 2012 も) の MSTest の delta を指定する Assert.AreEqual には下記のようなバグがある。

// delta を指定する場合
Assert.AreEqual(1.0, double.NaN, 1.0); // 成功

// delta を指定しない場合
Assert.AreEqual(1.0, double.NaN); // 失敗

// 失敗するのが正しい動作なので、delta を指定する方はバグっている

バグの原因はソースを見れば明らかで、NaN を考慮していないため。
[Assert.AreEqual のソース (ILSpy)]

public static void AreEqual(double expected, double actual, double delta, string message, params object[] parameters)
{
    if (Math.Abs(expected - actual) > delta)
    {
        string message2 = FrameworkMessages.AreEqualDeltaFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), expected.ToString(CultureInfo.CurrentCulture.NumberFormat), actual.ToString(CultureInfo.CurrentCulture.NumberFormat), delta.ToString(CultureInfo.CurrentCulture.NumberFormat));
        Assert.HandleFail("Assert.AreEqual", message2, parameters);
    }
}

// 比較条件に NaN が含まれると false になるので if の中に入らない

Assert.AreEqual が含まれるアセンブリの配置場所は
%VS のディレクトリ%\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll

Visual Studio 2013 では修正されているが・・・

このバグは VS2013 では修正されている。
[修正済み Assert.AreEqual のソース (ILSpy)]

public static void AreEqual(double expected, double actual, double delta, string message, params object[] parameters)
{
    if (double.IsNaN(expected) || double.IsNaN(actual) || double.IsNaN(delta))
    {
        string message2 = FrameworkMessages.AreEqualDeltaFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), expected.ToString(CultureInfo.CurrentCulture.NumberFormat), actual.ToString(CultureInfo.CurrentCulture.NumberFormat), delta.ToString(CultureInfo.CurrentCulture.NumberFormat));
         Assert.HandleFail("Assert.AreEqual", message2, parameters);
    }
    if (Math.Abs(expected - actual) > delta)
    {
        string message3 = FrameworkMessages.AreEqualDeltaFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), expected.ToString(CultureInfo.CurrentCulture.NumberFormat), actual.ToString(CultureInfo.CurrentCulture.NumberFormat), delta.ToString(CultureInfo.CurrentCulture.NumberFormat));
        Assert.HandleFail("Assert.AreEqual", message3, parameters);
    }
}

// 引数のいずれかが NaN の場合、テスト失敗

が、VS2010 をインストールしている場合、修正されたアセンブリ「Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll」(以降、UnitTestFramework.dll)が使われないケースがある。
[確認できた発生条件]
  • VS2010 と VS2013 インストール済 (インストール順序は関係ない)
  • .NET 4.0 以上のテストプロジェクト

原因

VS2010 は GAC に .NET 2.0 と 4.0 の UnitTestFramework.dll (バグ有り) をインストールするが、VS2013 は .NET 2.0 の UnitTestFramework.dll (修正済み) しかインストールしないため。
かつ、VS2010 の UnitTestFramework.dll (.NET 4.0) と VS2013 の UnitTestFramework.dll (.NET 2.0) のバージョン番号「10.0.0.0」が同じであるため。(公開キーは MS 製品なので同じ)
※他にバージョン番号「10.1.0.0」の UnitTestFramework.dll も存在するが、設定で参照されないようになっている

VS2010 と VS2013 をインストールした環境の GAC は下記の状態になる。
CLR 2.0 %windir%\assembly 修正済み
CLR 4 %windir%\Microsoft.NET\assembly バグ有り

そして、CLR 4 の実行アプリは CLR 4 の GAC を優先するため、バグ有りの方の UnitTestFramework.dll が参照されることになる。(参考:GAC 内のアセンブリ検索方法
公開キーが一緒なのは仕方ないとして、バージョン番号さえ変えとけば、こんな事態は回避できた・・・

解決手段

上記リンクでも説明されてるけど、CLR 4 の GAC の実ファイルを移動または削除すればOK。
%windir%\Microsoft.NET\assembly\GAC_MSIL\Microsoft.VisualStudio.QualityTools.UnitTestFramework\v4.0_10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll

副次的な効果として、VS2010 のテストプロジェクトからも修正された UnitTestFramework.dll が使用されるようになる。

2015年3月19日

IE10 table に colspan を指定するとカラムの下線が消えるバグ

検索でもたくさん引っかかる割と有名なバグ。
今更という気もするが、自分も遭遇したため、現象と解決手段をまとめておく。

発生条件(AND)
  • IE10 実機
  • ドキュメントモード: 標準 / Quirks / Internet Explorer 9 標準 / Internet Explorer 8 標準
  • table に { border-collapse: collapse; } を指定
  • td と th に 0 でない border-bottom を指定
  • td または th に 2 以上の colspan を指定

現象が発生するコードと画面キャプチャ。
[CSS]

table { border-collapse: collapse; width: 400px; }
td, th { border: 1px solid black; }
[HTML]

<table>
    <tr>
        <th>col</th>
        <th>col</th>
        <th>col</th>
    </tr>
    <tr>
        <td colspan=3>col3</td>
    </tr>
</table>
[画面キャプチャ]
IE10 table border bug image

常に発生するわけでもなく、画面の再描画で正常に表示されたりする。
開発者ツールを使うと再描画が走るので調査できなかったり・・・

解決手段

発生条件からどれか 1 つを取り除けば、現象は発生しない。
「IE10 を使わない」が理想だけど、実際はドキュメントモードを変更または CSS で何とかすることになると思う。

上のコードに対する CSS による解決例。
※ CSS ハックなどで、IE10 にだけ下記を適用
 (全ブラウザに対して適用してもコードが特殊になるだけで害はないはず・・)

/* カラムの下線を 0 に指定 */
td, th { border-bottom: 0 none; }

/* 代わりに table の下線を指定 */
table { border-bottom: 1px solid black; }

/* または、tr でも可能 (tr の border は IE8以降で有効) */
tr { border-bottom: 1px solid black; }

IE お得意の条件付きコメントは IE10 から廃止されているため、IE10 のみ適用する場合は CSS ハックまたは javascript に頼るしかない。
まだバグが残ってるのに・・・

2015年1月30日

VB.NET 匿名デリゲートと 3 項演算子で実行時エラー

下記は、何の問題もなさそうでコンパイル可能なコードだが、実行時エラーが発生する。
[コード]

Module TestDelegateIf
    Sub Main()
        Dim text As String = Nothing
        Dim fn As Func(Of Integer) = If(text Is Nothing, Nothing, Function() text.Length)
    End Sub
End Module
[結果]
ハンドルされていない例外: System.ArgumentException: インスタンス メソッドへのデリゲートに null の 'this' を指定することはできません。
   場所 System.MulticastDelegate.CtorClosed(Object target, IntPtr methodPtr)
   場所 TestDelegateIf.Main()

3 項演算子を使っているのに、Function() text.Length の部分が実行されているっぽく??????
Function ラムダ式だけでなく、Sub ラムダ式でも同じ現象が発生する。

解決手段?

3 項演算子でなく If 文を使えばエラーは起きない。
匿名デリゲートを Func(Of Integer) に代入してもエラーは起きない。
[正常コード1]

Module TestDelegateIf
    Sub Main()
        Dim text As String = Nothing

        ' If を使用
        Dim fn As Func(Of Integer)
        If text Is Nothing Then
            fn = Nothing
        Else
            fn= Function() text.Length
        End If

        ' 匿名デリゲートの代入
        Dim fn_ = Function() text.Length
        fn = fn_
    End Sub
End Module

匿名デリゲートを使わない方法もエラーは起きない。
※ C# だと匿名デリゲートが存在しないため、これと同等のコードになる。
[正常コード2]

Module TestDelegateIf
    Sub Main()
        Dim text As String = Nothing
        Dim fn = If(text Is Nothing, Nothing, New Func(Of Integer)(Function() text.Length))
    End Sub
End Module

まとめ

この現象は、たぶんコンパイラのバグに該当するものだと思われる。
VB の匿名デリゲートは便利だが、同時に無効化する Option が欲しい。というかあった方がいい。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.5.2

2014年7月12日

SortedSet<T> コンストラクタのバグ (.NET4~4.5)

次のような拡張メソッドを作った際に気付いたのだが、SortedSet<T> のコンストラクタ (IEnumerable<T>, IComparer<T>) に (要素 2 個以上のコレクション, null) を渡すと例外が発生する。
引数 1 個の場合や、要素 1 個のコレクションでは例外にならないから分かりにくい。

public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source, IComparer<T> comparer)
{
    if( source == null ) throw new ArgumentNullException("source");
    return new SortedSet<T>(source, comparer);
}
public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source)
{
    return source.ToSortedSet(null);
}

[検証コード]

static void Main()
{
    var test1 = new SortedSet<int>(new[]{ 1 }, null);
    Console.WriteLine(test1.Count);
    var test2 = new SortedSet<int>(new[]{ 1, 2 }, null);
    Console.WriteLine(test2.Count);
}
[結果]
1

ハンドルされていない例外: System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
   場所 System.Collections.Generic.SortedSet`1..ctor(IEnumerable`1 collection, IComparer`1 comparer)

   場所 Program.Main()

原因は、SortedSet<T> のソース (Date: August 15, 2008) を見ると明らかで、168行目の comparer に this. の付け忘れ…
ちなみに、似たようなクラスである HashSet<T> は問題なし。

バグを考慮した拡張メソッド

そのうち修正されることを期待しつつ、拡張メソッドは次のように修正。

public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source, IComparer<T> comparer)
{
    if( source == null ) throw new ArgumentNullException("source");
    return new SortedSet<T>(source, comparer ?? Comparer<T>.Default;);
}
public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source)
{
    return source.ToSortedSet(null);
}

C# 6.0 からコンストラクタの型引数を推論してくれるようになるらしいんで、こんなメソッドはいらなくなるはず。