Skip to content

Commit bf76999

Browse files
authored
Strip ANSI sequences from xunit output (mochajs#6107)
1 parent 9d925f0 commit bf76999

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

lib/reporters/xunit.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ var EVENT_RUN_END = constants.EVENT_RUN_END;
2525
var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING;
2626
var STATE_FAILED = Runnable.constants.STATE_FAILED;
2727
var escape = utils.escape;
28+
var ANSI_ESCAPE_SEQUENCE = new RegExp(
29+
String.fromCharCode(27) + "\\[[0-?]*[ -/]*[@-~]",
30+
"g",
31+
);
2832

2933
/**
3034
* Save timer references to avoid Sinon interfering (see GH-237).
@@ -173,7 +177,10 @@ class XUnit extends Base {
173177
"failure",
174178
{},
175179
false,
176-
escape(err.message) + escape(diff) + "\n" + escape(err.stack),
180+
escapeXml(err.message) +
181+
escapeXml(diff) +
182+
"\n" +
183+
escapeXml(err.stack),
177184
),
178185
),
179186
);
@@ -201,7 +208,7 @@ function tag(name, attrs, close, content) {
201208

202209
for (var key in attrs) {
203210
if (Object.prototype.hasOwnProperty.call(attrs, key)) {
204-
pairs.push(key + '="' + escape(attrs[key]) + '"');
211+
pairs.push(key + '="' + escapeXml(attrs[key]) + '"');
205212
}
206213
}
207214

@@ -212,6 +219,30 @@ function tag(name, attrs, close, content) {
212219
return tag;
213220
}
214221

222+
function escapeXml(value) {
223+
return escape(
224+
stripInvalidXmlCharacters(String(value).replace(ANSI_ESCAPE_SEQUENCE, "")),
225+
);
226+
}
227+
228+
function stripInvalidXmlCharacters(value) {
229+
var result = "";
230+
231+
for (var i = 0; i < value.length; i += 1) {
232+
var charCode = value.charCodeAt(i);
233+
if (
234+
charCode === 0x09 ||
235+
charCode === 0x0a ||
236+
charCode === 0x0d ||
237+
charCode >= 0x20
238+
) {
239+
result += value[i];
240+
}
241+
}
242+
243+
return result;
244+
}
245+
215246
function testFilePath(filepath, options) {
216247
if (
217248
options &&

test/reporters/xunit.spec.cjs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,38 @@ describe("XUnit reporter", function () {
429429

430430
expect(expectedWrite, "to contain", expectedDiff);
431431
});
432+
433+
it("should remove ANSI escape sequences and invalid XML control characters", function () {
434+
var xunit = new XUnit(runner);
435+
var expectedTest = {
436+
state: STATE_FAILED,
437+
title: "\x1b[32m" + expectedTitle + "\x1b[39m",
438+
file: expectedFile,
439+
parent: {
440+
fullTitle: function () {
441+
return expectedClassName;
442+
},
443+
},
444+
duration: 1000,
445+
err: {
446+
actual: "foo",
447+
expected: "bar",
448+
message: "\x1b[31m" + expectedMessage + "\x1b[39m\u0007",
449+
stack: "\x1b[31m" + expectedStack + "\x1b[39m\u0007",
450+
},
451+
};
452+
453+
xunit.test.call(fakeThis, expectedTest);
454+
sinon.restore();
455+
456+
expect(expectedWrite, "to contain", 'name="' + expectedTitle + '"');
457+
expect(expectedWrite, "to contain", "<failure>" + expectedMessage);
458+
expect(expectedWrite, "to contain", expectedStack);
459+
expect(expectedWrite, "not to contain", "&#x1B;");
460+
expect(expectedWrite, "not to contain", "&#x7;");
461+
expect(expectedWrite, "not to contain", "[31m");
462+
expect(expectedWrite, "not to contain", "[39m");
463+
});
432464
});
433465

434466
describe("on test pending", function () {

0 commit comments

Comments
 (0)