Skip to content

Commit

Permalink
JUnit formatter added. (palantir#3194)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikcsabee authored and HyphnKnight committed Apr 9, 2018
1 parent e33ac12 commit 5db0076
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/formatters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export { Formatter as StylishFormatter } from "./stylishFormatter";
export { Formatter as FileslistFormatter } from "./fileslistFormatter";
export { Formatter as CodeFrameFormatter } from "./codeFrameFormatter";
export { Formatter as TapFormatter } from "./tapFormatter";
export { Formatter as JUnitFormatter } from "./junitFormatter";
88 changes: 88 additions & 0 deletions src/formatters/junitFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @license
* Copyright 2016 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { AbstractFormatter } from "../language/formatter/abstractFormatter";
import { IFormatterMetadata } from "../language/formatter/formatter";
import { RuleFailure } from "../language/rule/rule";

import * as Utils from "../utils";

export class Formatter extends AbstractFormatter {
/* tslint:disable:object-literal-sort-keys */
public static metadata: IFormatterMetadata = {
formatterName: "junit",
description: "Formats errors as through they were JUnit output.",
descriptionDetails: Utils.dedent`
Imitates the JUnit XML Output`,
sample: Utils.dedent`
<?xml version="1.0" encoding="utf-8"?>
<testsuites package="tslint">
<testsuite name="myFile.ts">
<testcase name="Line 1, Column 14: semicolon">
<failure type="warning">Missing semicolon</failure>
</testcase>
</testsuite>
</testsuites>
`,
consumer: "machine",
};
/* tslint:enable:object-literal-sort-keys */

public format(failures: RuleFailure[]): string {
let output = '<?xml version="1.0" encoding="utf-8"?><testsuites package="tslint">';

if (failures.length !== 0) {
const failuresSorted = failures.sort(
(a, b) => a.getFileName().localeCompare(b.getFileName()));
let previousFilename: string | null = null;
for (const failure of failuresSorted) {
const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();
const message = this.escapeXml(failure.getFailure());
const rule = this.escapeXml(failure.getRuleName());
const severity = failure.getRuleSeverity();

if (failure.getFileName() !== previousFilename) {
if (previousFilename !== null) {
output += "</testsuite>";
}
previousFilename = failure.getFileName();
output += `<testsuite name="${this.escapeXml(failure.getFileName())}">`;
}

output += `<testcase name="Line ${lineAndCharacter.line + 1}, `;
output += `Column ${lineAndCharacter.character + 1}: ${rule}">`;
output += `<failure type="${severity}">${message}</failure>`;
output += "</testcase>";
}
if (previousFilename !== null) {
output += "</testsuite>";
}
}

output += "</testsuites>";
return output;
}

private escapeXml(str: string): string {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/'/g, "&#39;")
.replace(/"/g, "&quot;");
}
}
85 changes: 85 additions & 0 deletions test/formatters/junitFormatterTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* @license
* Copyright 2016 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { assert } from "chai";
import * as ts from "typescript";

import { IFormatter, TestUtils } from "../lint";
import { createFailure } from "./utils";

describe("JUnit Formatter", () => {
const TEST_FILE_1 = "formatters/jsonFormatter.test.ts"; // reuse existing sample file
const TEST_FILE_2 = "formatters/pmdFormatter.test.ts"; // reuse existing sample file
let sourceFile1: ts.SourceFile;
let sourceFile2: ts.SourceFile;
let formatter: IFormatter;

before(() => {
const Formatter = TestUtils.getFormatter("junit");
sourceFile1 = TestUtils.getSourceFile(TEST_FILE_1);
sourceFile2 = TestUtils.getSourceFile(TEST_FILE_2);
formatter = new Formatter();
});

it("formats failures", () => {
const maxPosition1 = sourceFile1.getFullWidth();
const maxPosition2 = sourceFile2.getFullWidth();

const failures = [
createFailure(sourceFile1, 0, 1, "first failure", "first-name", undefined, "error"),
createFailure(sourceFile1, 2, 3, "&<>'\" should be escaped", "escape", undefined, "error"),
createFailure(sourceFile1, maxPosition1 - 1, maxPosition1, "last failure", "last-name", undefined, "error"),
createFailure(sourceFile2, 0, 1, "first failure", "first-name", undefined, "error"),
createFailure(sourceFile2, 2, 3, "&<>'\" should be escaped", "escape", undefined, "warning"),
createFailure(sourceFile2, maxPosition2 - 1, maxPosition2, "last failure", "last-name", undefined, "warning"),
];

const expectedResult =
`<?xml version="1.0" encoding="utf-8"?>
<testsuites package="tslint">
<testsuite name="${TEST_FILE_1}">
<testcase name="Line 1, Column 1: first-name">
<failure type="error">first failure</failure>
</testcase>
<testcase name="Line 1, Column 3: escape">
<failure type="error">&amp;&lt;&gt;&#39;&quot; should be escaped</failure>
</testcase>
<testcase name="Line 6, Column 3: last-name">
<failure type="error">last failure</failure>
</testcase>
</testsuite>
<testsuite name="${TEST_FILE_2}">
<testcase name="Line 1, Column 1: first-name">
<failure type="error">first failure</failure>
</testcase>
<testcase name="Line 1, Column 3: escape">
<failure type="warning">&amp;&lt;&gt;&#39;&quot; should be escaped</failure>
</testcase>
<testcase name="Line 6, Column 3: last-name">
<failure type="warning">last failure</failure>
</testcase>
</testsuite>
</testsuites>`.replace(/>\s+/g, ">"); // Remove whitespace between tags;

assert.equal(formatter.format(failures), expectedResult);
});

it("handles no failures", () => {
const result = formatter.format([]);
assert.deepEqual(result, '<?xml version="1.0" encoding="utf-8"?><testsuites package="tslint"></testsuites>');
});
});

0 comments on commit 5db0076

Please sign in to comment.