/*
 * DrFx - FxCop Report Translator and Visualizer.
 * Copyright (C) 2010 Sasa Yuan
 * 
 * This program is free software: you can redistribute it and/or modify
 * it 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.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */



using NUnit.Framework;
using Sasa.QualityTools.DrFx.Console;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace Sasa.QualityTools.DrFx.Console.Tests.System
{
    /// <summary>
    /// drfx-console.exe が設計通りに動作することをテストします。
    /// </summary>
    [TestFixture]
    public class ProgramTest
    {
        /// <summary>
        /// 標準出力に出力された文字列の保持領域。
        /// </summary>
        private StringBuilder stdout;

        /// <summary>
        /// 標準エラー出力に出力された文字列の保持領域。
        /// </summary>
        private StringBuilder stderr;

        /// <summary>
        /// オリジナルの標準出力のキャッシュ。
        /// </summary>
        private TextWriter originalStdout;

        /// <summary>
        /// オリジナルの標準エラー出力のキャッシュ。
        /// </summary>
        private TextWriter originalStderr;



        /// <summary>
        /// <list>
        /// <item>プログラムが標準出力、標準エラー出力に出力した文字列をキャプチャできるように、Console クラスの標準出力、標準エラー出力を置き換えます。</item>
        /// <item>カレントディレクトリの全 XML ファイルを削除します。</item>
        /// </list>
        /// </summary>
        [SetUp]
        public void SetUp()
        {
            this.originalStdout = global::System.Console.Out;
            this.originalStderr = global::System.Console.Error;

            this.stdout = new StringBuilder();
            global::System.Console.SetOut(new StringWriter(this.stdout));
            this.stderr = new StringBuilder();
            global::System.Console.SetError(new StringWriter(this.stderr));

            Directory.CreateDirectory(@"testdata\result");

            foreach (string path in Directory.GetFiles(".", "*.xml"))
            {
                File.Delete(path);
            }
        }

        /// <summary>
        /// Console の標準出力、標準エラー出力をテスト前の状態に戻します。
        /// </summary>
        [TearDown]
        public void TearDown()
        {
            Directory.Delete(@"testdata\result", true);
            global::System.Console.SetError(this.originalStderr);
            global::System.Console.SetOut(this.originalStdout);
        }

        /// <summary>
        /// --target オプションを 1 つだけ指定した場合に、指定したアセンブリが正常に分析されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--target")]
        [TestCase("-t")]
        public void TestSingleTargetOption(string option)
        {
            string argument = String.Format(@"{0} testdata\bin\Target1.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(1, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --target オプションを 2 つ指定した場合に、両方のアセンブリが正常に分析されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--target")]
        [TestCase("-t")]
        public void TestMultiTargetOption(string option)
        {
            string argument = String.Format(@"{0} testdata\bin\Target1.dll {0} testdata\bin\Target2.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(2, GetViolationMessageCount());
            Assert.AreEqual(2, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --target オプションにワイルドカードを指定した場合に、ワイルドカードにマッチする全てのアセンブリが
        /// 正常に分析されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--target")]
        [TestCase("-t")]
        public void TestTargetOptionWithWildCard(string option)
        {
            string argument = String.Format(@"{0} testdata\bin\Target*.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(2, GetViolationMessageCount());
            Assert.AreEqual(2, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --target オプションに存在しないアセンブリを指定した場合に、エラーメッセージが表示されて
        /// 処理が終了することをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--target")]
        [TestCase("-t")]
        public void TestTargetOptionWithNotExistingAssembly(string option)
        {
            string argument = String.Format(@"{0} testdata\bin\NotExist.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"分析対象アセンブリのパスを修正してください。'testdata\bin\NotExist.dll' に一致するファイルが見つかりません。");
        }

        /// <summary>
        /// --target オプションを複数指定し、その中に存在しないアセンブリが含まれる場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--target")]
        [TestCase("-t")]
        public void TestTargetOptionContainsNotExistingAssembly(string option)
        {
            string argument = String.Format(@"{0} testdata\bin\Target1.dll {0} testdata\bin\NotExist.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"分析対象アセンブリのパスを修正してください。'testdata\bin\NotExist.dll' に一致するファイルが見つかりません。");
        }

        /// <summary>
        /// --target オプションにディレクトリを指定した場合に、エラーメッセージが表示されて
        /// 処理が終了することをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--target")]
        [TestCase("-t")]
        public void TestTargetOptionWithDirectory(string option)
        {
            string argument = String.Format(@"{0} testdata\bin", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"分析対象アセンブリのパスを修正してください。'testdata\bin' に一致するファイルが見つかりません。");
        }

        /// <summary>
        /// --target オプションを指定しない場合に、エラーメッセージが表示されて
        /// 処理が終了することをテストします。
        /// </summary>
        [Test]
        public void TestTargetOptionIsNotSpecified()
        {
            string argument = String.Format(@"");
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors("分析対象アセンブリを少なくとも 1 つは指定してください。");
        }

        /// <summary>
        /// --output オプションを指定した場合に、指定したパスにレポートが出力されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--output")]
        [TestCase("-o")]
        public void TestSingleOutputOption(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\result\report1.xml", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.IsTrue(File.Exists(@"testdata\result\report1.xml"));
            AssertErrors();
        }

        /// <summary>
        /// --output オプションを複数指定した場合に、最後に指定したパスにレポートが出力されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--output")]
        [TestCase("-o")]
        public void TestMultiOutputOption(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\result\report2.xml {0} testdata\result\report3.xml", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.IsTrue(File.Exists(@"testdata\result\report3.xml"));
            AssertErrors();
        }

        /// <summary>
        /// --output オプションにすでに存在するファイルを指定した場合に、既存のファイルが上書きされることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--output")]
        [TestCase("-o")]
        public void TestOutputOptionWithExistingFile(string option)
        {
            File.WriteAllText(@"testdata\result\report4.xml", "foo");

            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\result\report4.xml", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreNotEqual("foo", File.ReadAllText(@"testdata\result\report4.xml"));
            AssertErrors();
        }

        /// <summary>
        /// --output オプションに存在しないディレクトリ内のファイルを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--output")]
        [TestCase("-o")]
        public void TestOutputOptionWithFileInNotExistingDirectory(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\result2\report5.xml", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"レポート出力先パスを修正してください。'testdata\result2' というディレクトリが見つかりません。");
        }

        /// <summary>
        /// --output オプションにディレクトリを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--output")]
        [TestCase("-o")]
        public void TestOutputOptionWithDirectory(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\result", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"レポート出力先パスを修正してください。'testdata\result' はディレクトリです。");
        }

        /// <summary>
        /// --output オプションに読み取り専用ファイルを指定した場合に、
        /// 分析自体は実行されるものの、レポートは出力されないことをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--output")]
        [TestCase("-o")]
        public void TestOutputOptionWithReadOnlyFile(string option)
        {
            File.WriteAllText(@"testdata\result\report6.xml", "");
            FileInfo file = new FileInfo(@"testdata\result\report6.xml");

            file.IsReadOnly = true;
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\result\report6.xml", option);
            int exitStatus = ExecuteDrFxConsole(argument);
            file.IsReadOnly = false;

            Assert.AreEqual(1, exitStatus);
            Assert.AreEqual("", File.ReadAllText(@"testdata\result\report6.xml"));
            AssertErrors(
                "Could not open output file : Access to the path '" + file.FullName + "' is denied..",
                "FxCopCmd.exe が終了コード 65 で異常終了したため、レポートの翻訳を中止します。");
        }

        /// <summary>
        /// --output オプションを指定しない場合に、カレントディレクトリに codeanalysisreport.xml が
        /// 作成されることをテストします。
        /// </summary>
        [Test]
        public void TestOutputOptionIsNotSpecified()
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll");
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.IsTrue(File.Exists("codeanalysisreport.xml"));
            AssertErrors();
        }

        /// <summary>
        /// --rule オプションを一度だけ指定した場合に、
        /// 指定したルールを使用してコード分析が実行されることをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--rule")]
        [TestCase("-r")]
        public void TestSingleRuleOption(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\rules\CustomRule1.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            AssertErrors();
        }

        /// <summary>
        /// --rule オプションを複数指定した場合に、
        /// 指定した全てのルールを使用してコード分析が実行されることをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--rule")]
        [TestCase("-r")]
        public void TestMultiRuleOption(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\rules\CustomRule1.dll {0} testdata\rules\CustomRule2.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(3, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA2210"));
            Assert.AreEqual(1, GetViolationMessageCount("CA9001"));
            Assert.AreEqual(1, GetViolationMessageCount("CA9002"));
            AssertErrors();
        }

        /// <summary>
        /// --rule オプションにワイルドカードを指定した場合に、
        /// ワイルドカードにマッチした全てのルールを使用してコード分析が実行されることをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--rule")]
        [TestCase("-r")]
        public void TestRuleOptionWithWildCard(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\rules\CustomRule*.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(3, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA2210"));
            Assert.AreEqual(1, GetViolationMessageCount("CA9001"));
            Assert.AreEqual(1, GetViolationMessageCount("CA9002"));
            AssertErrors();
        }

        /// <summary>
        /// --rule オプションに存在しないパスを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--rule")]
        [TestCase("-r")]
        public void TestRuleOptionWithNotExistingAssembly(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\rules\CustomRule3.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"ルールアセンブリのパスを修正してください。'testdata\rules\CustomRule3.dll' に一致するファイルが見つかりません。");
        }

        /// <summary>
        /// --rule オプションを複数指定し、その中に存在しないパスが含まれていた場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--rule")]
        [TestCase("-r")]
        public void TestRuleOptionContainsNotExistingAssembly(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\rules\CustomRule1.dll {0} testdata\rules\CustomRule3.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"ルールアセンブリのパスを修正してください。'testdata\rules\CustomRule3.dll' に一致するファイルが見つかりません。");
        }

        /// <summary>
        /// --rule オプションにディレクトリを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// <param name="option">オプション文字列。</param>
        [TestCase("--rule")]
        [TestCase("-r")]
        public void TestRuleOptionWithDirectory(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll {0} testdata\rules", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"ルールアセンブリのパスを修正してください。'testdata\rules' に一致するファイルが見つかりません。");
        }

        /// <summary>
        /// --rule オプションを指定しない場合に、
        /// デフォルトのルールのみでコード分析が実行されることをテストします。
        [Test]
        public void TestRuleOptionIsNotSpecified()
        {
            string argument = String.Format(@"--target testdata\bin\Target1.dll");
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(1, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --dependency オプションを 1 つだけ指定した場合に、アセンブリが正常に分析されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--dependency")]
        [TestCase("-d")]
        public void TestSingleDependencyOption(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target3.exe {0} testdata\lib3", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(1, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --dependency オプションを複数指定した場合に、アセンブリが正常に分析されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--dependency")]
        [TestCase("-d")]
        public void TestMultiDependencyOption(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target*.exe {0} testdata\lib3 {0} testdata\lib4", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(2, GetViolationMessageCount());
            Assert.AreEqual(2, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --dependency オプションにワイルドカードを指定した場合に、アセンブリが正常に分析されることをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--dependency")]
        [TestCase("-d")]
        public void TestDependencyOptionWithWildCard(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target*.exe {0} testdata\lib*", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(2, GetViolationMessageCount());
            Assert.AreEqual(2, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --dependency オプションに存在しないディレクトリを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--dependency")]
        [TestCase("-d")]
        public void TestDependencyOptionWithNotExistingDirectory(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target3.exe {0} testdata\lib5", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"依存先アセンブリ検索ディレクトリのパスを修正してください。'testdata\lib5' に一致するディレクトリが見つかりません。");
        }

        /// <summary>
        /// --dependency オプションが複数指定され、その中に存在しないディレクトリを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--dependency")]
        [TestCase("-d")]
        public void TestDependencyOptionContainsNotExistingDirectory(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target*.exe {0} testdata\lib3 {0} testdata\lib5", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"依存先アセンブリ検索ディレクトリのパスを修正してください。'testdata\lib5' に一致するディレクトリが見つかりません。");
        }

        /// <summary>
        /// --dependency オプションにファイルを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// </summary>
        /// <param name="option">オプション文字列。</param>
        [TestCase("--dependency")]
        [TestCase("-d")]
        public void TestDependencyOptionWithFile(string option)
        {
            string argument = String.Format(@"--target testdata\bin\Target3.exe {0} testdata\lib3\Dependency3.dll", option);
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"依存先アセンブリ検索ディレクトリのパスを修正してください。'testdata\lib3\Dependency3.dll' に一致するディレクトリが見つかりません。");
        }

        /// <summary>
        /// --dependency オプションを指定しない場合、アセンブリの依存関係が解決できれば、
        /// アセンブリが正常に分析されることをテストします。
        /// </summary>
        [Test]
        public void TestDependencyOptionIsNotSpecifySuccess()
        {
            string argument = String.Format(@"--target testdata\bin\Target*.dll");
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(2, GetViolationMessageCount());
            Assert.AreEqual(2, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// --dependency オプションを指定しない場合、アセンブリの依存関係が解決できなければ、
        /// アセンブリの分析が失敗することをテストします。
        /// </summary>
        [Test]
        public void TestDependencyOptionIsNotSpecifyFailure()
        {
            string argument = String.Format(@"--target testdata\bin\Target*.exe");
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
        }

        /// <summary>
        /// --dictionary オプションを 1 つだけ指定した場合に、アセンブリが正常に分析されることをテストします。
        /// </summary>
        [Test]
        public void TestSingleDictionaryOption()
        {
            string argument = @"--target testdata\bin2\Target5.exe --dictionary testdata\resources\dictionary1.xml";
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(1, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA1709"));
            StringAssert.Contains("'St'", GetViolationMessages("CA1709")[0]);
            AssertErrors();
        }

        /// <summary>
        /// --dictionary オプションを複数指定した場合に、最後に指定したディクショナリファイルを使用して、
        /// アセンブリが分析されることをテストします。
        /// </summary>
        [Test]
        public void TestMultiDictionaryOption()
        {
            string argument = @"--target testdata\bin2\Target5.exe --dictionary testdata\resources\dictionary1.xml --dictionary testdata\resources\dictionary2.xml";
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(1, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA1709"));
            StringAssert.Contains("'Dr'", GetViolationMessages("CA1709")[0]);
            AssertErrors();
        }

        /// <summary>
        /// --dictionary オプションに存在しないファイルを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// </summary>
        [Test]
        public void TestDictionaryOptionWithNotExistingFile()
        {
            string argument = @"--target testdata\bin2\Target5.exe --dictionary testdata\dictionary.xml";
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"カスタムディクショナリファイルのパスを修正してください。'testdata\dictionary.xml' というファイルが見つかりません。");
        }

        /// <summary>
        /// --dictionary オプションにディレクトリを指定した場合に、
        /// エラーメッセージが表示されて処理が終了することをテストします。
        /// </summary>
        [Test]
        public void TestDictionaryOptionWithDirectory()
        {
            string argument = @"--target testdata\bin2\Target5.exe --dictionary testdata\resources";
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(@"カスタムディクショナリファイルのパスを修正してください。'testdata\resources' というファイルが見つかりません。");
        }

        /// <summary>
        /// 単体テストアセンブリを、--search-gac オプションを指定して分析した場合に、
        /// 正常に分析されることをテストします。
        /// </summary>
        [Test]
        public void TestAnalyseUnitTestAssemblyWithSearchGacOption()
        {
            string argument = @"--target testdata\bin2\Target5Tests.dll --search-gac";
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(0, exitStatus);
            Assert.AreEqual(1, GetViolationMessageCount());
            Assert.AreEqual(1, GetViolationMessageCount("CA2210"));
            AssertErrors();
        }

        /// <summary>
        /// 単体テストアセンブリを、--search-gac オプションを指定せずに分析した場合に、
        /// 依存関係が解決できずに、分析が失敗することをテストします。
        /// </summary>
        [Test]
        public void TestAnalyseUnitTestAssemblyWithoutSearchGacOption()
        {
            string argument = @"--target testdata\bin2\Target5Tests.dll";
            int exitStatus = ExecuteDrFxConsole(argument);

            Assert.AreEqual(1, exitStatus);
            AssertErrors(
                "* Analysis was not performed; at least one valid rules assembly and one valid",
                "target file must be specified.",
                "* 2 total analysis engine exceptions.",
                "FxCopCmd.exe が終了コード 513 で異常終了したため、レポートの翻訳を中止します。");
        }

        /// <summary>
        /// <paramref name="argument"/> をコマンドライン引数にして、drfx-console を実行します。
        /// </summary>
        /// <param name="argument">コマンドライン引数として使用したい文字列。</param>
        /// <returns>drfx-console の終了ステータス。</returns>
        private int ExecuteDrFxConsole(string argument)
        {
            string[] args = argument.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
            Program program = new Program();
            return program.Boot(args);
        }

        /// <summary>
        /// drfx-console の出力メッセージの中から、違反メッセージの数を取得します。
        /// </summary>
        /// <returns>違反メッセージ数。違反メッセージ数が特定できない場合は null。</returns>
        private int? GetViolationMessageCount()
        {
            int? result = null;
            Match match = Regex.Match(this.stdout.ToString(), @"Writing (?<Count>[0-9]+?) messages\.\.\.", RegexOptions.Compiled | RegexOptions.Multiline);
            if (match.Success)
            {
                result = Int32.Parse(match.Groups["Count"].Value);
            }
            return result;
        }

        /// <summary>
        /// 指定されたチェック ID に一致する、FxCop の違反メッセージを取得します。
        /// </summary>
        /// <param name="checkId">メッセージ数を取得したいルールのチェック ID。</param>
        /// <returns>指定されたチェック ID を持つルールの違反メッセージの配列。</returns>
        private string[] GetViolationMessages(string checkId)
        {
            string pattern = "警告 : " + checkId;
            Regex regex = new Regex(pattern, RegexOptions.Compiled);

            List<string> messages = new List<string>();

            foreach (string warning in this.stdout.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
            {
                Match match = regex.Match(warning);
                if (match.Success)
                {
                    messages.Add(warning);
                }
            }

            return messages.ToArray();
        }

        /// <summary>
        /// 指定されたチェック ID に一致する、FxCop の違反メッセージの数を取得します。
        /// </summary>
        /// <param name="checkId">メッセージ数を取得したいルールのチェック ID。</param>
        /// <returns>指定されたチェック ID を持つルールの違反メッセージ数。</returns>
        private int GetViolationMessageCount(string checkId)
        {
            string pattern = "警告 : " + checkId;
            MatchCollection matches = Regex.Matches(this.stdout.ToString(), pattern);
            return matches.Count;
        }

        /// <summary>
        /// 期待通りのエラーメッセージが出力されたことを検証します。
        /// </summary>
        /// <param name="expected">エラーメッセージの期待値。</param>
        private void AssertErrors(params string[] expected)
        {
            string[] actual = this.stderr.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            Assert.AreEqual(expected.Length, actual.Length);
            for (int i = 0; i < expected.Length; i++)
            {
                Assert.AreEqual(expected[i], actual[i]);
            }
        }
    }
}
