/trunk/Bordecal.FxCop.Sdk/Testing/FxCopRunner.cs
# · C# · 311 lines · 214 code · 49 blank · 48 comment · 24 complexity · e62456e41ab2eeb00d28033bbea5548d MD5 · raw file
- //-----------------------------------------------------------------------
- // <copyright file="FxCopRunner.cs">
- // Copyright (c) Nicole Calinoiu. All rights reserved.
- // </copyright>
- //-----------------------------------------------------------------------
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using System.Text;
-
- using Microsoft.FxCop.Sdk;
-
- using Bordecal.FxCop.Sdk.Testing.AppDomainCustomization;
-
- namespace Bordecal.FxCop.Sdk.Testing
- {
- /// <summary>
- /// Helper class used to run FxCop and compare analysis results.
- /// </summary>
- public sealed class FxCopRunner
- {
- #region Constructors
-
- /// <summary>
- /// Initializes a new instance of the <see cref="FxCopRunner"/> class.
- /// </summary>
- /// <param name="fxCopCmdPath">The path to fxcopcmd.exe.</param>
- /// <param name="ruleFile">The path to the rule assembly file.</param>
- /// <param name="targetFiles">The path(s) to the analysis target file(s).</param>
- /// <param name="expectedResultsFiles">A list of paths to the FxCop report files containing expected rule violations.</param>
- /// <param name="assertFailMethod">The method to be invoked when failing a violation matching assertion.</param>
- public FxCopRunner(
- string fxCopCmdPath,
- string ruleFile,
- IEnumerable<string> targetFiles,
- IEnumerable<string> expectedResultsFiles,
- Action<string> assertFailMethod)
- : this(fxCopCmdPath, new string[] { ruleFile }, targetFiles, null, expectedResultsFiles, assertFailMethod)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="FxCopRunner"/> class.
- /// </summary>
- /// <param name="fxCopCmdPath">The path to fxcopcmd.exe.</param>
- /// <param name="ruleFiles">The path(s) to the rule assembly file(s).</param>
- /// <param name="targetFiles">The path(s) to the analysis target file(s).</param>
- /// <param name="ruleConfigurations">Instances of configurable rules for which non-default settings should be used. A <c>null</c> value may be supplied to indicate that no settings overrides are to be applied.</param>
- /// <param name="expectedResultsFiles">A list of paths to the FxCop report files containing expected rule violations.</param>
- /// <param name="assertFailMethod">The method to be invoked when failing a violation matching assertion.</param>
- public FxCopRunner(
- string fxCopCmdPath,
- IEnumerable<string> ruleFiles,
- IEnumerable<string> targetFiles,
- IEnumerable<IConfigurableRule> ruleConfigurations,
- IEnumerable<string> expectedResultsFiles,
- Action<string> assertFailMethod)
- {
- if (string.IsNullOrEmpty(fxCopCmdPath))
- {
- throw new ArgumentNullException("fxCopCmdPath");
- }
-
- if (targetFiles == null)
- {
- throw new ArgumentNullException("targetFiles");
- }
-
- if (expectedResultsFiles == null)
- {
- throw new ArgumentNullException("expectedResultsFiles");
- }
-
- if (assertFailMethod == null)
- {
- throw new ArgumentNullException("assertFailMethod");
- }
-
- this.ExpectedIssues = FxCopRunner.ExtractExpectedIssues(expectedResultsFiles, targetFiles);
- this.AssertFailMethod = assertFailMethod;
-
- string projectPath = FxCopProjectFile.Create(ruleFiles, targetFiles, ruleConfigurations);
- this.RunAnalysis(fxCopCmdPath, projectPath);
-
- File.Delete(projectPath);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="FxCopRunner"/> class.
- /// </summary>
- /// <param name="fxCopCmdPath">The path to fxcopcmd.exe.</param>
- /// <param name="projectPath">The path to the FxCop project that should be analyzed.</param>
- /// <param name="expectedResultsFiles">A list of paths to the FxCop report files containing expected rule violations.</param>
- /// <param name="assertFailMethod">The method to be invoked when failing a violation matching assertion.</param>
- public FxCopRunner(
- string fxCopCmdPath,
- string projectPath,
- IEnumerable<string> expectedResultsFiles,
- Action<string> assertFailMethod)
- {
- if (string.IsNullOrEmpty(fxCopCmdPath))
- {
- throw new ArgumentNullException("fxCopCmdPath");
- }
-
- if (string.IsNullOrEmpty(projectPath))
- {
- throw new ArgumentNullException("projectPath");
- }
-
- if (expectedResultsFiles == null)
- {
- throw new ArgumentNullException("expectedResultsFiles");
- }
-
- if (assertFailMethod == null)
- {
- throw new ArgumentNullException("assertFailMethod");
- }
-
- this.ExpectedIssues = FxCopRunner.ExtractExpectedIssues(expectedResultsFiles, new ProjectFile(projectPath).TargetPaths);
- this.RunAnalysis(fxCopCmdPath, projectPath);
- this.AssertFailMethod = assertFailMethod;
- }
-
- #endregion
-
- #region Properties
-
- private bool IsAnalysisComplete { get; set; }
-
- private IList<Issue> ActualIssues { get; set; }
-
- private IList<Issue> ExpectedIssues { get; set; }
-
- private Action<string> AssertFailMethod { get; set; }
-
- #endregion
-
- #region Methods
-
- private static void RunAnalysisInProcess(string fxCopCmdPath, string projectPath, string outputPath)
- {
- ProcessStartInfo startInfo = new ProcessStartInfo(fxCopCmdPath);
- startInfo.Arguments = string.Format(
- CultureInfo.InvariantCulture,
- @"/project:""{0}"" /out:""{1}""",
- projectPath,
- outputPath);
- startInfo.CreateNoWindow = true;
- startInfo.UseShellExecute = false;
- startInfo.RedirectStandardError = true;
- startInfo.RedirectStandardOutput = true;
-
- using (Process process = Process.Start(startInfo))
- {
- string error = process.StandardError.ReadToEnd();
- string output = process.StandardOutput.ReadToEnd();
-
- process.WaitForExit();
-
- if (process.ExitCode != 0)
- {
- throw new OperationCanceledException(error + "\r\n" + output);
- }
- }
- }
-
- private static IEnumerable<Issue> GetRuleIssues(IList<Issue> allIssues, string checkId)
- {
- return from issue in allIssues
- where issue.Message.CheckId == checkId
- select issue;
- }
-
- private static void RecordMismatches(StringBuilder builder, string checkId, IEnumerable<Issue> expectedIssues, IEnumerable<Issue> actualIssues, string reportPattern)
- {
- if (builder.Length > 0)
- {
- builder.AppendLine();
- }
-
- foreach (Issue issue in actualIssues.Except(expectedIssues, IssueEqualityComparer.Default))
- {
- if (builder.Length > 0)
- {
- builder.AppendLine();
- }
-
- builder.AppendFormat(
- CultureInfo.CurrentCulture,
- reportPattern,
- checkId,
- issue.File,
- issue.Line,
- issue.Text);
- }
- }
-
- private static IList<Issue> ExtractExpectedIssues(IEnumerable<string> expectedResultsFiles, IEnumerable<string> targetFiles)
- {
- return ReportReader.ExtractIssues(expectedResultsFiles)
- .Concat(new ExpectedViolationAttributeReader().GetIssues(targetFiles))
- .ToList();
- }
-
- private void RunAnalysis(string fxCopCmdPath, string projectPath)
- {
- string outputPath = Path.GetTempFileName();
-
- if (Debugger.IsAttached)
- {
- this.RunAnalysisInAppDomain(fxCopCmdPath, projectPath, outputPath);
- }
- else
- {
- FxCopRunner.RunAnalysisInProcess(fxCopCmdPath, projectPath, outputPath);
- }
-
- this.ActualIssues = ReportReader.ExtractIssues(outputPath);
- File.Delete(outputPath);
-
- this.IsAnalysisComplete = true;
- }
-
- [SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines",
- Justification = "False positive.")]
- private void RunAnalysisInAppDomain(string fxCopCmdPath, string projectPath, string outputPath)
- {
- AppDomainSetup setup = new AppDomainSetup();
- setup.ApplicationBase = Path.GetDirectoryName(this.GetType().Assembly.Location);
-
- AppDomain appDomain = AppDomain.CreateDomain("FxCopRunner", null, setup);
- try
- {
- using (ConsoleWriter writer = new ConsoleWriter())
- {
- writer.CustomizeAppDomain(appDomain);
- new ThreadNameClearer().CustomizeAppDomain(appDomain);
-
- setup = new AppDomainSetup();
- setup.ApplicationBase = Path.GetDirectoryName(fxCopCmdPath);
- setup.ConfigurationFile = fxCopCmdPath + ".config";
- new FusionStoreSetter(setup).CustomizeAppDomain(appDomain);
-
- int result = appDomain.ExecuteAssemblyByName(
- Path.GetFileNameWithoutExtension(fxCopCmdPath),
- string.Format(CultureInfo.InvariantCulture, @"/project:""{0}""", projectPath),
- string.Format(CultureInfo.InvariantCulture, @"/out:""{0}""", outputPath));
-
- if (result != 0)
- {
- throw new OperationCanceledException(writer.ToString());
- }
- }
- }
- finally
- {
- AppDomain.Unload(appDomain);
- }
- }
-
- /// <summary>
- /// Makes a test assertion that verifies whether the actual results of the
- /// FxCop run match the expected violation set.
- /// </summary>
- /// <remarks>
- /// Both of the following must be true for the assertion to pass:
- /// <list type="number">
- /// <item>
- /// <description>Every violation in the expected list must have a match in the actual list.</description>
- /// </item>
- /// <item>
- /// <description>Every violation in the actual list must have a match in the expected list.</description>
- /// </item>
- /// </list>
- /// </remarks>
- /// <param name="checkId">The CheckId of the FxCop rule for which violations should be matched.</param>
- public void AssertActualViolationsMatchExpectedViolations(string checkId)
- {
- if (string.IsNullOrEmpty(checkId))
- {
- throw new ArgumentNullException(checkId);
- }
-
- if (!this.IsAnalysisComplete)
- {
- throw new InvalidOperationException(Strings.FxCopRunNotComplete);
- }
-
- IEnumerable<Issue> expectedIssues = FxCopRunner.GetRuleIssues(this.ExpectedIssues, checkId);
- IEnumerable<Issue> actualIssues = FxCopRunner.GetRuleIssues(this.ActualIssues, checkId);
-
- StringBuilder builder = new StringBuilder();
-
- FxCopRunner.RecordMismatches(builder, checkId, actualIssues, expectedIssues, Strings.ExpectedViolationNotFound);
- FxCopRunner.RecordMismatches(builder, checkId, expectedIssues, actualIssues, Strings.UnexpectedViolationFound);
-
- if (builder.Length > 0)
- {
- this.AssertFailMethod(builder.ToString());
- }
- }
-
- #endregion
- }
- }