diff --git a/TestPlatform.sln b/TestPlatform.sln index 4db14778e0..2e140d8990 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29025.244 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.779 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED0C35EB-7F31-4841-A24F-8EB708FFA959}" EndProject @@ -42,6 +42,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{46250E12-4CF1-4051-B4A7-80C8C06E0068}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logger", "Logger", "{020E15EA-731F-4667-95AF-226671E0C3AE}" + ProjectSection(SolutionItems) = preProject + test\Microsoft.TestPlatform.AcceptanceTests\TestResults.html = test\Microsoft.TestPlatform.AcceptanceTests\TestResults.html + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Performance", "Performance", "{595BE9C1-E10F-4E50-938A-E6C248D3F950}" EndProject @@ -172,6 +175,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator.UnitTests" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTestProject", "test\TestAssets\DiscoveryTestProject\DiscoveryTestProject.csproj", "{D16ACC60-52F8-4912-8870-5733A9F6852D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.HtmlLogger", "src\Microsoft.TestPlatform.Extensions.HtmlLogger\Microsoft.TestPlatform.Extensions.HtmlLogger.csproj", "{236A71E3-01DA-4679-9DFF-16A8E079ACFF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests", "test\Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests\Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj", "{41248B96-6E15-4E5E-A78F-859897676814}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -866,6 +873,30 @@ Global {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x64.Build.0 = Release|Any CPU {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x86.ActiveCfg = Release|Any CPU {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x86.Build.0 = Release|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|x64.ActiveCfg = Debug|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|x64.Build.0 = Debug|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|x86.ActiveCfg = Debug|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|x86.Build.0 = Debug|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|Any CPU.Build.0 = Release|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|x64.ActiveCfg = Release|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|x64.Build.0 = Release|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|x86.ActiveCfg = Release|Any CPU + {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|x86.Build.0 = Release|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Debug|x64.ActiveCfg = Debug|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Debug|x64.Build.0 = Debug|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Debug|x86.ActiveCfg = Debug|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Debug|x86.Build.0 = Debug|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Release|Any CPU.Build.0 = Release|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Release|x64.ActiveCfg = Release|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Release|x64.Build.0 = Release|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Release|x86.ActiveCfg = Release|Any CPU + {41248B96-6E15-4E5E-A78F-859897676814}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -940,6 +971,8 @@ Global {69F5FF81-5615-4F06-B83C-FCF979BB84CA} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} {D16ACC60-52F8-4912-8870-5733A9F6852D} = {8DA7CBD9-F17E-41B6-90C4-CFF55848A25A} + {236A71E3-01DA-4679-9DFF-16A8E079ACFF} = {5E7F18A8-F843-4C8A-AB02-4C7D9205C6CF} + {41248B96-6E15-4E5E-A78F-859897676814} = {020E15EA-731F-4667-95AF-226671E0C3AE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD} diff --git a/scripts/build.ps1 b/scripts/build.ps1 index c68528a0ed..ee4471e121 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -298,7 +298,7 @@ function Publish-Package # If there are some dependencies for the logger assemblies, those need to be moved too. # Ideally we should just be publishing the loggers to the Extensions folder. - $loggers = @("Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll", "Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.pdb") + $loggers = @("Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll", "Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.pdb", "Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.dll", "Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.pdb") foreach($file in $loggers) { Write-Verbose "Move-Item $fullCLRPackageDir\$file $fullCLRExtensionsDir -Force" Move-Item $fullCLRPackageDir\$file $fullCLRExtensionsDir -Force @@ -307,10 +307,12 @@ function Publish-Package Move-Item $coreCLR20PackageDir\$file $coreCLRExtensionsDir -Force } - # Move trx logger resource dlls + # Move logger resource dlls if($TPB_LocalizedBuild) { Move-Loc-Files $fullCLRPackageDir $fullCLRExtensionsDir "Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.resources.dll" Move-Loc-Files $coreCLR20PackageDir $coreCLRExtensionsDir "Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.resources.dll" + Move-Loc-Files $fullCLRPackageDir $fullCLRExtensionsDir "Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.resources.dll" + Move-Loc-Files $coreCLR20PackageDir $coreCLRExtensionsDir "Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.resources.dll" } # Copy Blame Datacollector to Extensions folder. @@ -354,7 +356,7 @@ function Publish-Package } # Move TestHostRuntimeProvider resource dlls - if($TPB_LocalizedBuild) { + if ($TPB_LocalizedBuild) { Move-Loc-Files $fullCLRPackageDir $fullCLRExtensionsDir "Microsoft.TestPlatform.TestHostRuntimeProvider.resources.dll" Move-Loc-Files $coreCLR20PackageDir $coreCLRExtensionsDir "Microsoft.TestPlatform.TestHostRuntimeProvider.resources.dll" } diff --git a/scripts/build.sh b/scripts/build.sh index 575a9810c1..2a04c38732 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -314,7 +314,7 @@ function publish_package() # Note Note: If there are some dependencies for the logger assemblies, those need to be moved too. # Ideally we should just be publishing the loggers to the Extensions folder. - loggers=("Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll" "Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.pdb") + loggers=("Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll" "Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.pdb" "Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.dll" "Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.pdb") for i in ${loggers[@]}; do mv $packageDir/${i} $extensionsDir done diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props index c9d3ebcd31..50026c9a5a 100644 --- a/scripts/build/TestPlatform.Dependencies.props +++ b/scripts/build/TestPlatform.Dependencies.props @@ -6,8 +6,8 @@ 15.5.0 - 1.3.1 - 1.3.1 + 1.4.0 + 1.4.0 1.0.3-preview 2.3.1 diff --git a/scripts/build/TestPlatform.Localization.targets b/scripts/build/TestPlatform.Localization.targets index b3af85662c..084c042101 100644 --- a/scripts/build/TestPlatform.Localization.targets +++ b/scripts/build/TestPlatform.Localization.targets @@ -29,6 +29,8 @@ + @@ -37,10 +39,10 @@ - + - + @@ -50,7 +52,7 @@ - + diff --git a/scripts/verify-nupkgs.ps1 b/scripts/verify-nupkgs.ps1 index eb20f2ed9f..b002ab1610 100644 --- a/scripts/verify-nupkgs.ps1 +++ b/scripts/verify-nupkgs.ps1 @@ -14,12 +14,12 @@ function Verify-Nuget-Packages($packageDirectory) Write-Log "Starting Verify-Nuget-Packages." $expectedNumOfFiles = @{"Microsoft.CodeCoverage" = 29; "Microsoft.NET.Test.Sdk" = 13; - "Microsoft.TestPlatform" = 423; + "Microsoft.TestPlatform" = 437; "Microsoft.TestPlatform.Build" = 19; - "Microsoft.TestPlatform.CLI" = 303; + "Microsoft.TestPlatform.CLI" = 317; "Microsoft.TestPlatform.Extensions.TrxLogger" = 33; "Microsoft.TestPlatform.ObjectModel" = 62; - "Microsoft.TestPlatform.Portable" = 474; + "Microsoft.TestPlatform.Portable" = 502; "Microsoft.TestPlatform.TestHost" = 140; "Microsoft.TestPlatform.TranslationLayer" = 121} diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj index d89ff11ad2..55258c0977 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj @@ -37,3 +37,4 @@ + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs index de8b1bac5b..461679f2de 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs @@ -132,5 +132,11 @@ public string[] GetFiles(string path, string searchPattern, SearchOption searchO { return Directory.GetFiles(path, searchPattern, searchOption); } + + /// + public void Delete(string path) + { + File.Delete(path); + } } } diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs index fa7393242e..4c73017044 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs @@ -134,5 +134,11 @@ public interface IFileHelper /// Search option /// string[] string[] GetFiles(string path, string searchPattern, SearchOption searchOption); + + /// + /// Deletes the specified file + /// + /// + void Delete(string path); } } diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Constants.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Constants.cs new file mode 100644 index 0000000000..fb91883432 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Constants.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + public static class Constants + { + /// + /// Uri used to uniquely identify the Html logger. + /// + public const string ExtensionUri = "logger://Microsoft/TestPlatform/HtmlLogger/v1"; + + /// + /// Alternate user friendly string to uniquely identify the console logger. + /// + public const string FriendlyName = "Html"; + + /// + /// The file extension of xml file + /// + public const string XmlFileExtension = "xml"; + + /// + /// The file extension of html file + /// + public const string HtmlFileExtension = "html"; + + /// /// + /// Property Id storing the TestType. + /// + public const string TestTypePropertyIdentifier = "TestType"; + + /// + /// Ordered test type guid + /// + public static readonly Guid OrderedTestTypeGuid = new Guid("ec4800e8-40e5-4ab3-8510-b8bf29b1904d"); + + /// + /// Property Id storing the ParentExecutionId. + /// + public const string ParentExecutionIdPropertyIdentifier = "ParentExecId"; + + /// + /// Property Id storing the ExecutionId. + /// + public const string ExecutionIdPropertyIdentifier = "ExecutionId"; + + /// + /// Log file parameter key + /// + public const string LogFileNameKey = "LogFileName"; + + public static readonly TestProperty ExecutionIdProperty = TestProperty.Register("ExecutionId", ExecutionIdPropertyIdentifier, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult)); + + public static readonly TestProperty ParentExecIdProperty = TestProperty.Register("ParentExecId", ParentExecutionIdPropertyIdentifier, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult)); + + public static readonly TestProperty TestTypeProperty = TestProperty.Register("TestType", TestTypePropertyIdentifier, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult)); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Friends.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Friends.cs new file mode 100644 index 0000000000..6b0b46c20b --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Friends.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +#region Test Assemblies + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] + +#endregion diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Html.xslt b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Html.xslt new file mode 100644 index 0000000000..8ecccc1626 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Html.xslt @@ -0,0 +1,203 @@ + + + + + + +

Test run details

+ + + + + +
+ + + + + + + +
+
+
+ + + +
+
+
+
+
+
+ + + +

Failed Results

+ +
+

All Results

+
+ + + + + +
+ +
+
+
+ + + + + +
+
+ +
+
+
+
+ + + + + +
+ +
+ + +
+
+ +
+ + +
+
+ + +
+ + +
+
+
+ + +
+
+ + + Error:
+
+ + + Stack trace:
+
+ + + Failed  : 
+
+ + + Passed  : 
+
+ + + Skipped : 
+
+ + + Total tests

+
+ + + Run duration

+
+ + + Pass percentage
%

+
+ + + + + + + + +
+
+ + +   + +
diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs new file mode 100644 index 0000000000..650aba384c --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs @@ -0,0 +1,410 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.Serialization; + + using ObjectModel; + using Utilities; + using Utilities.Helpers; + using Utilities.Helpers.Interfaces; + + using HtmlResource = Resources.Resources; + using HtmlLoggerConstants = Constants; + + /// + /// Logger for generating Html. + /// + [FriendlyName(HtmlLoggerConstants.FriendlyName)] + [ExtensionUri(HtmlLoggerConstants.ExtensionUri)] + public class HtmlLogger : ITestLoggerWithParameters + { + private readonly IFileHelper fileHelper; + private readonly XmlObjectSerializer xmlSerializer; + private readonly IHtmlTransformer htmlTransformer; + private Dictionary parametersDictionary; + + public HtmlLogger() + : this(new FileHelper(), new HtmlTransformer(), new DataContractSerializer(typeof(TestRunDetails))) + { + } + + public HtmlLogger(IFileHelper fileHelper, IHtmlTransformer htmlTransformer, + XmlObjectSerializer dataContractSerializer) + { + this.fileHelper = fileHelper; + this.htmlTransformer = htmlTransformer; + this.xmlSerializer = dataContractSerializer; + } + + /// + /// Gets the directory under which default html file and test results attachments should be saved. + /// + public string TestResultsDirPath { get; private set; } + + /// + /// Total results are stored in sequential order + /// + /// + public ConcurrentDictionary Results { get; private set; } + + /// + /// + /// + public ConcurrentDictionary ResultCollectionDictionary { get; private set; } + + /// + /// Test results stores all the summary and the details of every results in hierarchical order. + /// + public TestRunDetails TestRunDetails { get; private set; } + + /// + /// Total passed tests in the test results. + /// + public int PassedTests { get; private set; } + + /// + /// Total failed tests in the test results. + /// + public int FailedTests { get; private set; } + + /// + /// Total tests in the results. + /// + public int TotalTests { get; private set; } + + /// + /// Total skipped tests in the results. + /// + public int SkippedTests { get; private set; } + + /// + /// Path to the xml file. + /// + public string XmlFilePath { get; private set; } + + /// + /// path to html file. + /// + public string HtmlFilePath { get; private set; } + + /// + public void Initialize(TestLoggerEvents events, string testResultsDirPath) + { + if (events == null) + { + throw new ArgumentNullException(nameof(events)); + } + + if (string.IsNullOrEmpty(testResultsDirPath)) + { + throw new ArgumentNullException(nameof(testResultsDirPath)); + } + + // Register for the events. + events.TestRunMessage += TestMessageHandler; + events.TestResult += TestResultHandler; + events.TestRunComplete += TestRunCompleteHandler; + + TestResultsDirPath = testResultsDirPath; + TestRunDetails = new TestRunDetails(); + Results = new ConcurrentDictionary(); + ResultCollectionDictionary = new ConcurrentDictionary(); + } + + /// + public void Initialize(TestLoggerEvents events, Dictionary parameters) + { + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (parameters.Count == 0) + { + throw new ArgumentException("No default parameters added", nameof(parameters)); + } + + parametersDictionary = parameters; + this.Initialize(events, parameters[DefaultLoggerParameterNames.TestRunDirectory]); + } + + /// + /// Handles the message level information like warnings, errors etc.. + /// + /// + /// + public void TestMessageHandler(object sender, TestRunMessageEventArgs e) + { + ValidateArg.NotNull(sender, "sender"); + ValidateArg.NotNull(e, "e"); + + switch (e.Level) + { + case TestMessageLevel.Informational: + TestRunDetails.RunLevelMessageInformational.Add(e.Message); + break; + case TestMessageLevel.Warning: + TestRunDetails.RunLevelMessageErrorAndWarning.Add(e.Message); + break; + case TestMessageLevel.Error: + TestRunDetails.RunLevelMessageErrorAndWarning.Add(e.Message); + break; + default: + EqtTrace.Info("htmlLogger.TestMessageHandler: The test message level is unrecognized: {0}", + e.Level.ToString()); + break; + } + } + + /// + /// Handles the result coming from vs test and store it in test results. + /// + /// + /// + public void TestResultHandler(object sender, TestResultEventArgs e) + { + ValidateArg.NotNull(sender, "sender"); + ValidateArg.NotNull(e, "e"); + + var testResult = new ObjectModel.TestResult + { + DisplayName = e.Result.DisplayName ?? e.Result.TestCase.FullyQualifiedName, + FullyQualifiedName = e.Result.TestCase.FullyQualifiedName, + ErrorStackTrace = e.Result.ErrorStackTrace, + ErrorMessage = e.Result.ErrorMessage, + TestResultId = e.Result.TestCase.Id, + Duration = GetFormattedDurationString(e.Result.Duration), + ResultOutcome = e.Result.Outcome + }; + + var executionId = GetExecutionId(e.Result); + var parentExecutionId = GetParentExecutionId(e.Result); + + ResultCollectionDictionary.TryGetValue(e.Result.TestCase.Source, out var testResultCollection); + if (testResultCollection == null) + { + testResultCollection = new TestResultCollection(e.Result.TestCase.Source) + { + ResultList = new List(), + FailedResultList = new List(), + }; + ResultCollectionDictionary.TryAdd(e.Result.TestCase.Source, testResultCollection); + TestRunDetails.ResultCollectionList.Add(testResultCollection); + } + + TotalTests++; + switch (e.Result.Outcome) + { + case TestOutcome.Failed: + FailedTests++; + break; + case TestOutcome.Passed: + PassedTests++; + break; + case TestOutcome.Skipped: + SkippedTests++; + break; + default: + break; + } + + Results.TryAdd(executionId, testResult); + + // Check for parent execution id to store the test results in hierarchical way + if (parentExecutionId == Guid.Empty) + { + if (e.Result.Outcome == TestOutcome.Failed) + { + testResultCollection.FailedResultList.Add(testResult); + } + + testResultCollection.ResultList.Add(testResult); + } + else + { + AddToParentResult(parentExecutionId, testResult); + } + } + + private void AddToParentResult(Guid parentExecutionId, ObjectModel.TestResult testResult) + { + if (Results.TryGetValue(parentExecutionId, out var parentTestResult)) + { + if (parentTestResult.InnerTestResults == null) + parentTestResult.InnerTestResults = new List(); + + parentTestResult.InnerTestResults.Add(testResult); + } + } + + /// + /// Creates a summary of tests and populates the html file by transforming the xml file with help of xslt file. + /// + /// + /// + public void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) + { + TestRunDetails.Summary = new TestRunSummary + { + FailedTests = FailedTests, + PassedTests = PassedTests, + TotalTests = TotalTests, + SkippedTests = SkippedTests, + PassPercentage = (PassedTests * 100) / TotalTests, + TotalRunTime = GetFormattedDurationString(e.ElapsedTimeInRunningTests), + }; + var isLogFileNameParameterExists = parametersDictionary.TryGetValue(HtmlLoggerConstants.LogFileNameKey, + out string logFileNameValue); + if (isLogFileNameParameterExists && !string.IsNullOrWhiteSpace(logFileNameValue)) + { + HtmlFilePath = Path.Combine(TestResultsDirPath, logFileNameValue); + } + + PopulateHtmlFile(); + } + + private void PopulateHtmlFile() + { + try + { + var fileName = string.Format(CultureInfo.CurrentCulture, "{0}_{1}_{2}", + Environment.GetEnvironmentVariable("UserName"), Environment.MachineName, + FormatDateTimeForRunName(DateTime.Now)); + + XmlFilePath = GetFilePath(HtmlLoggerConstants.XmlFileExtension, fileName); + + using (var xmlStream = fileHelper.GetStream(XmlFilePath, FileMode.Create)) + { + xmlSerializer.WriteObject(xmlStream, TestRunDetails); + } + + if (string.IsNullOrEmpty(HtmlFilePath)) + { + HtmlFilePath = GetFilePath(HtmlLoggerConstants.HtmlFileExtension, fileName); + } + + htmlTransformer.Transform(XmlFilePath, HtmlFilePath); + } + catch (Exception ex) + { + EqtTrace.Error("HtmlLogger : Failed to populate html file. Exception : {0}", + ex.ToString()); + ConsoleOutput.Instance.Error(false, string.Concat(HtmlResource.HtmlLoggerError), ex.Message); + return; + } + finally + { + if (XmlFilePath != null) + { + this.fileHelper.Delete(XmlFilePath); + } + } + + var htmlFilePathMessage = string.Format(CultureInfo.CurrentCulture, HtmlResource.HtmlFilePath, HtmlFilePath); + EqtTrace.Info(htmlFilePathMessage); + ConsoleOutput.Instance.Information(false, htmlFilePathMessage); + } + + private string GetFilePath(string fileExtension, string fileName) + { + var fullFileFormat = $".{fileExtension}"; + return Path.Combine(TestResultsDirPath, string.Concat("TestResult_", fileName, fullFileFormat)); + } + + private string FormatDateTimeForRunName(DateTime timeStamp) + { + return timeStamp.ToString("yyyyMMdd_HHmmss", DateTimeFormatInfo.InvariantInfo); + } + + /// + /// Gives the parent execution id of a TestResult. + /// + /// + /// + private Guid GetParentExecutionId(TestPlatform.ObjectModel.TestResult testResult) + { + var parentExecutionIdProperty = testResult.Properties.FirstOrDefault(property => + property.Id.Equals(HtmlLoggerConstants.ParentExecutionIdPropertyIdentifier)); + return parentExecutionIdProperty == null + ? Guid.Empty + : testResult.GetPropertyValue(parentExecutionIdProperty, Guid.Empty); + } + + /// + /// Gives the execution id of a TestResult. + /// + /// + /// + private Guid GetExecutionId(TestPlatform.ObjectModel.TestResult testResult) + { + var executionIdProperty = testResult.Properties.FirstOrDefault(property => + property.Id.Equals(HtmlLoggerConstants.ExecutionIdPropertyIdentifier)); + var executionId = Guid.Empty; + + if (executionIdProperty != null) + { + executionId = testResult.GetPropertyValue(executionIdProperty, Guid.Empty); + } + + return executionId.Equals(Guid.Empty) ? Guid.NewGuid() : executionId; + } + + /// + /// Converts the time span format to readable string. + /// + /// + /// + internal string GetFormattedDurationString(TimeSpan duration) + { + if (duration == default(TimeSpan)) + { + return null; + } + + var time = new List(); + if (duration.Days > 0) + { + time.Add("> 1d"); + } + else + { + if (duration.Hours > 0) + { + time.Add(duration.Hours + "h"); + } + + if (duration.Minutes > 0) + { + time.Add(duration.Minutes + "m"); + } + + if (duration.Hours == 0) + { + if (duration.Seconds > 0) + { + time.Add(duration.Seconds + "s"); + } + + if (duration.Milliseconds > 0 && duration.Minutes == 0) + { + time.Add(duration.Milliseconds + "ms"); + } + } + } + + return time.Count == 0 ? "< 1ms" : string.Join(" ", time); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlTransformer.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlTransformer.cs new file mode 100644 index 0000000000..3eca547b4e --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlTransformer.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger +{ + using System.Xml; + using System.Xml.Xsl; + + /// + /// Html transformer transforms the xml file to html file using xslt file. + /// + internal class HtmlTransformer : IHtmlTransformer + { + private readonly XslCompiledTransform xslTransform; + + /// + /// The following function invokes the compiled transform and Loads the xslt file. + /// + public HtmlTransformer() + { + xslTransform = new XslCompiledTransform(); + xslTransform.Load(XmlReader.Create(this.GetType().Assembly.GetManifestResourceStream("Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.Html.xslt") ?? throw new InvalidOperationException())); + } + + /// + /// It transforms the xml file to html file. + /// + public void Transform(string xmlFile, string htmlFile) + { + xslTransform.Transform(xmlFile, htmlFile); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/IHtmlTransformer.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/IHtmlTransformer.cs new file mode 100644 index 0000000000..225c56190d --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/IHtmlTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger +{ + public interface IHtmlTransformer + { + /// + /// It transforms the xml file to html file. + /// + /// + /// + void Transform(string xmlFile, string htmlFile); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj new file mode 100644 index 0000000000..9025a0d8c1 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj @@ -0,0 +1,44 @@ + + + + ..\..\ + + + + Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger + true + netstandard2.0;net451 + netstandard2.0 + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger + + + diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/HtmlTestResult.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/HtmlTestResult.cs new file mode 100644 index 0000000000..0285f114e0 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/HtmlTestResult.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.Serialization; + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.ObjectModel +{ + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System; + using System.Collections.Generic; + + /// + /// Test results stores the relevant information to show on html file + /// + [DataContract] + public class TestResult + { + /// + /// Fully qualified name of the Test Result. + /// + [DataMember] public string FullyQualifiedName { get; set; } + + /// + /// Unique identifier for test result + /// + [DataMember] public Guid TestResultId { get; set; } + + /// + /// Display Name for the particular Test Result + /// + [DataMember] public string DisplayName { get; set; } + + /// + /// The error stack trace of the Test Result. + /// + [DataMember] public string ErrorStackTrace { get; set; } + + /// + /// Error message of the Test Result. + /// + [DataMember] public string ErrorMessage { get; set; } + + /// + /// Enum that determines the outcome of the test case + /// + [DataMember] public TestOutcome ResultOutcome { get; set; } + + /// + /// Total timespan of the TestResult + /// + [DataMember] public string Duration { get; set; } + + /// + /// The list of TestResults that are children to the current Test Result. + /// + [DataMember] public List InnerTestResults { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestResultCollection.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestResultCollection.cs new file mode 100644 index 0000000000..b2778d3e3c --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestResultCollection.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.ObjectModel +{ + using System.Collections.Generic; + + /// + /// Stores the list of failed results and list of all results corresponding to the source. + /// + [DataContract] + public class TestResultCollection + { + private readonly string source; + + public TestResultCollection(string source) => this.source = source ?? throw new ArgumentNullException(nameof(source)); + + /// + /// Source of the test dll. + /// + [DataMember] public string Source + { + get => this.source; + private set { } + } + + /// + /// Hash id of source. + /// + [DataMember] public int Id + { + get => this.source.GetHashCode(); + private set { } + } + + /// + /// List of test results. + /// + [DataMember] public List ResultList { get; set; } + + /// + /// List of failed test results. + /// + [DataMember] public List FailedResultList { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestRunDetails.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestRunDetails.cs new file mode 100644 index 0000000000..5edfbfd364 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestRunDetails.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.ObjectModel +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// It stores the all relevant information of the test run. + /// + [DataContract] + public sealed class TestRunDetails + { + /// + /// Test run summary of all test results. + /// + [DataMember] public TestRunSummary Summary { get; set; } + + /// + /// List of informational run level messages. + /// + [DataMember] public List RunLevelMessageInformational = new List(); + + /// + /// List of error and warning messages. + /// + [DataMember] public List RunLevelMessageErrorAndWarning = new List(); + + /// + /// List of all the results + /// + [DataMember] public List ResultCollectionList = new List(); + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestRunSummary.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestRunSummary.cs new file mode 100644 index 0000000000..fdd693946f --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/ObjectModel/TestRunSummary.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.Serialization; + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.ObjectModel +{ + /// + /// Test run summary collects the relevant summary information. + /// + [DataContract] + public class TestRunSummary + { + /// + /// Indicates the pass percentage + /// + [DataMember] public int PassPercentage { get; set; } + + /// + /// Total test run time. + /// + [DataMember] public string TotalRunTime { get; set; } + + /// + /// Total tests of a test run. + /// + [DataMember] public int TotalTests { get; set; } + + /// + /// Passed tests of test run. + /// + [DataMember] public int PassedTests { get; set; } + + /// + /// Failed Tests of test run. + /// + [DataMember] public int FailedTests { get; set; } + + /// + /// Skipped Tests of test run. + /// + [DataMember] public int SkippedTests { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/Resources.Designer.cs new file mode 100644 index 0000000000..9c2bc33784 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.Resources.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Html test results file : {0}. + /// + internal static string HtmlFilePath { + get { + return ResourceManager.GetString("HtmlFilePath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Html Logger Error : {0}. + /// + internal static string HtmlLoggerError { + get { + return ResourceManager.GetString("HtmlLoggerError", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/Resources.resx b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/Resources.resx new file mode 100644 index 0000000000..a176fb3154 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Html test results file : {0} + + + Html Logger Error : {0} + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.cs.xlf new file mode 100644 index 0000000000..a56978a9ee --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.cs.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.de.xlf new file mode 100644 index 0000000000..5ff9cfca7f --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.de.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.es.xlf new file mode 100644 index 0000000000..fe7cdbe5db --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.es.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.fr.xlf new file mode 100644 index 0000000000..93bddc0de2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.fr.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.it.xlf new file mode 100644 index 0000000000..ab6dbcd720 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.it.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ja.xlf new file mode 100644 index 0000000000..896b79f62d --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ja.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ko.xlf new file mode 100644 index 0000000000..92f75efbc2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ko.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.pl.xlf new file mode 100644 index 0000000000..92c1eb2c85 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.pl.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.pt-BR.xlf new file mode 100644 index 0000000000..34172d94cc --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.pt-BR.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ru.xlf new file mode 100644 index 0000000000..b4629668c5 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.ru.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.tr.xlf new file mode 100644 index 0000000000..c5e02cd5dd --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.tr.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.xlf new file mode 100644 index 0000000000..9c1fa7577b --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.xlf @@ -0,0 +1,12 @@ + + + + + + Html test results file : {0} + Obtained Html File's Path is {0} + + + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.zh-Hans.xlf new file mode 100644 index 0000000000..f12b190c4e --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.zh-Hant.xlf new file mode 100644 index 0000000000..e2b04eaeb2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Resources/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,30 @@ + + + +
+ + 27 + 22.95 + 22.95 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 27 + 0 + +
+ + + Html test results file : {0} + Obtained Html File's Path is {0} + + + +
+
\ No newline at end of file diff --git a/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec b/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec index 5c56f5415d..287b34f826 100644 --- a/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec +++ b/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec @@ -92,6 +92,20 @@ + + + + + + + + + + + + + + @@ -256,6 +270,7 @@ + @@ -267,6 +282,7 @@ + @@ -278,11 +294,13 @@ + + @@ -292,6 +310,7 @@ + @@ -303,6 +322,7 @@ + @@ -314,6 +334,7 @@ + @@ -325,6 +346,7 @@ + @@ -336,6 +358,7 @@ + @@ -347,6 +370,7 @@ + @@ -358,6 +382,7 @@ + @@ -469,6 +494,7 @@ + @@ -480,6 +506,7 @@ + @@ -491,6 +518,7 @@ + diff --git a/src/package/nuspec/Microsoft.TestPlatform.nuspec b/src/package/nuspec/Microsoft.TestPlatform.nuspec index ba9eb38a9d..ef6cf820dc 100644 --- a/src/package/nuspec/Microsoft.TestPlatform.nuspec +++ b/src/package/nuspec/Microsoft.TestPlatform.nuspec @@ -64,7 +64,7 @@ - + @@ -188,6 +188,7 @@ + @@ -200,6 +201,7 @@ + @@ -212,6 +214,7 @@ + @@ -224,6 +227,7 @@ + @@ -278,6 +282,7 @@ + @@ -290,6 +295,7 @@ + @@ -302,6 +308,7 @@ + @@ -314,6 +321,7 @@ + @@ -326,6 +334,7 @@ + @@ -338,6 +347,7 @@ + @@ -350,6 +360,7 @@ + @@ -362,6 +373,7 @@ + @@ -378,6 +390,7 @@ + @@ -390,6 +403,7 @@ + diff --git a/src/package/package/package.csproj b/src/package/package/package.csproj index 082f23c5fa..4b39d1211e 100644 --- a/src/package/package/package.csproj +++ b/src/package/package/package.csproj @@ -39,6 +39,7 @@ + true diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/LoggerTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/LoggerTests.cs index 7eb8b5e08b..9c86723df4 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/LoggerTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/LoggerTests.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.IO; using System.Xml; namespace Microsoft.TestPlatform.AcceptanceTests { using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Text; [TestClass] public class LoggerTests : AcceptanceTestBase @@ -32,6 +32,26 @@ public void TrxLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runn Assert.IsTrue(IsValidXml(trxLogFilePath), "Invalid content in Trx log file"); } + [TestMethod] + [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + public void HtmlLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runnerInfo) + { + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + + var arguments = PrepareArguments(this.GetSampleTestAssembly(), this.GetTestAdapterPath(), string.Empty, this.FrameworkArgValue, runnerInfo.InIsolationValue); + var htmlFileName = "TestResults.html"; + arguments = string.Concat(arguments, $" /logger:\"html;LogFileName={htmlFileName}\""); + this.InvokeVsTest(arguments); + + arguments = PrepareArguments(this.GetSampleTestAssembly(), this.GetTestAdapterPath(), string.Empty, this.FrameworkArgValue, runnerInfo.InIsolationValue); + arguments = string.Concat(arguments, $" /logger:\"html;LogFileName={htmlFileName}\""); + arguments = string.Concat(arguments, " /testcasefilter:Name~Pass"); + this.InvokeVsTest(arguments); + + var htmlLogFilePath = Path.Combine(Directory.GetCurrentDirectory(), "TestResults", htmlFileName); + IsFileAndContentEqual(htmlLogFilePath); + } + [TestMethod] [NetCoreTargetFrameworkDataSource] public void TrxLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runnerInfo) @@ -52,6 +72,26 @@ public void TrxLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runne Assert.IsTrue(IsValidXml(trxLogFilePath), "Invalid content in Trx log file"); } + [TestMethod] + [NetCoreTargetFrameworkDataSource] + public void HtmlLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runnerInfo) + { + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + + var arguments = PrepareArguments(this.GetSampleTestAssembly(), this.GetTestAdapterPath(), string.Empty, this.FrameworkArgValue, runnerInfo.InIsolationValue); + var htmlFileName = "TestResults.html"; + arguments = string.Concat(arguments, $" /logger:\"logger://Microsoft/TestPlatform/htmlLogger/v1;LogFileName{htmlFileName}\""); + this.InvokeVsTest(arguments); + + arguments = PrepareArguments(this.GetSampleTestAssembly(), this.GetTestAdapterPath(), string.Empty, this.FrameworkArgValue, runnerInfo.InIsolationValue); + arguments = string.Concat(arguments, $" /logger:\"logger://Microsoft/TestPlatform/htmlLogger/v1;LogFileName={htmlFileName}\""); + arguments = string.Concat(arguments, " /testcasefilter:Name~Pass"); + this.InvokeVsTest(arguments); + + var htmlLogFilePath = Path.Combine(Directory.GetCurrentDirectory(), "TestResults", htmlFileName); + IsFileAndContentEqual(htmlLogFilePath); + } + private bool IsValidXml(string xmlFilePath) { var reader = System.Xml.XmlReader.Create(File.OpenRead(xmlFilePath)); @@ -67,5 +107,20 @@ private bool IsValidXml(string xmlFilePath) return false; } } + + private void IsFileAndContentEqual(string filePath) + { + StringBuilder sb = new StringBuilder(); + using (var sr = new StreamReader(filePath)) + { + sb.Append(sr.ReadToEnd()); + } + string filePathContent = sb.ToString(); + string[] divs = { "Total tests", "Passed", "Failed", "Skipped", "Run duration", "Pass percentage", "SampleUnitTestProject.UnitTest1.PassingTest" }; + foreach (string str in divs) + { + StringAssert.Contains(filePathContent, str); + } + } } } diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/Microsoft.TestPlatform.AcceptanceTests.csproj b/test/Microsoft.TestPlatform.AcceptanceTests/Microsoft.TestPlatform.AcceptanceTests.csproj index a938d9ac78..928f62332f 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/Microsoft.TestPlatform.AcceptanceTests.csproj +++ b/test/Microsoft.TestPlatform.AcceptanceTests/Microsoft.TestPlatform.AcceptanceTests.csproj @@ -11,6 +11,7 @@ Microsoft.TestPlatform.AcceptanceTests + $(ChutzpahAdapterVersion) diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/TestResults.html b/test/Microsoft.TestPlatform.AcceptanceTests/TestResults.html new file mode 100644 index 0000000000..fe5d919771 --- /dev/null +++ b/test/Microsoft.TestPlatform.AcceptanceTests/TestResults.html @@ -0,0 +1,59 @@ + + +

TestResults

+
+

Summary

+ TotalTests:   + 1
+ FailedTests:  + 0
+ PassedTests:  + 1
+ SkippedTests: + 0

+

Results

+
+
+ ✔ + SampleUnitTestProject.UnitTest1.PassingTest
7ms
+

+ + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs new file mode 100644 index 0000000000..fe53dc7adc --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs @@ -0,0 +1,496 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using VisualStudio.TestTools.UnitTesting; + using Moq; + using ObjectModel = VisualStudio.TestPlatform.ObjectModel; + using VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger; + using HtmlLoggerConstants = VisualStudio.TestPlatform.Extensions.HtmlLogger.Constants; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using HtmlLogger = VisualStudio.TestPlatform.Extensions.HtmlLogger; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + using System.Runtime.Serialization; + using Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.ObjectModel; + + [TestClass] + public class HtmlLoggerTests + { + private Mock events; + private HtmlLogger.HtmlLogger htmlLogger; + private Dictionary parameters; + private static readonly string DefaultTestRunDirectory = Path.GetTempPath(); + private static readonly string DefaultLogFileNameParameterValue = "logfilevalue.html"; + private Mock mockFileHelper; + private Mock mockXmlSerializer; + private Mock mockHtmlTransformer; + + [TestInitialize] + public void TestInitialize() + { + this.events = new Mock(); + this.mockFileHelper = new Mock(); + this.mockHtmlTransformer = new Mock(); + this.mockXmlSerializer = new Mock(); + this.htmlLogger = new HtmlLogger.HtmlLogger(this.mockFileHelper.Object, this.mockHtmlTransformer.Object, this.mockXmlSerializer.Object); + this.parameters = new Dictionary(2) + { + [DefaultLoggerParameterNames.TestRunDirectory] = HtmlLoggerTests.DefaultTestRunDirectory, + [HtmlLoggerConstants.LogFileNameKey] = HtmlLoggerTests.DefaultLogFileNameParameterValue + }; + this.htmlLogger.Initialize(this.events.Object, this.parameters); + } + + #region Initialize Method + + [TestMethod] + public void InitializeShouldThrowExceptionIfEventsIsNull() + { + Assert.ThrowsException( + () => + { + this.htmlLogger.Initialize(null, this.parameters); + }); + } + + [TestMethod] + public void InitializeShouldInitializeAllProperties() + { + const string testResultDir = @"C:\Code\abc"; + var events = new Mock(); + + this.htmlLogger.Initialize(events.Object, testResultDir); + + Assert.AreEqual(this.htmlLogger.TestResultsDirPath, testResultDir); + Assert.IsNotNull(this.htmlLogger.TestRunDetails); + Assert.IsNotNull(this.htmlLogger.Results); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfTestRunDirectoryIsEmptyOrNull() + { + Assert.ThrowsException( + () => + { + this.events = new Mock(); + this.parameters[DefaultLoggerParameterNames.TestRunDirectory] = null; + this.htmlLogger.Initialize(events.Object, parameters); + }); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfParametersAreEmpty() + { + var events = new Mock(); + Assert.ThrowsException(() => this.htmlLogger.Initialize(events.Object, new Dictionary())); + } + + [TestMethod] + public void TestMessageHandlerShouldThrowExceptionIfEventArgsIsNull() + { + Assert.ThrowsException(() => + { + this.htmlLogger.TestMessageHandler(new object(), default(TestRunMessageEventArgs)); + }); + } + + #endregion + + [TestMethod] + public void TestMessageHandlerShouldAddMessageWhenItIsInformation() + { + const string message = "First message"; + var testRunMessageEventArgs = new TestRunMessageEventArgs(TestMessageLevel.Informational, message); + + this.htmlLogger.TestMessageHandler(new object(), testRunMessageEventArgs); + + var actualMessage = this.htmlLogger.TestRunDetails.RunLevelMessageInformational.First(); + Assert.AreEqual(message, actualMessage); + } + + + [TestMethod] + public void TestCompleteHandlerShouldThrowExceptionIfParametersAreNull() + { + Dictionary parameters = null; + var events = new Mock(); + Assert.ThrowsException(() => this.htmlLogger.Initialize(events.Object, parameters)); + } + + [TestMethod] + public void TestMessageHandlerShouldAddMessageInListIfItIsWarningAndError() + { + const string message = "error message"; + const string message2 = "warning message"; + + var testRunMessageEventArgs = new TestRunMessageEventArgs(TestMessageLevel.Error, message); + this.htmlLogger.TestMessageHandler(new object(), testRunMessageEventArgs); + var testRunMessageEventArgs2 = new TestRunMessageEventArgs(TestMessageLevel.Warning, message2); + this.htmlLogger.TestMessageHandler(new object(), testRunMessageEventArgs2); + + Assert.AreEqual(message, this.htmlLogger.TestRunDetails.RunLevelMessageErrorAndWarning.First()); + Assert.AreEqual(2, this.htmlLogger.TestRunDetails.RunLevelMessageErrorAndWarning.Count); + } + + [TestMethod] + public void TestResultHandlerShouldKeepTrackOfFailedResult() + { + var failTestCase1 = CreateTestCase("Fail1"); + + var failResult1 = new ObjectModel.TestResult(failTestCase1) { Outcome = TestOutcome.Failed }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(failResult1).Object); + + Assert.AreEqual(this.htmlLogger.FailedTests, 1, "Failed Tests"); + } + + [TestMethod] + public void TestResultHandlerShouldKeepTrackOfTotalResult() + { + var passTestCase1 = CreateTestCase("Pass1"); + var passResult1 = new ObjectModel.TestResult(passTestCase1) { Outcome = TestOutcome.Passed }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(passResult1).Object); + + Assert.AreEqual(this.htmlLogger.TotalTests, 1, "Total Tests"); + } + + [TestMethod] + public void TestResultHandlerShouldKeepTrackOfPassedResult() + { + var passTestCase2 = CreateTestCase("Pass2"); + var passResult2 = new ObjectModel.TestResult(passTestCase2) { Outcome = TestOutcome.Passed }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(passResult2).Object); + + Assert.AreEqual(this.htmlLogger.PassedTests, 1, "Passed Tests"); + } + + [TestMethod] + public void TestResultHandlerShouldKeepTrackOfSkippedResult() + { + + var skipTestCase1 = CreateTestCase("Skip1"); + var skipResult1 = new ObjectModel.TestResult(skipTestCase1) { Outcome = TestOutcome.Skipped }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(skipResult1).Object); + + Assert.AreEqual(this.htmlLogger.SkippedTests, 1, "Skipped Tests"); + } + + [TestMethod] + public void TestResultHandlerShouldSetDisplayNameIfDisplayNameIsNull() + { + //this assert is for checking result display name equals to null + var passTestCase1 = CreateTestCase("Pass1"); + var passTestResultExpected = new ObjectModel.TestResult(passTestCase1) + { + DisplayName = null, + TestCase = { FullyQualifiedName = "abc" } + }; + + this.htmlLogger.TestResultHandler(new object(),new Mock(passTestResultExpected).Object); + + Assert.AreEqual("abc", this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.First().DisplayName); + } + + [TestMethod] + public void TestResultHandlerShouldSetDisplayNameIfDisplayNameIsNotNull() + { + //this assert is for checking result display name not equals to null + var passTestCase1 = CreateTestCase("Pass1"); + var passTestResultExpected = new ObjectModel.TestResult(passTestCase1) + { + DisplayName = "def", + TestCase = { FullyQualifiedName = "abc" } + }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(passTestResultExpected).Object); + + Assert.AreEqual("def", this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.Last().DisplayName); + } + + [TestMethod] + public void TestResultHandlerShouldCreateTestResultProperly() + { + var passTestCase = CreateTestCase("Pass1"); + passTestCase.DisplayName = "abc"; + passTestCase.FullyQualifiedName = "fully"; + passTestCase.Source = "abc/def.dll"; + TimeSpan ts1 = new TimeSpan(0, 0, 0, 1, 0); + + var passTestResultExpected = new ObjectModel.TestResult(passTestCase) + { + DisplayName = "def", + ErrorMessage = "error message", + ErrorStackTrace = "Error stack trace", + Duration = ts1 + }; + + var eventArg = new Mock(passTestResultExpected); + // Act + this.htmlLogger.TestResultHandler(new object(), eventArg.Object); + + var result = this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.First(); + + Assert.AreEqual(result.DisplayName, "def"); + Assert.AreEqual(result.ErrorMessage, "error message"); + Assert.AreEqual(result.ErrorStackTrace, "Error stack trace"); + Assert.AreEqual(result.FullyQualifiedName, "fully"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().Source, "abc/def.dll"); + Assert.AreEqual(result.Duration, "1s"); + } + + [TestMethod] + public void GetFormattedDurationStringShouldGiveCorrectFormat() + { + TimeSpan ts1 = new TimeSpan(0, 0, 0, 0, 1); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts1), "1ms"); + + TimeSpan ts2 = new TimeSpan(0, 0, 0, 1, 0); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts2), "1s"); + + TimeSpan ts3 = new TimeSpan(0, 0, 1, 0, 1); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts3), "1m"); +; + TimeSpan ts4 = new TimeSpan(0, 1, 0, 2, 3); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts4), "1h"); + + TimeSpan ts5 = new TimeSpan(0, 1, 2, 3, 4); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts5), "1h 2m"); + + TimeSpan ts6 = new TimeSpan(0, 0, 1, 2, 3); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts6), "1m 2s"); + + TimeSpan ts7 = new TimeSpan(0, 0, 0, 1, 3); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts7), "1s 3ms"); + + TimeSpan ts8 = new TimeSpan(2); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts8), "< 1ms"); + + TimeSpan ts10 = new TimeSpan(1, 0, 0, 1, 3); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts10), "> 1d"); + + TimeSpan ts9 = new TimeSpan(0, 0, 0, 0, 0); + Assert.AreEqual(htmlLogger.GetFormattedDurationString(ts9), null); + } + + [TestMethod] + public void TestResultHandlerShouldCreateOneTestEntryForEachTestCase() + { + TestCase testCase1 = CreateTestCase("TestCase1"); + TestCase testCase2 = CreateTestCase("TestCase2"); + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2) { Outcome = TestOutcome.Passed }; + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + + // Act + this.htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.htmlLogger.TestResultHandler(new object(), resultEventArg2.Object); + + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.Count, 2, "TestResultHandler is not creating test result entry for each test case"); + } + + [TestMethod] + public void TestResultHandlerShouldCreateOneTestResultCollectionForOneSource() + { + TestCase testCase1 = CreateTestCase("TestCase1"); + testCase1.Source = "abc.dll"; + + TestCase testCase2 = CreateTestCase("TestCase2"); + testCase2.Source = "def.dll"; + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2) { Outcome = TestOutcome.Passed }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(result1).Object); + this.htmlLogger.TestResultHandler(new object(), new Mock(result2).Object); + + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.Count, 2); + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().Source, "abc.dll"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.Last().Source, "def.dll"); + } + + [TestMethod] + public void TestResultHandlerShouldAddFailedResultToFailedResultListInTestResultCollection() + { + TestCase testCase1 = CreateTestCase("TestCase1"); + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(result1).Object); + + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().FailedResultList.Count, 1); + } + + [TestMethod] + public void TestResultHandlerShouldAddHierarchicalResultsForOrderedTest() + { + TestCase testCase1 = CreateTestCase("TestCase1"); + TestCase testCase2 = CreateTestCase("TestCase2"); + TestCase testCase3 = CreateTestCase("TestCase3"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(HtmlLoggerConstants.ExecutionIdProperty, parentExecutionId); + result1.SetPropertyValue(HtmlLoggerConstants.TestTypeProperty, HtmlLoggerConstants.OrderedTestTypeGuid); + + this.htmlLogger.TestResultHandler(new object(), new Mock(result1).Object); + + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.Count, 1, "test handler is adding parent result correctly"); + Assert.IsNull(this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.First().InnerTestResults, "test handler is adding child result correctly"); + + var result2 = new ObjectModel.TestResult(testCase2); + result2.SetPropertyValue(HtmlLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(HtmlLoggerConstants.ParentExecIdProperty, parentExecutionId); + + var result3 = new ObjectModel.TestResult(testCase3) { Outcome = TestOutcome.Failed }; + result3.SetPropertyValue(HtmlLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(HtmlLoggerConstants.ParentExecIdProperty, parentExecutionId); + + this.htmlLogger.TestResultHandler(new object(), new Mock(result2).Object); + this.htmlLogger.TestResultHandler(new object(), new Mock(result3).Object); + + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.Count, 1, "test handler is adding parent result correctly"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.ResultCollectionList.First().ResultList.First().InnerTestResults.Count, 2, "test handler is adding child result correctly"); + } + + [TestMethod] + public void TestCompleteHandlerShouldKeepTackOfSummary() + { + TestCase passTestCase1 = CreateTestCase("Pass1"); + TestCase passTestCase2 = CreateTestCase("Pass2"); + TestCase failTestCase1 = CreateTestCase("Fail1"); + TestCase skipTestCase1 = CreateTestCase("Skip1"); + var passResult1 = new ObjectModel.TestResult(passTestCase1) { Outcome = TestOutcome.Passed }; + var passResult2 = new ObjectModel.TestResult(passTestCase2) { Outcome = TestOutcome.Passed }; + var failResult1 = new ObjectModel.TestResult(failTestCase1) { Outcome = TestOutcome.Failed }; + var skipResult1 = new ObjectModel.TestResult(skipTestCase1) { Outcome = TestOutcome.Skipped }; + + this.htmlLogger.TestResultHandler(new object(), new Mock(passResult1).Object); + this.htmlLogger.TestResultHandler(new object(), new Mock(passResult2).Object); + this.htmlLogger.TestResultHandler(new object(), new Mock(failResult1).Object); + this.htmlLogger.TestResultHandler(new object(), new Mock(skipResult1).Object); + + this.mockFileHelper.Setup(x => x.GetStream(It.IsAny(), FileMode.Create, FileAccess.ReadWrite)).Callback((x, y, z) => + { + }).Returns(new Mock().Object); + + this.htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero)); + + Assert.AreEqual(this.htmlLogger.TestRunDetails.Summary.TotalTests, 4, "summary should keep track of total tests"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.Summary.FailedTests, 1, "summary should keep track of failed tests"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.Summary.PassedTests, 2, "summary should keep track of passed tests"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.Summary.SkippedTests, 1, "summary should keep track of passed tests"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.Summary.PassPercentage, 50, "summary should keep track of passed tests"); + Assert.AreEqual(this.htmlLogger.TestRunDetails.Summary.TotalRunTime, null, "summary should keep track of passed tests"); + } + + [TestMethod] + public void TestCompleteHandlerShouldCreateCustumHtmlFileNameIfParameterDirectoryIsNull() + { + var parameters = new Dictionary(); + parameters[HtmlLoggerConstants.LogFileNameKey] = null; + parameters[DefaultLoggerParameterNames.TestRunDirectory] = "dsa"; + + var testCase1 = CreateTestCase("TestCase1"); + var result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + var resultEventArg1 = new Mock(result1); + this.htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); + + this.htmlLogger.Initialize(new Mock().Object, parameters); + this.htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero)); + Assert.IsTrue(this.htmlLogger.HtmlFilePath.Contains("TestResult")); + } + + [TestMethod] + public void TestCompleteHandlerShouldCreateFileCorrectly() + { + var testCase1 = CreateTestCase("TestCase1"); + var result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + var resultEventArg1 = new Mock(result1); + + + this.mockFileHelper.Setup(x => x.GetStream(It.IsAny(), FileMode.Create, FileAccess.ReadWrite)).Callback((x, y, z) => + { + }).Returns(new Mock().Object); + + this.htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero)); + + this.mockFileHelper.Verify(x => x.GetStream(It.IsAny(), FileMode.Create, FileAccess.ReadWrite), Times.Once); + } + + [TestMethod] + public void TestCompleteHandlerShouldDeleteFileCorrectly() + { + var testCase1 = CreateTestCase("TestCase1"); + var result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + var resultEventArg1 = new Mock(result1); + + this.mockFileHelper.Setup(x => x.Delete(It.IsAny())).Callback((x) => + { + }); + + this.htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero)); + + this.mockFileHelper.Verify(x => x.Delete(It.IsAny()), Times.Once); + } + + + [TestMethod] + public void TestCompleteHandlerShouldCallHtmlTransformerCorrectly() + { + var testCase1 = CreateTestCase("TestCase1"); + var result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + var resultEventArg1 = new Mock(result1); + + this.mockFileHelper.Setup(x => x.GetStream(It.IsAny(), FileMode.Create, FileAccess.ReadWrite)).Callback((x, y, z) => + { + }).Returns(new Mock().Object); + + this.htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero)); + + this.mockHtmlTransformer.Verify(x => x.Transform(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public void TestCompleteHandlerShouldWriteToXmlSerializerCorrectly() + { + var testCase1 = CreateTestCase("TestCase1") ?? throw new ArgumentNullException($"CreateTestCase(\"TestCase1\")"); + var result1 = new ObjectModel.TestResult(testCase1) { Outcome = TestOutcome.Failed }; + var resultEventArg1 = new Mock(result1); + this.mockFileHelper.Setup(x => x.GetStream(It.IsAny(), FileMode.Create, FileAccess.ReadWrite)).Callback((x, y, z) => + { + }).Returns(new Mock().Object); + + this.htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero)); + + this.mockXmlSerializer.Verify(x => x.WriteObject(It.IsAny(), It.IsAny()), Times.Once); + Assert.IsTrue(htmlLogger.XmlFilePath.Contains(".xml")); + Assert.IsTrue(htmlLogger.HtmlFilePath.Contains(".html")); + } + + private static TestCase CreateTestCase(string testCaseName) + { + return new TestCase(testCaseName, new Uri("some://uri"), "DummySourceFileName"); + } + } +} + + + + + + diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj new file mode 100644 index 0000000000..9b61960acb --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + ..\..\ + true + true + true + + + + Exe + netcoreapp2.1;net451 + + + + + + + + + + + + diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Program.cs new file mode 100644 index 0000000000..d674656dd0 --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Program.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests +{ + public static class Program + { + public static void Main(string[] args) + { + } + } +} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs index 0eb5f6f1a4..96291ad49c 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs @@ -179,13 +179,11 @@ public void TestResultHandlerKeepingTheTrackOfPassedAndFailedTests() Mock fail1 = new Mock(failResult1); Mock skip1 = new Mock(skipResult1); - this.testableTrxLogger.TestResultHandler(new object(), pass1.Object); this.testableTrxLogger.TestResultHandler(new object(), pass2.Object); this.testableTrxLogger.TestResultHandler(new object(), fail1.Object); this.testableTrxLogger.TestResultHandler(new object(), skip1.Object); - Assert.AreEqual(this.testableTrxLogger.PassedTestCount, 2, "Passed Tests"); Assert.AreEqual(this.testableTrxLogger.FailedTestCount, 1, "Failed Tests"); } @@ -215,13 +213,11 @@ public void TestResultHandlerKeepingTheTrackOfTotalTests() Mock fail1 = new Mock(failResult1); Mock skip1 = new Mock(skipResult1); - this.testableTrxLogger.TestResultHandler(new object(), pass1.Object); this.testableTrxLogger.TestResultHandler(new object(), pass2.Object); this.testableTrxLogger.TestResultHandler(new object(), fail1.Object); this.testableTrxLogger.TestResultHandler(new object(), skip1.Object); - Assert.AreEqual(this.testableTrxLogger.TotalTestCount, 4, "Passed Tests"); }