Skip to content

Commit fc1f7b9

Browse files
authored
Merge pull request #282 from smadala/trx-logger-enhancement
fixes #243
2 parents 055287c + 1fb09a7 commit fc1f7b9

File tree

12 files changed

+338
-126
lines changed

12 files changed

+338
-126
lines changed

scripts/test.ps1

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,18 @@ $Script:TPT_SkipProjects = @("testhost.UnitTests", "vstest.console.UnitTests")
6363
$Script:TPT_Pattern = $Pattern
6464
$Script:TPT_FailFast = $FailFast
6565
$Script:TPT_Parallel = $Parallel
66+
$Script:TPT_TestResultsDir = Join-Path $env:TP_ROOT_DIR "TestResults"
67+
$Script:TPT_DefaultTrxFileName = "TrxLogResults.trx"
68+
$Script:TPT_ErrorMsgColor = "Red"
6669

6770
#
6871
# Capture error state in any step globally to modify return code
6972
$Script:ScriptFailed = $false
7073

71-
function Write-Log ([string] $message)
74+
function Write-Log ([string] $message, $messageColor = "Green")
7275
{
7376
$currentColor = $Host.UI.RawUI.ForegroundColor
74-
$Host.UI.RawUI.ForegroundColor = "Green"
77+
$Host.UI.RawUI.ForegroundColor = $messageColor
7578
if ($message)
7679
{
7780
Write-Output "... $message"
@@ -84,6 +87,19 @@ function Write-VerboseLog([string] $message)
8487
Write-Verbose $message
8588
}
8689

90+
function Print-FailedTests($TrxFilePath)
91+
{
92+
if(![System.IO.File]::Exists($TrxFilePath)){
93+
Write-Log "TrxFile: $TrxFilePath doesn't exists"
94+
return
95+
}
96+
$xdoc = [xml] (get-content $TrxFilePath)
97+
$FailedTestIds = $xdoc.TestRun.Results.UnitTestResult |?{$_.GetAttribute("outcome") -eq "Failed"} | %{$_.testId}
98+
if ($FailedTestIds) {
99+
Write-Log (".. .. . " + ($xdoc.TestRun.TestDefinitions.UnitTest | ?{ $FailedTestIds.Contains($_.GetAttribute("id")) } | %{ "$($_.TestMethod.className).$($_.TestMethod.name)"})) $Script:TPT_ErrorMsgColor
100+
}
101+
}
102+
87103
function Invoke-Test
88104
{
89105
$timer = Start-Timer
@@ -147,16 +163,16 @@ function Invoke-Test
147163
if ($TPT_Parallel) {
148164
# Fill in the framework in test containers
149165
$testContainerSet = $testContainers | % { [System.String]::Format($_, $fx) }
150-
166+
$trxLogFileName = [System.String]::Format("Parallel_{0}_{1}", $fx, $Script:TPT_DefaultTrxFileName)
151167
Set-TestEnvironment
152-
if($fx -eq $TPT_TargetFrameworkFullCLR){
168+
if($fx -eq $TPT_TargetFrameworkFullCLR) {
153169

154-
Write-Verbose "$vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath /parallel"
155-
$output = & $vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath" /parallel
156-
}else{
170+
Write-Verbose "$vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath /parallel /logger:`"trx;LogFileName=$trxLogFileName`""
171+
$output = & $vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath" /parallel /logger:"trx;LogFileName=$trxLogFileName"
172+
} else {
157173

158-
Write-Verbose "$dotNetPath $vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath /parallel"
159-
$output = & $dotNetPath $vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath" /parallel
174+
Write-Verbose "$dotNetPath $vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath /parallel /logger:`"trx;LogFileName=$trxLogFileName`""
175+
$output = & $dotNetPath $vstestConsolePath $testContainerSet /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath" /parallel /logger:"trx;LogFileName=$trxLogFileName"
160176
}
161177

162178
Reset-TestEnvironment
@@ -165,8 +181,8 @@ function Invoke-Test
165181
Write-Log ".. . $($output[-3])"
166182
} else {
167183
Write-Log ".. . $($output[-2])"
168-
Write-Log ".. . Failed tests:"
169-
Write-Log ".. . $($output -match '^Failed')"
184+
Write-Log ".. . Failed tests:" $Script:TPT_ErrorMsgColor
185+
Print-FailedTests (Join-Path $Script:TPT_TestResultsDir $trxLogFileName)
170186

171187
Set-ScriptFailed
172188

@@ -179,28 +195,29 @@ function Invoke-Test
179195
$testContainers | % {
180196
# Fill in the framework in test containers
181197
$testContainer = [System.String]::Format($_, $fx)
198+
$trxLogFileName = [System.String]::Format("{0}_{1}_{2}", ($(Get-ChildItem $testContainer).Name), $fx, $Script:TPT_DefaultTrxFileName)
199+
182200
Write-Log ".. Container: $testContainer"
183201

184202
Set-TestEnvironment
185203

186-
if($fx -eq $TPT_TargetFrameworkFullCLR){
204+
if($fx -eq $TPT_TargetFrameworkFullCLR) {
187205

188-
Write-Verbose "$vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath"
189-
$output = & $vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath"
190-
}else{
206+
Write-Verbose "$vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath /logger:`"trx;LogFileName=$trxLogFileName`""
207+
$output = & $vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath" /logger:"trx;LogFileName=$trxLogFileName"
208+
} else {
191209

192-
Write-Verbose "$dotNetPath $vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath"
193-
$output = & $dotNetPath $vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath"
210+
Write-Verbose "$dotNetPath $vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:$testAdapterPath /logger:`"trx;LogFileName=$trxLogFileName`""
211+
$output = & $dotNetPath $vstestConsolePath $testContainer /platform:$testArchitecture /framework:$testFrameWork /testAdapterPath:"$testAdapterPath" /logger:"trx;LogFileName=$trxLogFileName"
194212
}
195213

196214
Reset-TestEnvironment
197-
198215
if ($output[-2].Contains("Test Run Successful.")) {
199216
Write-Log ".. . $($output[-3])"
200217
} else {
201218
Write-Log ".. . $($output[-2])"
202-
Write-Log ".. . Failed tests:"
203-
Write-Log ".. . $($output -match '^Failed')"
219+
Write-Log ".. . Failed tests:" $Script:TPT_ErrorMsgColor
220+
Print-FailedTests (Join-Path $Script:TPT_TestResultsDir $trxLogFileName)
204221

205222
Set-ScriptFailed
206223

src/Microsoft.TestPlatform.Common/Logging/TestLoggerManager.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,7 @@ private Dictionary<string, string> UpdateLoggerParamters(Dictionary<string, stri
385385
}
386386

387387
// Add default logger parameters...
388-
// todo Read Output Directory from RunSettings
389-
loggerParams[DefaultLoggerParameterNames.TestRunDirectory] = null;
388+
loggerParams[DefaultLoggerParameterNames.TestRunDirectory] = this.GetResultsDirectory(RunSettingsManager.Instance.ActiveRunSettings);
390389
return loggerParams;
391390
}
392391

src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.Designer.cs

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,7 @@ Error Details: {1}:{2}</value>
208208
<data name="TS_UncategorizedResults" xml:space="preserve">
209209
<value>Results Not in a List</value>
210210
</data>
211+
<data name="TrxLoggerResultsFileOverwriteWarning" xml:space="preserve">
212+
<value>WARNING: Overwriting results file: {0}</value>
213+
</data>
211214
</root>

src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs

Lines changed: 92 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger
2828
/// </summary>
2929
[FriendlyName(TrxLogger.FriendlyName)]
3030
[ExtensionUri(TrxLogger.ExtensionUri)]
31-
internal class TrxLogger : ITestLogger
31+
internal class TrxLogger : ITestLoggerWithParameters
3232
{
3333
#region Constants
3434

@@ -47,14 +47,19 @@ internal class TrxLogger : ITestLogger
4747
/// </summary>
4848
public const string DataCollectorUriPrefix = "dataCollector://";
4949

50+
/// <summary>
51+
/// Log file parameter key
52+
/// </summary>
53+
public const string LogFileNameKey = "LogFileName";
54+
5055
#endregion
5156

5257
#region Fields
5358

5459
/// <summary>
55-
/// Cache the TRX filename
60+
/// Cache the TRX file path
5661
/// </summary>
57-
private static string trxFileName;
62+
private string trxFilePath;
5863

5964
private TrxLoggerObjectModel.TestRun testRun;
6065
private List<TrxLoggerObjectModel.UnitTestResult> results;
@@ -75,47 +80,59 @@ internal class TrxLogger : ITestLogger
7580

7681
private DateTime testRunStartTime;
7782

78-
#endregion
83+
/// <summary>
84+
/// Parameters dictionary for logger. Ex: {"LogFileName":"TestResults.trx"}.
85+
/// </summary>
86+
private Dictionary<string, string> parametersDictionary;
7987

8088
/// <summary>
81-
/// Gets the directory under which trx file should be saved.
89+
/// Gets the directory under which default trx file and test results attachements should be saved.
8290
/// </summary>
83-
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
84-
public static string TrxFileDirectory
85-
{
86-
get;
87-
internal set;
88-
}
91+
private string testResultsDirPath;
92+
93+
#endregion
8994

9095
#region ITestLogger
9196

92-
/// <summary>
93-
/// Initializes the Test Logger.
94-
/// </summary>
95-
/// <param name="events">Events that can be registered for.</param>
96-
/// <param name="testRunDirectory">Test Run Directory</param>
97-
public void Initialize(TestLoggerEvents events, string testRunDirectory)
97+
/// <inheritdoc/>
98+
public void Initialize(TestLoggerEvents events, string testResultsDirPath)
9899
{
99100
if (events == null)
100101
{
101102
throw new ArgumentNullException(nameof(events));
102103
}
103104

104-
if (string.IsNullOrEmpty(testRunDirectory))
105+
if (string.IsNullOrEmpty(testResultsDirPath))
105106
{
106-
throw new ArgumentNullException(nameof(testRunDirectory));
107+
throw new ArgumentNullException(nameof(testResultsDirPath));
107108
}
108109

109110
// Register for the events.
110111
events.TestRunMessage += this.TestMessageHandler;
111112
events.TestResult += this.TestResultHandler;
112113
events.TestRunComplete += this.TestRunCompleteHandler;
113114

114-
TrxFileDirectory = testRunDirectory;
115+
this.testResultsDirPath = testResultsDirPath;
115116

116117
this.InitializeInternal();
117118
}
118119

120+
/// <inheritdoc/>
121+
public void Initialize(TestLoggerEvents events, Dictionary<string, string> parameters)
122+
{
123+
if (parameters == null)
124+
{
125+
throw new ArgumentNullException(nameof(parameters));
126+
}
127+
128+
if (parameters.Count == 0)
129+
{
130+
throw new ArgumentException("No default parameters added", nameof(parameters));
131+
}
132+
133+
this.parametersDictionary = parameters;
134+
this.Initialize(events, this.parametersDictionary[DefaultLoggerParameterNames.TestRunDirectory]);
135+
}
119136
#endregion
120137

121138
#region ForTesting
@@ -257,7 +274,7 @@ internal void TestResultHandler(object sender, ObjectModel.Logging.TestResultEve
257274

258275
// Conver the rocksteady result to MSTest result
259276
TrxLoggerObjectModel.TestOutcome testOutcome = Converter.ToOutcome(e.Result.Outcome);
260-
TrxLoggerObjectModel.UnitTestResult testResult = Converter.ToUnitTestResult(e.Result, testElement, testOutcome, this.testRun, TrxFileDirectory);
277+
TrxLoggerObjectModel.UnitTestResult testResult = Converter.ToUnitTestResult(e.Result, testElement, testOutcome, this.testRun, this.testResultsDirPath);
261278

262279
// Set various counts (passtests, failed tests, total tests)
263280
this.totalTests++;
@@ -331,8 +348,8 @@ internal void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e)
331348
}
332349

333350
List<string> errorMessages = new List<string>();
334-
List<CollectorDataEntry> collectorEntries = Converter.ToCollectionEntries(e.AttachmentSets, this.testRun, TrxFileDirectory);
335-
IList<String> resultFiles = Converter.ToResultFiles(e.AttachmentSets, this.testRun, TrxFileDirectory, errorMessages);
351+
List<CollectorDataEntry> collectorEntries = Converter.ToCollectionEntries(e.AttachmentSets, this.testRun, this.testResultsDirPath);
352+
IList<String> resultFiles = Converter.ToResultFiles(e.AttachmentSets, this.testRun, this.testResultsDirPath, errorMessages);
336353

337354
if (errorMessages.Count > 0)
338355
{
@@ -358,19 +375,9 @@ internal void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e)
358375

359376
helper.SaveObject(runSummary, rootElement, "ResultSummary", parameters);
360377

361-
if (Directory.Exists(TrxFileDirectory) == false)
362-
{
363-
Directory.CreateDirectory(TrxFileDirectory);
364-
}
365-
366-
if (string.IsNullOrEmpty(trxFileName))
367-
{
368-
// save the xml to file in testResultsFolder
369-
// [RunDeploymentRootDirectory] Replace white space with underscore from trx file name to make it command line friendly
370-
trxFileName = this.GetTrxFileName(TrxFileDirectory, this.testRun.RunConfiguration.RunDeploymentRootDirectory.Replace(' ', '_'));
371-
}
372-
373-
this.PopulateTrxFile(trxFileName, rootElement);
378+
//Save results to Trx file
379+
this.DeriveTrxFilePath();
380+
this.PopulateTrxFile(this.trxFilePath, rootElement);
374381
}
375382
}
376383

@@ -387,35 +394,34 @@ internal virtual void PopulateTrxFile(string trxFileName, XmlElement rootElement
387394
{
388395
try
389396
{
390-
FileStream fs = File.OpenWrite(trxFileName);
391-
rootElement.OwnerDocument.Save(fs);
397+
var trxFileDirPath = Path.GetDirectoryName(trxFilePath);
398+
if (Directory.Exists(trxFileDirPath) == false)
399+
{
400+
Directory.CreateDirectory(trxFileDirPath);
401+
}
402+
403+
if (File.Exists(trxFilePath))
404+
{
405+
var overwriteWarningMsg = string.Format(CultureInfo.CurrentCulture,
406+
TrxLoggerResources.TrxLoggerResultsFileOverwriteWarning, trxFileName);
407+
Console.WriteLine(overwriteWarningMsg);
408+
EqtTrace.Warning(overwriteWarningMsg);
409+
}
410+
411+
using (var fs = File.Open(trxFileName, FileMode.Create))
412+
{
413+
rootElement.OwnerDocument.Save(fs);
414+
}
392415
String resultsFileMessage = String.Format(CultureInfo.CurrentCulture, TrxLoggerResources.TrxLoggerResultsFile, trxFileName);
393416
Console.WriteLine(resultsFileMessage);
417+
EqtTrace.Info(resultsFileMessage);
394418
}
395419
catch (System.UnauthorizedAccessException fileWriteException)
396420
{
397421
Console.WriteLine(fileWriteException.Message);
398422
}
399423
}
400424

401-
/// <summary>
402-
/// Get full path to trx file
403-
/// </summary>
404-
/// <param name="baseDirectory">
405-
/// The base Directory.
406-
/// </param>
407-
/// <param name="trxFileName">
408-
/// The trx File Name.
409-
/// </param>
410-
/// <returns>
411-
/// trx file name.
412-
/// </returns>
413-
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
414-
private string GetTrxFileName(string baseDirectory, string trxFileName)
415-
{
416-
return FileHelper.GetNextIterationFileName(baseDirectory, trxFileName + ".trx", false);
417-
}
418-
419425
// Initializes trx logger cache.
420426
private void InitializeInternal()
421427
{
@@ -454,6 +460,36 @@ private void HandleSkippedTest(ObjectModel.TestResult rsTestResult)
454460
this.AddRunLevelInformationalMessage(message);
455461
}
456462

463+
private void DeriveTrxFilePath()
464+
{
465+
if (this.parametersDictionary != null)
466+
{
467+
var isLogFileNameParameterExists = this.parametersDictionary.TryGetValue(TrxLogger.LogFileNameKey, out string logFileNameValue);
468+
if (isLogFileNameParameterExists && !string.IsNullOrWhiteSpace(logFileNameValue))
469+
{
470+
this.trxFilePath = Path.Combine(this.testResultsDirPath, logFileNameValue);
471+
}
472+
else
473+
{
474+
this.SetDefaultTrxFilePath();
475+
}
476+
}
477+
else
478+
{
479+
this.SetDefaultTrxFilePath();
480+
}
481+
}
482+
483+
/// <summary>
484+
/// Sets auto generated Trx file name under test results directory.
485+
/// </summary>
486+
private void SetDefaultTrxFilePath()
487+
{
488+
// [RunDeploymentRootDirectory] Replace white space with underscore from trx file name to make it command line friendly
489+
var defaultTrxFileName = this.testRun.RunConfiguration.RunDeploymentRootDirectory.Replace(' ', '_') + ".trx";
490+
this.trxFilePath = FileHelper.GetNextIterationFileName(this.testResultsDirPath, defaultTrxFileName, false);
491+
}
492+
457493
#endregion
458494
}
459495
}

0 commit comments

Comments
 (0)