Skip to content

Commit

Permalink
updating code
Browse files Browse the repository at this point in the history
  • Loading branch information
agracio committed Nov 12, 2024
1 parent c4780fc commit 572d9d5
Show file tree
Hide file tree
Showing 14 changed files with 918 additions and 149 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Mochawesome is a custom test reporter originally designed for Mocha Javascript t
It features a clean modern interface allowing users to easily view and navigate test runs.
https://github.com/adamgruber/mochawesome


<img align="right" src="./docs/NUnit-mock-assembly-dll5.png" style="padding-top: 25px" alt="Mochawesome Report" width="55%" />

### List of supported features
Expand Down Expand Up @@ -75,6 +74,7 @@ https://github.com/adamgruber/mochawesome
- Converts **&lt;Output&gt;&lt;ErrorInfo&gt;&lt;StackTrace&gt;** to JUnit **&lt;failure&gt;** stack trace.
- Converts **&lt;Output&gt;&lt;StdErr&gt;** to JUnit **&lt;system-err&gt;**.
- Converts **&lt;Output&gt;&lt;StdOut&gt;** to JUnit **&lt;system-out&gt;**.
- Tests are ordered by name in Mochawesome.
- Does not resolve test suite times in JUnit output.

### Usage
Expand Down Expand Up @@ -124,8 +124,6 @@ convert(options).then(() => console.log(`Report created: ${options.reportDir}/${
| TRX | Visual Studio TRX |




## Implementation and documentation in progress...


Expand Down
129 changes: 70 additions & 59 deletions src/junit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,69 @@ const _ = require('lodash');

let skippedTests = 0;
let failedTests = 0;

let suites = [];

/**
* @param {ConverterOptions} options
* @param {string|Buffer} xml
* @returns {TestSuites}
*/
function parseXml(options, xml){

let xmlParserOptions = {
object: true,
arrayNotation: true,
sanitize: false,
}

let json;

try{
json = parser.toJson(xml, xmlParserOptions);
}
catch (e){
throw `\nCould not read JSON from converted input ${options.testFile}.\n ${e.message}`;
}


if(!json || !json.testsuites || !json.testsuites.length){
if(json && json.testsuite){
json.testsuites = [{testsuite: json.testsuite}];
delete json['testsuite'];
}
else{
throw `\nCould not find valid <testsuites> or <testsuite> element in converted ${options.testFile}`;
}
}

if(options.saveIntermediateFiles){
let fileName = `${path.parse(options.testFile).name}-converted.json`;
fs.writeFileSync(path.join(options.reportDir, fileName), JSON.stringify(json, null, 2), 'utf8')
}

if(!json.testsuites[0].testsuite){
throw `\nNo <testsuite> elements in <testsuites> element in converted ${options.testFile}`;
}

// sort test suites
if(json.testsuites[0].testsuite[0].file && json.testsuites[0].testsuite[0].classname){
json.testsuites[0].testsuite = _.sortBy(json.testsuites[0].testsuite, ['file', 'classname'])
}
else if(json.testsuites[0].testsuite[0].classname){
json.testsuites[0].testsuite = _.sortBy(json.testsuites[0].testsuite, ['classname'])
}
else{
json.testsuites[0].testsuite.sort((a,b) => a.name - b.name);
//json.testsuites[0].testsuite = _.sortBy(json.testsuites[0].testsuite, ['name'])
}

if(options.testType === 'trx' && json.testsuites[0].testsuite[0].testcase.length !== 0){
json.testsuites[0].testsuite[0].testcase = _.sortBy(json.testsuites[0].testsuite[0].testcase, ['name']);
}

return json.testsuites[0];
}

/**
* @param {TestCase} testcase
* @returns {ErrorMessage|{}}
Expand All @@ -26,8 +86,8 @@ function getError(testcase){
let prefix = fail.type ? `${fail.type}: ` : ''
let diff = !fail.type || fail.type === 'Error' ? null : `${fail.message}`;
if(fail.message || fail.$t){
message = `${prefix}${fail.message}`;
estack = fail.$t;
message = `${prefix}${fail.message.replaceAll('&#xD;', '').replaceAll('&#xA;', '')}`;
estack = fail.$t.replaceAll('&#xD;', '\n');
}
else if(typeof fail === 'string'){
estack = fail;
Expand Down Expand Up @@ -96,61 +156,6 @@ function getContext(testcase){
return context;
}

/**
* @param {ConverterOptions} options
* @param {string|Buffer} xml
* @returns {TestSuites}
*/
function parseXml(options, xml){

let xmlParserOptions = {
object: true,
arrayNotation: true,
sanitize: false,
}

let json;

try{
json = parser.toJson(xml, xmlParserOptions);
}
catch (e){
throw `\nCould not read JSON from converted input ${options.testFile}.\n ${e.message}`;
}


if(!json || !json.testsuites || !json.testsuites.length){
if(json && json.testsuite){
json.testsuites = [{testsuite: json.testsuite}];
delete json['testsuite'];
}
else{
throw `\nCould not find valid <testsuites> or <testsuite> element in converted ${options.testFile}`;
}
}

if(options.saveIntermediateFiles){
let fileName = `${path.parse(options.testFile).name}-converted.json`;
fs.writeFileSync(path.join(options.reportDir, fileName), JSON.stringify(json, null, 2), 'utf8')
}

if(!json.testsuites[0].testsuite){
throw `\nNo <testsuite> elements in <testsuites> element in converted ${options.testFile}`;
}

// sort test suites
if(json.testsuites[0].testsuite[0].file && json.testsuites[0].testsuite[0].classname){
json.testsuites[0].testsuite = _.sortBy(json.testsuites[0].testsuite, ['file', 'classname'])
}
else if(json.testsuites[0].testsuite[0].classname){
json.testsuites[0].testsuite = _.sortBy(json.testsuites[0].testsuite, ['classname'])
}
else{
json.testsuites[0].testsuite = _.sortBy(json.testsuites[0].testsuite, ['name'])
}

return json.testsuites[0];
}

/**
* @param {ConverterOptions} options
Expand Down Expand Up @@ -293,10 +298,16 @@ async function convert(options, suitesRoot){

parseTestSuites(options, testSuites, duration, avg);

let name = suitesRoot.name;

// if(!name && suitesRoot.testsuite.length === 1){
// name = suitesRoot.testsuite[0].name;
// }

results.push(
{
"uuid": crypto.randomUUID(),
"title": suitesRoot.name ?? '' ,
"title": name ?? '' ,
"fullFile": "",
"file": "",
"beforeHooks": [],
Expand Down
155 changes: 83 additions & 72 deletions src/trx-junit.xslt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
<xsl:variable name="totalDuration" select="hours-from-duration($duration)*3600 + minutes-from-duration($duration)*60 + seconds-from-duration($duration)" />
<testsuites
tests="{$numberOfTests}"
time="{$totalDuration}"
failures="{$numberOfFailures}"
errors="{$numberOfErrors}"
skipped="{$numberSkipped}">
Expand All @@ -27,78 +26,90 @@
errors="{$numberOfErrors}"
skipped="{$numberSkipped}">
<xsl:for-each select="//vs:UnitTestResult">
<xsl:variable name="testName" select="@testName"/>
<xsl:variable name="executionId" select="@executionId"/>
<xsl:variable name="testId" select="@testId"/>
<xsl:variable name="testDuration">
<xsl:choose>
<xsl:when test="@duration">
<xsl:variable name="time" select="substring-before(@duration, '.')" />
<xsl:variable name="hours" select="substring-before($time, ':')" />
<xsl:variable name="minutesSeconds" select="substring-after($time, ':')" />
<xsl:variable name="minutes" select="substring-before($minutesSeconds, ':')" />
<xsl:variable name="seconds" select="substring-after($minutesSeconds, ':')" />
<xsl:variable name="millisecond" select="substring-after(@duration, '.')"/>
<xsl:value-of select="$hours*3600 + $minutes*60 + $seconds"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="$millisecond"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="duration" select="xs:dateTime(@endTime) - xs:dateTime(@startTime)" />
<xsl:value-of select="hours-from-duration($duration)*3600 + minutes-from-duration($duration)*60 + seconds-from-duration($duration)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="outcome">
<xsl:choose>
<xsl:when test="@outcome">
<xsl:value-of select="@outcome"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'Error'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="message" select="replace(vs:Output/vs:ErrorInfo/vs:Message, '&#xD;', '')"/>
<xsl:variable name="stacktrace" select="replace(vs:Output/vs:ErrorInfo/vs:StackTrace, '&#xD;', '')"/>
<xsl:variable name="stderr" select="vs:Output/vs:StdErr"/>
<xsl:variable name="stdout" select="vs:Output/vs:StdOut"/>
<xsl:for-each select="//vs:UnitTest">
<xsl:variable name="currentTestId" select="@id"/>
<xsl:if test="$currentTestId = $testId" >
<xsl:variable name="className" select="vs:TestMethod/@className"/>
<testcase
classname="{$className}"
name="{$testName}"
status="{replace(replace($outcome,'Error','Failed'),'NotExecuted','Skipped')}"
time="{$testDuration}"
>
<xsl:if test="contains($outcome, 'Failed')">
<failure message="{$message}">
<xsl:value-of select="$stacktrace" />
</failure>
<xsl:if test="not(InnerResults)">
<xsl:variable name="testName" select="@testName"/>
<xsl:variable name="executionId" select="@executionId"/>
<xsl:variable name="testId" select="@testId"/>
<xsl:variable name="testDuration">
<xsl:choose>
<xsl:when test="@duration">
<xsl:variable name="time" select="substring-before(@duration, '.')" />
<xsl:variable name="hours" select="substring-before($time, ':')" />
<xsl:variable name="minutesSeconds" select="substring-after($time, ':')" />
<xsl:variable name="minutes" select="substring-before($minutesSeconds, ':')" />
<xsl:variable name="seconds" select="substring-after($minutesSeconds, ':')" />
<xsl:variable name="millisecond" select="substring-after(@duration, '.')"/>
<xsl:value-of select="$hours*3600 + $minutes*60 + $seconds"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="$millisecond"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="duration" select="xs:dateTime(@endTime) - xs:dateTime(@startTime)" />
<xsl:value-of select="hours-from-duration($duration)*3600 + minutes-from-duration($duration)*60 + seconds-from-duration($duration)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="outcome">
<xsl:choose>
<xsl:when test="@outcome">
<xsl:value-of select="@outcome"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'Error'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="message" select="replace(vs:Output/vs:ErrorInfo/vs:Message, '&#xD;', '')"/>
<xsl:variable name="stacktrace" select="replace(vs:Output/vs:ErrorInfo/vs:StackTrace, '&#xD;', '')"/>
<xsl:variable name="stderr" select="vs:Output/vs:StdErr"/>
<xsl:variable name="stdout" select="vs:Output/vs:StdOut"/>
<xsl:for-each select="//vs:UnitTest">
<xsl:variable name="currentTestId" select="@id"/>
<xsl:if test="$currentTestId = $testId" >
<xsl:variable name="className" select="vs:TestMethod/@className"/>
<xsl:variable name="name">
<xsl:choose>
<xsl:when test="substring-after($testName, $className)=''">
<xsl:value-of select="$testName"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring(substring-after($testName, $className), 2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<testcase
classname="{$className}"
name="{$name}"
status="{replace(replace($outcome,'Error','Failed'),'NotExecuted','Skipped')}"
time="{$testDuration}"
>
<xsl:if test="contains($outcome, 'Failed')">
<failure message="{$message}">
<xsl:value-of select="$stacktrace" />
</failure>
</xsl:if>
<xsl:if test="contains($outcome, 'Error')">
<error message="{$message}">
<xsl:value-of select="$stacktrace" />
</error>
</xsl:if>
<xsl:if test="contains($outcome, 'NotExecuted')">
<skipped message="{$message}"/>
</xsl:if>
<xsl:if test="$stderr">
<system-err>
<xsl:value-of select="$stderr" />
</system-err>
</xsl:if>
<xsl:if test="$stdout">
<system-out>
<xsl:value-of select="$stdout" />
</system-out>
</xsl:if>
</testcase>
</xsl:if>
<xsl:if test="contains($outcome, 'Error')">
<error message="{$message}">
<xsl:value-of select="$stacktrace" />
</error>
</xsl:if>
<xsl:if test="contains($outcome, 'NotExecuted')">
<skipped message="{$message}"/>
</xsl:if>
<xsl:if test="$stderr">
<system-err>
<xsl:value-of select="$stderr" />
</system-err>
</xsl:if>
<xsl:if test="$stdout">
<system-out>
<xsl:value-of select="$stdout" />
</system-out>
</xsl:if>
</testcase>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</testsuite>
</testsuites>
Expand Down
11 changes: 5 additions & 6 deletions tests/converter.junit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ const beforeAll = require('@jest/globals').beforeAll;
const afterAll = require('@jest/globals').afterAll;
const describe = require('@jest/globals').describe;

const margeConvert = require('../src/converter');
const config = require('../src/config');
const converter = require('../src/converter');

describe("JUnit converter tests", () => {

Expand Down Expand Up @@ -57,28 +56,28 @@ describe("JUnit converter tests", () => {
test('convert junit-jenkins.xml', async() => {
let options = createOptions('junit-jenkins.xml', 'junit');

await margeConvert(options);
await converter(options);
compare(options);
});

test('convert junit-notestsuites.xml', async() => {
let options = createOptions('junit-notestsuites.xml', 'junit');

await margeConvert(options);
await converter(options);
compare(options, 'junit-jenkins-mochawesome.json');
});

test('convert junit-testsuites-noattributes.xml', async() => {
let options = createOptions('junit-testsuites-noattributes.xml', 'junit');

await margeConvert(options);
await converter(options);
compare(options, 'junit-jenkins-mochawesome.json');
});

test('convert junit-mocha-xunit.xml', async() => {
let options = createOptions('junit-mocha-xunit.xml', 'junit')

await margeConvert(options);
await converter(options);
compare(options);
});

Expand Down
Loading

0 comments on commit 572d9d5

Please sign in to comment.