xamarin-macios/tests/xharness/Microsoft.DotNet.XHarness.i.../XmlResultParser.cs

765 строки
28 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
namespace Microsoft.DotNet.XHarness.iOS.Shared {
public enum XmlResultJargon {
TouchUnit,
NUnitV2,
NUnitV3,
xUnit,
Missing,
}
public class XmlResultParser : IResultParser {
// test if the file is valid xml, or at least, that can be read it.
public bool IsValidXml (string path, out XmlResultJargon type)
{
type = XmlResultJargon.Missing;
if (!File.Exists (path))
return false;
using (var stream = File.OpenText (path)) {
string line;
while ((line = stream.ReadLine ()) != null) { // special case when get got the tcp connection
if (line.Contains ("ping"))
continue;
if (line.Contains ("test-run")) { // first element of the NUnitV3 test collection
type = XmlResultJargon.NUnitV3;
return true;
}
if (line.Contains ("TouchUnitTestRun")) {
type = XmlResultJargon.TouchUnit;
return true;
}
if (line.Contains ("test-results")) { // first element of the NUnitV3 test collection
type = XmlResultJargon.NUnitV2;
return true;
}
if (line.Contains ("<assemblies>")) { // first element of the xUnit test collection
type = XmlResultJargon.xUnit;
return true;
}
}
}
return false;
}
static (string resultLine, bool failed) ParseNUnitV3Xml (StreamReader stream, StreamWriter writer)
{
long testcasecount, passed, failed, inconclusive, skipped;
bool failedTestRun = false; // result = "Failed"
testcasecount = passed = failed = inconclusive = skipped = 0L;
using (var reader = XmlReader.Create (stream)) {
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-run") {
long.TryParse (reader ["testcasecount"], out testcasecount);
long.TryParse (reader ["passed"], out passed);
long.TryParse (reader ["failed"], out failed);
long.TryParse (reader ["inconclusive"], out inconclusive);
long.TryParse (reader ["skipped"], out skipped);
failedTestRun = failed != 0;
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-suite" && (reader ["type"] == "TestFixture" || reader ["type"] == "ParameterizedFixture")) {
var testCaseName = reader ["fullname"];
writer.WriteLine (testCaseName);
var time = reader.GetAttribute ("time") ?? "0"; // some nodes might not have the time :/
// get the first node and then move in the siblings of the same type
reader.ReadToDescendant ("test-case");
do {
if (reader.Name != "test-case")
break;
// read the test cases in the current node
var status = reader ["result"];
switch (status) {
case "Passed":
writer.Write ("\t[PASS] ");
break;
case "Skipped":
writer.Write ("\t[IGNORED] ");
break;
case "Error":
case "Failed":
writer.Write ("\t[FAIL] ");
break;
case "Inconclusive":
writer.Write ("\t[INCONCLUSIVE] ");
break;
default:
writer.Write ("\t[INFO] ");
break;
}
writer.Write (reader ["name"]);
if (status == "Failed") { // we need to print the message
reader.ReadToDescendant ("failure");
reader.ReadToDescendant ("message");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
reader.ReadToNextSibling ("stack-trace");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
}
if (status == "Skipped") { // nice to have the skip reason
reader.ReadToDescendant ("reason");
reader.ReadToDescendant ("message");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
}
// add a new line
writer.WriteLine ();
} while (reader.ReadToNextSibling ("test-case"));
writer.WriteLine ($"{testCaseName} {time} ms");
}
}
}
var resultLine = $"Tests run: {testcasecount} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed} Ignored: {skipped + inconclusive}";
writer.WriteLine (resultLine);
return (resultLine, failedTestRun);
}
static (string resultLine, bool failed) ParseTouchUnitXml (StreamReader stream, StreamWriter writer)
{
long total, errors, failed, notRun, inconclusive, ignored, skipped, invalid;
total = errors = failed = notRun = inconclusive = ignored = skipped = invalid = 0L;
using (var reader = XmlReader.Create (stream)) {
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-results") {
long.TryParse (reader ["total"], out total);
long.TryParse (reader ["errors"], out errors);
long.TryParse (reader ["failures"], out failed);
long.TryParse (reader ["not-run"], out notRun);
long.TryParse (reader ["inconclusive"], out inconclusive);
long.TryParse (reader ["ignored"], out ignored);
long.TryParse (reader ["skipped"], out skipped);
long.TryParse (reader ["invalid"], out invalid);
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "TouchUnitExtraData") {
// move fwd to get to the CData
if (reader.Read ())
writer.Write (reader.Value);
}
}
}
var passed = total - errors - failed - notRun - inconclusive - ignored - skipped - invalid;
var resultLine = $"Tests run: {total} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed + errors} Ignored: {ignored + skipped + invalid}";
return (resultLine, total == 0 || errors != 0 || failed != 0);
}
static (string resultLine, bool failed) ParseNUnitXml (StreamReader stream, StreamWriter writer)
{
long total, errors, failed, notRun, inconclusive, ignored, skipped, invalid;
total = errors = failed = notRun = inconclusive = ignored = skipped = invalid = 0L;
XmlReaderSettings settings = new XmlReaderSettings ();
settings.ValidationType = ValidationType.None;
using (var reader = XmlReader.Create (stream, settings)) {
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-results") {
long.TryParse (reader ["total"], out total);
long.TryParse (reader ["errors"], out errors);
long.TryParse (reader ["failures"], out failed);
long.TryParse (reader ["not-run"], out notRun);
long.TryParse (reader ["inconclusive"], out inconclusive);
long.TryParse (reader ["ignored"], out ignored);
long.TryParse (reader ["skipped"], out skipped);
long.TryParse (reader ["invalid"], out invalid);
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-suite" && (reader ["type"] == "TestFixture" || reader ["type"] == "TestCollection")) {
var testCaseName = reader ["name"];
writer.WriteLine (testCaseName);
var time = reader.GetAttribute ("time") ?? "0"; // some nodes might not have the time :/
// get the first node and then move in the siblings of the same type
reader.ReadToDescendant ("test-case");
do {
if (reader.Name != "test-case")
break;
// read the test cases in the current node
var status = reader ["result"];
switch (status) {
case "Success":
writer.Write ("\t[PASS] ");
break;
case "Ignored":
writer.Write ("\t[IGNORED] ");
break;
case "Error":
case "Failure":
writer.Write ("\t[FAIL] ");
break;
case "Inconclusive":
writer.Write ("\t[INCONCLUSIVE] ");
break;
default:
writer.Write ("\t[INFO] ");
break;
}
writer.Write (reader ["name"]);
if (status == "Failure" || status == "Error") { // we need to print the message
reader.ReadToDescendant ("message");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
reader.ReadToNextSibling ("stack-trace");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
}
// add a new line
writer.WriteLine ();
} while (reader.ReadToNextSibling ("test-case"));
writer.WriteLine ($"{testCaseName} {time} ms");
}
}
}
var passed = total - errors - failed - notRun - inconclusive - ignored - skipped - invalid;
string resultLine = $"Tests run: {total} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed + errors} Ignored: {ignored + skipped + invalid}";
writer.WriteLine (resultLine);
return (resultLine, total == 0 | errors != 0 || failed != 0);
}
static (string resultLine, bool failed) ParsexUnitXml (StreamReader stream, StreamWriter writer)
{
long total, errors, failed, notRun, inconclusive, ignored, skipped, invalid;
total = errors = failed = notRun = inconclusive = ignored = skipped = invalid = 0L;
using (var reader = XmlReader.Create (stream)) {
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "assembly") {
long.TryParse (reader ["total"], out var assemblyCount);
total += assemblyCount;
long.TryParse (reader ["errors"], out var assemblyErrors);
errors += assemblyErrors;
long.TryParse (reader ["failed"], out var assemblyFailures);
failed += assemblyFailures;
long.TryParse (reader ["skipped"], out var assemblySkipped);
skipped += assemblySkipped;
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "collection") {
var testCaseName = reader ["name"].Replace ("Test collection for ", "");
writer.WriteLine (testCaseName);
var time = reader.GetAttribute ("time") ?? "0"; // some nodes might not have the time :/
// get the first node and then move in the siblings of the same type
reader.ReadToDescendant ("test");
do {
if (reader.Name != "test")
break;
// read the test cases in the current node
var status = reader ["result"];
switch (status) {
case "Pass":
writer.Write ("\t[PASS] ");
break;
case "Skip":
writer.Write ("\t[IGNORED] ");
break;
case "Fail":
writer.Write ("\t[FAIL] ");
break;
default:
writer.Write ("\t[FAIL] ");
break;
}
writer.Write (reader ["name"]);
if (status == "Fail") { // we need to print the message
reader.ReadToDescendant ("message");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
reader.ReadToNextSibling ("stack-trace");
writer.Write ($" : {reader.ReadElementContentAsString ()}");
}
// add a new line
writer.WriteLine ();
} while (reader.ReadToNextSibling ("test"));
writer.WriteLine ($"{testCaseName} {time} ms");
}
}
}
var passed = total - errors - failed - notRun - inconclusive - ignored - skipped - invalid;
string resultLine = $"Tests run: {total} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed + errors} Ignored: {ignored + skipped + invalid}";
writer.WriteLine (resultLine);
return (resultLine, total == 0 | errors != 0 || failed != 0);
}
public string GetXmlFilePath (string path, XmlResultJargon xmlType)
{
var fileName = Path.GetFileName (path);
switch (xmlType) {
case XmlResultJargon.TouchUnit:
case XmlResultJargon.NUnitV2:
case XmlResultJargon.NUnitV3:
return path.Replace (fileName, $"nunit-{fileName}");
case XmlResultJargon.xUnit:
return path.Replace (fileName, $"xunit-{fileName}");
default:
return path;
}
}
public void CleanXml (string source, string destination)
{
using (var reader = new StreamReader (source))
using (var writer = new StreamWriter (destination)) {
string line;
while ((line = reader.ReadLine ()) != null) {
if (line.StartsWith ("ping", StringComparison.Ordinal) || line.Contains ("TouchUnitTestRun") || line.Contains ("NUnitOutput") || line.Contains ("<!--")) continue;
if (line == "") // remove white lines, some files have them
continue;
if (line.Contains ("TouchUnitExtraData")) // always last node in TouchUnit
break;
writer.WriteLine (line);
}
}
}
public (string resultLine, bool failed) GenerateHumanReadableResults (string source, string destination, XmlResultJargon xmlType)
{
(string resultLine, bool failed) parseData;
using (var reader = new StreamReader (source))
using (var writer = new StreamWriter (destination, true)) {
switch (xmlType) {
case XmlResultJargon.TouchUnit:
parseData = ParseTouchUnitXml (reader, writer);
break;
case XmlResultJargon.NUnitV2:
parseData = ParseNUnitXml (reader, writer);
break;
case XmlResultJargon.NUnitV3:
parseData = ParseNUnitV3Xml (reader, writer);
break;
case XmlResultJargon.xUnit:
parseData = ParsexUnitXml (reader, writer);
break;
default:
parseData = ("", true);
break;
}
}
return parseData;
}
static void GenerateNUnitV2TestReport (StreamWriter writer, XmlReader reader)
{
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-suite" && (reader ["type"] == "TestFixture" || reader ["type"] == "TestCollection")) {
long.TryParse (reader ["errors"], out var errors);
long.TryParse (reader ["failed"], out var failed);
if (errors == 0 && failed == 0) // if we do not have any errors, return, nothing to be written here
return;
writer.WriteLine ("<div style='padding-left: 15px;'>");
writer.WriteLine ("<ul>");
reader.ReadToDescendant ("test-case");
do {
if (reader.Name != "test-case")
break;
// read the test cases in the current node
var status = reader ["result"];
switch (status) { // only interested in errors
case "Error":
case "Failure":
writer.WriteLine ("<li>");
var test_name = reader ["name"];
writer.Write (test_name.AsHtml ());
// read to the message of the error and get it
reader.ReadToDescendant ("message");
var message = reader.ReadElementContentAsString ();
if (!string.IsNullOrEmpty (message)) {
writer.Write (": ");
writer.Write (message.AsHtml ());
}
writer.WriteLine ("<br />");
writer.WriteLine ("</li>");
break;
}
} while (reader.ReadToNextSibling ("test-case"));
writer.WriteLine ("</ul>");
writer.WriteLine ("</div>");
}
}
}
static void GenerateNUnitV3TestReport (StreamWriter writer, XmlReader reader)
{
List<(string name, string message)> failedTests = new List<(string name, string message)> ();
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-run") {
long.TryParse (reader ["failed"], out var failed);
if (failed == 0)
break;
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-suite" && (reader ["type"] == "TestFixture" || reader ["type"] == "ParameterizedFixture")) {
var testCaseName = reader ["fullname"];
reader.ReadToDescendant ("test-case");
do {
if (reader.Name != "test-case")
break;
// read the test cases in the current node
var status = reader ["result"];
switch (status) {
case "Error":
case "Failed":
var name = reader ["name"];
reader.ReadToDescendant ("message");
var message = reader.ReadElementContentAsString ();
failedTests.Add ((name, message));
break;
}
} while (reader.ReadToNextSibling ("test-case"));
}
}
if (failedTests.Count > 0) {
writer.WriteLine ("<div style='padding-left: 15px;'>");
writer.WriteLine ("<ul>");
foreach (var (name, message) in failedTests) {
writer.WriteLine ("<li>");
writer.Write (name.AsHtml ());
if (!string.IsNullOrEmpty (message)) {
writer.Write (": ");
writer.Write (message.AsHtml ());
}
writer.WriteLine ("<br />");
writer.WriteLine ("</li>");
}
writer.WriteLine ("</ul>");
writer.WriteLine ("</div>");
}
}
static void GeneratexUnitTestReport (StreamWriter writer, XmlReader reader)
{
var failedTests = new List<(string name, string message)> ();
// xUnit is not as nice and does not provide the final result in a top node,
// we need to look in all the collections and find all the failed tests, this is really bad :/
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name == "collection") {
reader.ReadToDescendant ("test");
do {
if (reader.Name != "test")
break;
// read the test cases in the current node
var status = reader ["result"];
switch (status) {
case "Fail":
var name = reader ["name"];
reader.ReadToDescendant ("message");
var message = reader.ReadElementContentAsString ();
failedTests.Add ((name, message));
break;
}
} while (reader.ReadToNextSibling ("test"));
}
}
if (failedTests.Count > 0) {
writer.WriteLine ("<div style='padding-left: 15px;'>");
writer.WriteLine ("<ul>");
foreach (var (name, message) in failedTests) {
writer.WriteLine ("<li>");
writer.Write (name.AsHtml ());
if (!string.IsNullOrEmpty (message)) {
writer.Write (": ");
writer.Write (message.AsHtml ());
}
writer.WriteLine ("<br />");
writer.WriteLine ("</li>");
}
writer.WriteLine ("</ul>");
writer.WriteLine ("</div>");
}
}
public void GenerateTestReport (StreamWriter writer, string resultsPath, XmlResultJargon xmlType)
{
using (var stream = new StreamReader (resultsPath))
using (var reader = XmlReader.Create (stream)) {
switch (xmlType) {
case XmlResultJargon.NUnitV2:
case XmlResultJargon.TouchUnit:
GenerateNUnitV2TestReport (writer, reader);
break;
case XmlResultJargon.xUnit:
GeneratexUnitTestReport (writer, reader);
break;
case XmlResultJargon.NUnitV3:
GenerateNUnitV3TestReport (writer, reader);
break;
default:
writer.WriteLine ($"<span style='padding-left: 15px;'>Could not parse {resultsPath}: Not supported format.</span><br />");
break;
}
}
}
// get the file, parse it and add the attachments to the first node found
public void UpdateMissingData (string source, string destination, string applicationName, IEnumerable<string> attachments)
{
// we could do this with a XmlReader and a Writer, but might be to complicated to get right, we pay with performance what we
// cannot pay with brain cells.
var doc = XDocument.Load (source);
var attachmentsElement = new XElement ("attachments");
foreach (var path in attachments) {
// we do not add a description, VSTS ignores that :/
attachmentsElement.Add (new XElement ("attachment",
new XElement ("filePath", path)));
}
var testSuitesElements = doc.Descendants ().Where (e => e.Name == "test-suite" && e.Attribute ("type")?.Value == "Assembly");
if (!testSuitesElements.Any ())
return;
// add the attachments to the first test-suite, this will add the attachmnets to it, which will be added to the test-run, the pipeline
// SHOULD NOT merge runs, else this upload will be really hard to use. Also, just to one of them, else we have duplicated logs.
testSuitesElements.FirstOrDefault ().Add (attachmentsElement);
foreach (var suite in testSuitesElements) {
suite.SetAttributeValue ("name", applicationName);
suite.SetAttributeValue ("fullname", applicationName); // docs say just name, but I've seen the fullname instead, docs usually lie
// add also the attachments to all the failing tests, this will make the life of the person monitoring easier, since
// he will see the logs directly from the attachment page
var tests = suite.Descendants ().Where (e => e.Name == "test-case" && e.Attribute ("result").Value == "Failed");
foreach (var t in tests) {
t.Add (attachmentsElement);
}
}
doc.Save (destination);
}
static void WriteAttributes (XmlWriter writer, params (string name, string data) [] attrs)
{
foreach (var (name, data) in attrs) {
writer.WriteAttributeString (name, data);
}
}
static void WriteNUnitV2TestSuiteAttributes (XmlWriter writer, string title) => WriteAttributes (writer,
("name", title),
("executed", "True"),
("result", "Failure"),
("success", "False"),
("time", "0"),
("asserts", "1"));
static void WriteNUnitV2TestCase (XmlWriter writer, string title, string message, StreamReader stderr)
{
writer.WriteStartElement ("test-case");
WriteAttributes (writer,
("name", title),
("executed", "True"),
("result", "Failure"),
("success", "False"),
("time", "0"),
("asserts", "1")
);
writer.WriteStartElement ("failure");
writer.WriteStartElement ("message");
writer.WriteCData (message);
writer.WriteEndElement (); // message
writer.WriteStartElement ("stack-trace");
writer.WriteCData (stderr.ReadToEnd ());
writer.WriteEndElement (); // stack-trace
writer.WriteEndElement (); // failure
writer.WriteEndElement (); // test-case
}
static void GenerateNUnitV2Failure (XmlWriter writer, string title, string message, StreamReader stderr)
{
writer.WriteStartElement ("test-results");
WriteAttributes (writer,
("name", title),
("total", "1"),
("errors", "0"),
("failures", "1"),
("not-run", "0"),
("inconclusive", "0"),
("ignored", "0"),
("skipped", "0"),
("invalid", "0"),
("date", XmlConvert.ToString (DateTime.Now, "yyyy-MM-dd")));
// we are not writting the env and the cunture info since the VSTS uploader does not care
writer.WriteStartElement ("test-suite");
writer.WriteAttributeString ("type", "Assembly");
WriteNUnitV2TestSuiteAttributes (writer, title);
writer.WriteStartElement ("results");
writer.WriteStartElement ("test-suite");
writer.WriteAttributeString ("type", "TestFixture");
WriteNUnitV2TestSuiteAttributes (writer, title);
writer.WriteStartElement ("results");
WriteNUnitV2TestCase (writer, title, message, stderr);
writer.WriteEndElement (); // results
writer.WriteEndElement (); // test-suite TextFixture
writer.WriteEndElement (); // results
writer.WriteEndElement (); // test-suite Assembly
writer.WriteEndElement (); // test-results
}
static void WriteNUnitV3TestSuiteAttributes (XmlWriter writer, string title) => WriteAttributes (writer,
("id", "1"),
("name", title),
("testcasecount", "1"),
("result", "Failed"),
("time", "0"),
("total", "1"),
("passed", "0"),
("failed", "1"),
("inconclusive", "0"),
("skipped", "0"),
("asserts", "0"));
static void WriteFailure (XmlWriter writer, string message, StreamReader? stderr = null)
{
writer.WriteStartElement ("failure");
writer.WriteStartElement ("message");
writer.WriteCData (message);
writer.WriteEndElement (); // message
if (stderr != null) {
writer.WriteStartElement ("stack-trace");
writer.WriteCData (stderr.ReadToEnd ());
writer.WriteEndElement (); //stack trace
}
writer.WriteEndElement (); // failure
}
static void GenerateNUnitV3Failure (XmlWriter writer, string title, string message, StreamReader stderr)
{
var date = DateTime.Now;
writer.WriteStartElement ("test-run");
// defualt values for the crash
WriteAttributes (writer,
("name", title),
("testcasecount", "1"),
("result", "Failed"),
("time", "0"),
("total", "1"),
("passed", "0"),
("failed", "1"),
("inconclusive", "0"),
("skipped", "0"),
("asserts", "1"),
("run-date", XmlConvert.ToString (date, "yyyy-MM-dd")),
("start-time", date.ToString ("HH:mm:ss"))
);
writer.WriteStartElement ("test-suite");
writer.WriteAttributeString ("type", "Assembly");
WriteNUnitV3TestSuiteAttributes (writer, title);
WriteFailure (writer, "Child test failed");
writer.WriteStartElement ("test-suite");
WriteAttributes (writer,
("name", title),
("fullname", title),
("type", "TestFixture"),
("testcasecount", "1"),
("result", "Failed"),
("time", "0"),
("total", "1"),
("passed", "0"),
("failed", "1"),
("inconclusive", "0"),
("skipped", "0"),
("asserts", "1"));
writer.WriteStartElement ("test-case");
WriteAttributes (writer,
("id", "1"),
("name", title),
("fullname", title),
("result", "Failed"),
("time", "0"),
("asserts", "1"));
WriteFailure (writer, message, stderr);
writer.WriteEndElement (); // test-case
writer.WriteEndElement (); // test-suite = TestFixture
writer.WriteEndElement (); // test-suite = Assembly
writer.WriteEndElement (); // test-run
}
static void GeneratexUnitFailure (XmlWriter writer, string title, string message, StreamReader stderr)
{
writer.WriteStartElement ("assemblies");
writer.WriteStartElement ("assembly");
WriteAttributes (writer,
("name", title),
("environment", "64-bit .NET Standard [collection-per-class, non-parallel]"),
("test-framework", "xUnit.net 2.4.1.0"),
("run-date", XmlConvert.ToString (DateTime.Now, "yyyy-MM-dd")),
("total", "1"),
("passed", "0"),
("failed", "1"),
("skipped", "0"),
("time", "0"),
("errors", "0"));
writer.WriteStartElement ("collection");
WriteAttributes (writer,
("total", "1"),
("passed", "0"),
("failed", "1"),
("skipped", "0"),
("name", title),
("time", "0"));
writer.WriteStartElement ("test");
WriteAttributes (writer,
("name", title),
("type", "TestApp"),
("method", "Run"),
("time", "0"),
("result", "Fail"));
WriteFailure (writer, message, stderr);
writer.WriteEndElement (); // test
writer.WriteEndElement (); // collection
writer.WriteEndElement (); // assembly
writer.WriteEndElement (); // assemblies
}
static void GenerateFailureXml (string destination, string title, string message, StreamReader stderrReader, XmlResultJargon jargon)
{
XmlWriterSettings settings = new XmlWriterSettings { Indent = true };
using (var stream = File.CreateText (destination))
using (var xmlWriter = XmlWriter.Create (stream, settings)) {
xmlWriter.WriteStartDocument ();
switch (jargon) {
case XmlResultJargon.NUnitV2:
GenerateNUnitV2Failure (xmlWriter, title, message, stderrReader);
break;
case XmlResultJargon.NUnitV3:
GenerateNUnitV3Failure (xmlWriter, title, message, stderrReader);
break;
case XmlResultJargon.xUnit:
GeneratexUnitFailure (xmlWriter, title, message, stderrReader);
break;
}
xmlWriter.WriteEndDocument ();
}
}
public void GenerateFailure (ILogs logs, string source, string appName, string variation, string title,
string message, StreamReader stderr, XmlResultJargon jargon)
{
// VSTS does not provide a nice way to report build errors, create a fake
// test result with a failure in the case the build did not work
var failureLogXml = logs.Create ($"vsts-nunit-{source}-{Helpers.Timestamp}.xml", LogType.XmlLog.ToString ());
if (jargon == XmlResultJargon.NUnitV3) {
var failureXmlTmp = logs.Create ($"nunit-{source}-{Helpers.Timestamp}.tmp", "Failure Log tmp");
GenerateFailureXml (failureXmlTmp.FullPath, title, message, stderr, jargon);
// add the required attachments and the info of the application that failed to install
var failure_logs = Directory.GetFiles (logs.Directory).Where (p => !p.Contains ("nunit")); // all logs but ourself
UpdateMissingData (failureXmlTmp.FullPath, failureLogXml.FullPath, $"{appName} {variation}", failure_logs);
} else {
GenerateFailureXml (failureLogXml.FullPath, title, message, stderr, jargon);
}
}
public void GenerateFailure (ILogs logs, string source, string appName, string variation, string title, string message, string stderrPath, XmlResultJargon jargon)
{
using var stderrReader = new StreamReader (stderrPath);
GenerateFailure (logs, source, appName, variation, title, message, stderrReader, jargon);
}
public static string GetVSTSFilename (string filename)
=> Path.Combine (Path.GetDirectoryName (filename), $"vsts-{Path.GetFileName (filename)}");
}
}