PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Agent.Worker/TestResults/TestRunPublisher.cs

https://gitlab.com/srbhgupta/vsts-agent
C# | 324 lines | 262 code | 32 blank | 30 comment | 31 complexity | 199dc6e0b38f87759cb122a448819e1f MD5 | raw file
  1. using Microsoft.TeamFoundation.TestManagement.WebApi;
  2. using Microsoft.VisualStudio.Services.Agent.Util;
  3. using Microsoft.VisualStudio.Services.WebApi;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.IO.Compression;
  10. using System.Linq;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. namespace Microsoft.VisualStudio.Services.Agent.Worker.TestResults
  14. {
  15. [ServiceLocator(Default = typeof(TestRunPublisher))]
  16. public interface ITestRunPublisher : IAgentService
  17. {
  18. void InitializePublisher(IExecutionContext executionContext, VssConnection connection, string projectName, IResultReader resultReader);
  19. Task<TestRun> StartTestRunAsync(TestRunData testRunData, CancellationToken cancellationToken = default(CancellationToken));
  20. Task AddResultsAsync(TestRun testRun, TestCaseResultData[] testResults, CancellationToken cancellationToken = default(CancellationToken));
  21. Task EndTestRunAsync(TestRunData testRunData, int testRunId, bool publishAttachmentsAsArchive = false, CancellationToken cancellationToken = default(CancellationToken));
  22. TestRunData ReadResultsFromFile(TestRunContext runContext, string filePath, string runName);
  23. TestRunData ReadResultsFromFile(TestRunContext runContext, string filePath);
  24. }
  25. public class TestRunPublisher : AgentService, ITestRunPublisher
  26. {
  27. #region Private
  28. const int BATCH_SIZE = 1000;
  29. const int PUBLISH_TIMEOUT = 300;
  30. const int TCM_MAX_FILESIZE = 104857600;
  31. private IExecutionContext _executionContext;
  32. private string _projectName;
  33. private ITestResultsServer _testResultsServer;
  34. private IResultReader _resultReader;
  35. #endregion
  36. #region Public API
  37. public void InitializePublisher(IExecutionContext executionContext, VssConnection connection, string projectName, IResultReader resultReader)
  38. {
  39. Trace.Entering();
  40. _executionContext = executionContext;
  41. _projectName = projectName;
  42. _resultReader = resultReader;
  43. connection.InnerHandler.Settings.SendTimeout = TimeSpan.FromSeconds(PUBLISH_TIMEOUT);
  44. _testResultsServer = HostContext.GetService<ITestResultsServer>();
  45. _testResultsServer.InitializeServer(connection);
  46. Trace.Leaving();
  47. }
  48. /// <summary>
  49. /// Publishes the given results to the test run.
  50. /// </summary>
  51. /// <param name="testResults">Results to be published.</param>
  52. public async Task AddResultsAsync(TestRun testRun, TestCaseResultData[] testResults, CancellationToken cancellationToken)
  53. {
  54. Trace.Entering();
  55. int noOfResultsToBePublished = BATCH_SIZE;
  56. _executionContext.Output(StringUtil.Loc("PublishingTestResults", testRun.Id));
  57. for (int i = 0; i < testResults.Length; i += BATCH_SIZE)
  58. {
  59. cancellationToken.ThrowIfCancellationRequested();
  60. if (i + BATCH_SIZE >= testResults.Length)
  61. {
  62. noOfResultsToBePublished = testResults.Length - i;
  63. }
  64. _executionContext.Output(StringUtil.Loc("TestResultsRemaining", (testResults.Length - i), testRun.Id));
  65. var currentBatch = new TestCaseResultData[noOfResultsToBePublished];
  66. Array.Copy(testResults, i, currentBatch, 0, noOfResultsToBePublished);
  67. List<TestCaseResult> testresults = await _testResultsServer.AddTestResultsToTestRunAsync(currentBatch, _projectName, testRun.Id, cancellationToken);
  68. for (int j = 0; j < noOfResultsToBePublished; j++)
  69. {
  70. cancellationToken.ThrowIfCancellationRequested();
  71. // Do not upload duplicate entries
  72. string[] attachments = testResults[i + j].Attachments;
  73. if (attachments != null)
  74. {
  75. Hashtable attachedFiles = new Hashtable(StringComparer.CurrentCultureIgnoreCase);
  76. var createAttachmentsTasks = attachments.Select(async attachment =>
  77. {
  78. if (!attachedFiles.ContainsKey(attachment))
  79. {
  80. TestAttachmentRequestModel reqModel = GetAttachmentRequestModel(attachment);
  81. if (reqModel != null)
  82. {
  83. await _testResultsServer.CreateTestResultAttachmentAsync(reqModel, _projectName, testRun.Id, testresults[j].Id, cancellationToken);
  84. }
  85. attachedFiles.Add(attachment, null);
  86. }
  87. });
  88. await Task.WhenAll(createAttachmentsTasks);
  89. }
  90. // Upload console log as attachment
  91. string consoleLog = testResults[i + j].ConsoleLog;
  92. TestAttachmentRequestModel attachmentRequestModel = GetConsoleLogAttachmentRequestModel(consoleLog);
  93. if (attachmentRequestModel != null)
  94. {
  95. await _testResultsServer.CreateTestResultAttachmentAsync(attachmentRequestModel, _projectName, testRun.Id, testresults[j].Id, cancellationToken);
  96. }
  97. }
  98. }
  99. Trace.Leaving();
  100. }
  101. /// <summary>
  102. /// Start a test run
  103. /// </summary>
  104. public async Task<TestRun> StartTestRunAsync(TestRunData testRunData, CancellationToken cancellationToken)
  105. {
  106. Trace.Entering();
  107. var testRun = await _testResultsServer.CreateTestRunAsync(_projectName, testRunData, cancellationToken);
  108. Trace.Leaving();
  109. return testRun;
  110. }
  111. /// <summary>
  112. /// Mark the test run as completed
  113. /// </summary>
  114. public async Task EndTestRunAsync(TestRunData testRunData, int testRunId, bool publishAttachmentsAsArchive = false, CancellationToken cancellationToken = default(CancellationToken))
  115. {
  116. Trace.Entering();
  117. RunUpdateModel updateModel = new RunUpdateModel(
  118. completedDate: testRunData.CompleteDate,
  119. state: TestRunState.Completed.ToString()
  120. );
  121. TestRun testRun = await _testResultsServer.UpdateTestRunAsync(_projectName, testRunId, updateModel, cancellationToken);
  122. // Uploading run level attachments, only after run is marked completed;
  123. // so as to make sure that any server jobs that acts on the uploaded data (like CoverAn job does for Coverage files)
  124. // have a fully published test run results, in case it wants to iterate over results
  125. if (publishAttachmentsAsArchive)
  126. {
  127. await UploadTestRunAttachmentsAsArchiveAsync(testRunId, testRunData.Attachments, cancellationToken);
  128. }
  129. else
  130. {
  131. await UploadTestRunAttachmentsIndividualAsync(testRunId, testRunData.Attachments, cancellationToken);
  132. }
  133. _executionContext.Output(string.Format(CultureInfo.CurrentCulture, "Published Test Run : {0}", testRun.WebAccessUrl));
  134. }
  135. /// <summary>
  136. /// Converts the given results file to TestRunData object
  137. /// </summary>
  138. /// <param name="filePath">File path</param>
  139. /// <returns>TestRunData</returns>
  140. public TestRunData ReadResultsFromFile(TestRunContext runContext, string filePath)
  141. {
  142. Trace.Entering();
  143. return _resultReader.ReadResults(_executionContext, filePath, runContext);
  144. }
  145. /// <summary>
  146. /// Converts the given results file to TestRunData object
  147. /// </summary>
  148. /// <param name="filePath">File path</param>
  149. /// <param name="runName">Run Name</param>
  150. /// <returns>TestRunData</returns>
  151. public TestRunData ReadResultsFromFile(TestRunContext runContext, string filePath, string runName)
  152. {
  153. Trace.Entering();
  154. runContext.RunName = runName;
  155. return _resultReader.ReadResults(_executionContext, filePath, runContext);
  156. }
  157. #endregion
  158. private async Task UploadTestRunAttachmentsAsArchiveAsync(int testRunId, string[] attachments, CancellationToken cancellationToken)
  159. {
  160. Trace.Entering();
  161. // Do not upload duplicate entries
  162. HashSet<string> attachedFiles = GetUniqueTestRunFiles(attachments);
  163. try
  164. {
  165. string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
  166. Directory.CreateDirectory(tempDirectory);
  167. string zipFile = Path.Combine(tempDirectory, "TestResults_" + testRunId + ".zip");
  168. File.Delete(zipFile); //if there's already file. remove silently without exception
  169. CreateZipFile(zipFile, attachedFiles);
  170. await CreateTestRunAttachmentAsync(testRunId, zipFile, cancellationToken);
  171. }
  172. catch (Exception ex)
  173. {
  174. _executionContext.Warning(StringUtil.Loc("UnableToArchiveResults", ex));
  175. await UploadTestRunAttachmentsIndividualAsync(testRunId, attachments, cancellationToken);
  176. }
  177. }
  178. private void CreateZipFile(string zipfileName, IEnumerable<string> files)
  179. {
  180. Trace.Entering();
  181. // Create and open a new ZIP file
  182. using (ZipArchive zip = ZipFile.Open(zipfileName, ZipArchiveMode.Create))
  183. {
  184. foreach (string file in files)
  185. {
  186. // Add the entry for each file
  187. zip.CreateEntryFromFile(file, Path.GetFileName(file), CompressionLevel.Optimal);
  188. }
  189. }
  190. }
  191. private async Task UploadTestRunAttachmentsIndividualAsync(int testRunId, string[] attachments, CancellationToken cancellationToken)
  192. {
  193. Trace.Entering();
  194. _executionContext.Debug("Uploading test run attachements individually");
  195. // Do not upload duplicate entries
  196. HashSet<string> attachedFiles = GetUniqueTestRunFiles(attachments);
  197. var attachFilesTasks = attachedFiles.Select(async file =>
  198. {
  199. await CreateTestRunAttachmentAsync(testRunId, file, cancellationToken);
  200. });
  201. await Task.WhenAll(attachFilesTasks);
  202. }
  203. private async Task CreateTestRunAttachmentAsync(int testRunId, string zipFile, CancellationToken cancellationToken)
  204. {
  205. Trace.Entering();
  206. TestAttachmentRequestModel reqModel = GetAttachmentRequestModel(zipFile);
  207. if (reqModel != null)
  208. {
  209. await _testResultsServer.CreateTestRunAttachmentAsync(reqModel, _projectName, testRunId, cancellationToken);
  210. }
  211. }
  212. private string GetAttachmentType(string file)
  213. {
  214. Trace.Entering();
  215. string fileName = Path.GetFileNameWithoutExtension(file);
  216. if (string.Compare(Path.GetExtension(file), ".coverage", StringComparison.OrdinalIgnoreCase) == 0)
  217. {
  218. return AttachmentType.CodeCoverage.ToString();
  219. }
  220. else if (string.Compare(Path.GetExtension(file), ".trx", StringComparison.OrdinalIgnoreCase) == 0)
  221. {
  222. return AttachmentType.TmiTestRunSummary.ToString();
  223. }
  224. else if (string.Compare(fileName, "testimpact", StringComparison.OrdinalIgnoreCase) == 0)
  225. {
  226. return AttachmentType.TestImpactDetails.ToString();
  227. }
  228. else if (string.Compare(fileName, "SystemInformation", StringComparison.OrdinalIgnoreCase) == 0)
  229. {
  230. return AttachmentType.IntermediateCollectorData.ToString();
  231. }
  232. else
  233. {
  234. return AttachmentType.GeneralAttachment.ToString();
  235. }
  236. }
  237. private TestAttachmentRequestModel GetAttachmentRequestModel(string attachment)
  238. {
  239. Trace.Entering();
  240. if (File.Exists(attachment) && new FileInfo(attachment).Length <= TCM_MAX_FILESIZE)
  241. {
  242. byte[] bytes = File.ReadAllBytes(attachment);
  243. string encodedData = Convert.ToBase64String(bytes);
  244. if (encodedData.Length <= TCM_MAX_FILESIZE)
  245. {
  246. return new TestAttachmentRequestModel(encodedData, Path.GetFileName(attachment), "", GetAttachmentType(attachment));
  247. }
  248. else
  249. {
  250. _executionContext.Warning(StringUtil.Loc("AttachmentExceededMaximum", attachment));
  251. }
  252. }
  253. else
  254. {
  255. _executionContext.Warning(StringUtil.Loc("NoSpaceOnDisk", attachment));
  256. }
  257. return null;
  258. }
  259. private TestAttachmentRequestModel GetConsoleLogAttachmentRequestModel(string consoleLog)
  260. {
  261. Trace.Entering();
  262. if (!string.IsNullOrWhiteSpace(consoleLog))
  263. {
  264. string consoleLogFileName = "Standard Console Output.log";
  265. if (consoleLog.Length <= TCM_MAX_FILESIZE)
  266. {
  267. byte[] bytes = System.Text.Encoding.UTF8.GetBytes(consoleLog);
  268. string encodedData = Convert.ToBase64String(bytes);
  269. return new TestAttachmentRequestModel(encodedData, consoleLogFileName, "",
  270. AttachmentType.ConsoleLog.ToString());
  271. }
  272. else
  273. {
  274. _executionContext.Warning(StringUtil.Loc("AttachmentExceededMaximum", consoleLogFileName));
  275. }
  276. }
  277. return null;
  278. }
  279. private HashSet<string> GetUniqueTestRunFiles(string[] attachments)
  280. {
  281. var attachedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  282. if (attachments != null)
  283. {
  284. foreach (string attachment in attachments)
  285. {
  286. attachedFiles.Add(attachment);
  287. }
  288. }
  289. return attachedFiles;
  290. }
  291. }
  292. }