PageRenderTime 74ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/Bordecal.FxCop.Sdk/Testing/FxCopRunner.cs

#
C# | 311 lines | 214 code | 49 blank | 48 comment | 24 complexity | e62456e41ab2eeb00d28033bbea5548d MD5 | raw file
  1. //-----------------------------------------------------------------------
  2. // <copyright file="FxCopRunner.cs">
  3. // Copyright (c) Nicole Calinoiu. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Text;
  14. using Microsoft.FxCop.Sdk;
  15. using Bordecal.FxCop.Sdk.Testing.AppDomainCustomization;
  16. namespace Bordecal.FxCop.Sdk.Testing
  17. {
  18. /// <summary>
  19. /// Helper class used to run FxCop and compare analysis results.
  20. /// </summary>
  21. public sealed class FxCopRunner
  22. {
  23. #region Constructors
  24. /// <summary>
  25. /// Initializes a new instance of the <see cref="FxCopRunner"/> class.
  26. /// </summary>
  27. /// <param name="fxCopCmdPath">The path to fxcopcmd.exe.</param>
  28. /// <param name="ruleFile">The path to the rule assembly file.</param>
  29. /// <param name="targetFiles">The path(s) to the analysis target file(s).</param>
  30. /// <param name="expectedResultsFiles">A list of paths to the FxCop report files containing expected rule violations.</param>
  31. /// <param name="assertFailMethod">The method to be invoked when failing a violation matching assertion.</param>
  32. public FxCopRunner(
  33. string fxCopCmdPath,
  34. string ruleFile,
  35. IEnumerable<string> targetFiles,
  36. IEnumerable<string> expectedResultsFiles,
  37. Action<string> assertFailMethod)
  38. : this(fxCopCmdPath, new string[] { ruleFile }, targetFiles, null, expectedResultsFiles, assertFailMethod)
  39. {
  40. }
  41. /// <summary>
  42. /// Initializes a new instance of the <see cref="FxCopRunner"/> class.
  43. /// </summary>
  44. /// <param name="fxCopCmdPath">The path to fxcopcmd.exe.</param>
  45. /// <param name="ruleFiles">The path(s) to the rule assembly file(s).</param>
  46. /// <param name="targetFiles">The path(s) to the analysis target file(s).</param>
  47. /// <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>
  48. /// <param name="expectedResultsFiles">A list of paths to the FxCop report files containing expected rule violations.</param>
  49. /// <param name="assertFailMethod">The method to be invoked when failing a violation matching assertion.</param>
  50. public FxCopRunner(
  51. string fxCopCmdPath,
  52. IEnumerable<string> ruleFiles,
  53. IEnumerable<string> targetFiles,
  54. IEnumerable<IConfigurableRule> ruleConfigurations,
  55. IEnumerable<string> expectedResultsFiles,
  56. Action<string> assertFailMethod)
  57. {
  58. if (string.IsNullOrEmpty(fxCopCmdPath))
  59. {
  60. throw new ArgumentNullException("fxCopCmdPath");
  61. }
  62. if (targetFiles == null)
  63. {
  64. throw new ArgumentNullException("targetFiles");
  65. }
  66. if (expectedResultsFiles == null)
  67. {
  68. throw new ArgumentNullException("expectedResultsFiles");
  69. }
  70. if (assertFailMethod == null)
  71. {
  72. throw new ArgumentNullException("assertFailMethod");
  73. }
  74. this.ExpectedIssues = FxCopRunner.ExtractExpectedIssues(expectedResultsFiles, targetFiles);
  75. this.AssertFailMethod = assertFailMethod;
  76. string projectPath = FxCopProjectFile.Create(ruleFiles, targetFiles, ruleConfigurations);
  77. this.RunAnalysis(fxCopCmdPath, projectPath);
  78. File.Delete(projectPath);
  79. }
  80. /// <summary>
  81. /// Initializes a new instance of the <see cref="FxCopRunner"/> class.
  82. /// </summary>
  83. /// <param name="fxCopCmdPath">The path to fxcopcmd.exe.</param>
  84. /// <param name="projectPath">The path to the FxCop project that should be analyzed.</param>
  85. /// <param name="expectedResultsFiles">A list of paths to the FxCop report files containing expected rule violations.</param>
  86. /// <param name="assertFailMethod">The method to be invoked when failing a violation matching assertion.</param>
  87. public FxCopRunner(
  88. string fxCopCmdPath,
  89. string projectPath,
  90. IEnumerable<string> expectedResultsFiles,
  91. Action<string> assertFailMethod)
  92. {
  93. if (string.IsNullOrEmpty(fxCopCmdPath))
  94. {
  95. throw new ArgumentNullException("fxCopCmdPath");
  96. }
  97. if (string.IsNullOrEmpty(projectPath))
  98. {
  99. throw new ArgumentNullException("projectPath");
  100. }
  101. if (expectedResultsFiles == null)
  102. {
  103. throw new ArgumentNullException("expectedResultsFiles");
  104. }
  105. if (assertFailMethod == null)
  106. {
  107. throw new ArgumentNullException("assertFailMethod");
  108. }
  109. this.ExpectedIssues = FxCopRunner.ExtractExpectedIssues(expectedResultsFiles, new ProjectFile(projectPath).TargetPaths);
  110. this.RunAnalysis(fxCopCmdPath, projectPath);
  111. this.AssertFailMethod = assertFailMethod;
  112. }
  113. #endregion
  114. #region Properties
  115. private bool IsAnalysisComplete { get; set; }
  116. private IList<Issue> ActualIssues { get; set; }
  117. private IList<Issue> ExpectedIssues { get; set; }
  118. private Action<string> AssertFailMethod { get; set; }
  119. #endregion
  120. #region Methods
  121. private static void RunAnalysisInProcess(string fxCopCmdPath, string projectPath, string outputPath)
  122. {
  123. ProcessStartInfo startInfo = new ProcessStartInfo(fxCopCmdPath);
  124. startInfo.Arguments = string.Format(
  125. CultureInfo.InvariantCulture,
  126. @"/project:""{0}"" /out:""{1}""",
  127. projectPath,
  128. outputPath);
  129. startInfo.CreateNoWindow = true;
  130. startInfo.UseShellExecute = false;
  131. startInfo.RedirectStandardError = true;
  132. startInfo.RedirectStandardOutput = true;
  133. using (Process process = Process.Start(startInfo))
  134. {
  135. string error = process.StandardError.ReadToEnd();
  136. string output = process.StandardOutput.ReadToEnd();
  137. process.WaitForExit();
  138. if (process.ExitCode != 0)
  139. {
  140. throw new OperationCanceledException(error + "\r\n" + output);
  141. }
  142. }
  143. }
  144. private static IEnumerable<Issue> GetRuleIssues(IList<Issue> allIssues, string checkId)
  145. {
  146. return from issue in allIssues
  147. where issue.Message.CheckId == checkId
  148. select issue;
  149. }
  150. private static void RecordMismatches(StringBuilder builder, string checkId, IEnumerable<Issue> expectedIssues, IEnumerable<Issue> actualIssues, string reportPattern)
  151. {
  152. if (builder.Length > 0)
  153. {
  154. builder.AppendLine();
  155. }
  156. foreach (Issue issue in actualIssues.Except(expectedIssues, IssueEqualityComparer.Default))
  157. {
  158. if (builder.Length > 0)
  159. {
  160. builder.AppendLine();
  161. }
  162. builder.AppendFormat(
  163. CultureInfo.CurrentCulture,
  164. reportPattern,
  165. checkId,
  166. issue.File,
  167. issue.Line,
  168. issue.Text);
  169. }
  170. }
  171. private static IList<Issue> ExtractExpectedIssues(IEnumerable<string> expectedResultsFiles, IEnumerable<string> targetFiles)
  172. {
  173. return ReportReader.ExtractIssues(expectedResultsFiles)
  174. .Concat(new ExpectedViolationAttributeReader().GetIssues(targetFiles))
  175. .ToList();
  176. }
  177. private void RunAnalysis(string fxCopCmdPath, string projectPath)
  178. {
  179. string outputPath = Path.GetTempFileName();
  180. if (Debugger.IsAttached)
  181. {
  182. this.RunAnalysisInAppDomain(fxCopCmdPath, projectPath, outputPath);
  183. }
  184. else
  185. {
  186. FxCopRunner.RunAnalysisInProcess(fxCopCmdPath, projectPath, outputPath);
  187. }
  188. this.ActualIssues = ReportReader.ExtractIssues(outputPath);
  189. File.Delete(outputPath);
  190. this.IsAnalysisComplete = true;
  191. }
  192. [SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines",
  193. Justification = "False positive.")]
  194. private void RunAnalysisInAppDomain(string fxCopCmdPath, string projectPath, string outputPath)
  195. {
  196. AppDomainSetup setup = new AppDomainSetup();
  197. setup.ApplicationBase = Path.GetDirectoryName(this.GetType().Assembly.Location);
  198. AppDomain appDomain = AppDomain.CreateDomain("FxCopRunner", null, setup);
  199. try
  200. {
  201. using (ConsoleWriter writer = new ConsoleWriter())
  202. {
  203. writer.CustomizeAppDomain(appDomain);
  204. new ThreadNameClearer().CustomizeAppDomain(appDomain);
  205. setup = new AppDomainSetup();
  206. setup.ApplicationBase = Path.GetDirectoryName(fxCopCmdPath);
  207. setup.ConfigurationFile = fxCopCmdPath + ".config";
  208. new FusionStoreSetter(setup).CustomizeAppDomain(appDomain);
  209. int result = appDomain.ExecuteAssemblyByName(
  210. Path.GetFileNameWithoutExtension(fxCopCmdPath),
  211. string.Format(CultureInfo.InvariantCulture, @"/project:""{0}""", projectPath),
  212. string.Format(CultureInfo.InvariantCulture, @"/out:""{0}""", outputPath));
  213. if (result != 0)
  214. {
  215. throw new OperationCanceledException(writer.ToString());
  216. }
  217. }
  218. }
  219. finally
  220. {
  221. AppDomain.Unload(appDomain);
  222. }
  223. }
  224. /// <summary>
  225. /// Makes a test assertion that verifies whether the actual results of the
  226. /// FxCop run match the expected violation set.
  227. /// </summary>
  228. /// <remarks>
  229. /// Both of the following must be true for the assertion to pass:
  230. /// <list type="number">
  231. /// <item>
  232. /// <description>Every violation in the expected list must have a match in the actual list.</description>
  233. /// </item>
  234. /// <item>
  235. /// <description>Every violation in the actual list must have a match in the expected list.</description>
  236. /// </item>
  237. /// </list>
  238. /// </remarks>
  239. /// <param name="checkId">The CheckId of the FxCop rule for which violations should be matched.</param>
  240. public void AssertActualViolationsMatchExpectedViolations(string checkId)
  241. {
  242. if (string.IsNullOrEmpty(checkId))
  243. {
  244. throw new ArgumentNullException(checkId);
  245. }
  246. if (!this.IsAnalysisComplete)
  247. {
  248. throw new InvalidOperationException(Strings.FxCopRunNotComplete);
  249. }
  250. IEnumerable<Issue> expectedIssues = FxCopRunner.GetRuleIssues(this.ExpectedIssues, checkId);
  251. IEnumerable<Issue> actualIssues = FxCopRunner.GetRuleIssues(this.ActualIssues, checkId);
  252. StringBuilder builder = new StringBuilder();
  253. FxCopRunner.RecordMismatches(builder, checkId, actualIssues, expectedIssues, Strings.ExpectedViolationNotFound);
  254. FxCopRunner.RecordMismatches(builder, checkId, expectedIssues, actualIssues, Strings.UnexpectedViolationFound);
  255. if (builder.Length > 0)
  256. {
  257. this.AssertFailMethod(builder.ToString());
  258. }
  259. }
  260. #endregion
  261. }
  262. }