Skip to content

Commit

Permalink
implement test suites notification in Typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
franklychilled committed Feb 23, 2024
1 parent 18895dd commit fe0078f
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 86 deletions.
2 changes: 0 additions & 2 deletions hooks/command
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,12 @@ if [[ -n "$(plugin_read_list TEST_SUITES_0_ARTIFACTS)" ]] ; then
i=0
while true; do
var="TEST_SUITES_${i}_ARTIFACTS"
echo "$var=$(plugin_read_list $var)"
if [[ -n "$(plugin_read_list $var)" ]] ; then
buildkite-agent artifact download \
"$(plugin_read_config "${var}" "")" \
"$ARTIFACTS_DIR"
i=$((i+1))
else
echo "!!! LEAVING on $var"
break
fi
done
Expand Down
279 changes: 240 additions & 39 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/interfaces/junitResult.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface JunitResult {
name: string | undefined,
buildkite_pipeline: string,
git_branch_name: string,
build_id: number,
Expand Down
26 changes: 23 additions & 3 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {addStatsToCommit} from "./testcaseStats";

export const run = async () => {
const commit: JunitResult = {
name: "",
build_id: parseInt(process.env.BUILDKITE_BUILD_NUMBER, 10),
build_url: process.env.BUILDKITE_BUILD_URL,
buildkite_pipeline: process.env.BUILDKITE_PIPELINE_NAME,
Expand All @@ -20,8 +21,27 @@ export const run = async () => {
const SLACK_TOKEN = process.env.SLACK_TOKEN;
const SLACK_CHANNEL = process.env.SLACK_CHANNEL;

const testsuites = await parseFiles();
const result = await addStatsToCommit(testsuites, commit);
await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, result)
const results:JunitResult[] = [];

const TEST_SUITES_0_ARTIFACTS = process.env.TEST_SUITES_0_ARTIFACTS || "";

if (TEST_SUITES_0_ARTIFACTS !== "") {
// loop until the string TEST_SUITES_0_ARTIFACTS is empty and 0 increase over each loop
let i = 0;
while (process.env[`TEST_SUITES_${i}_ARTIFACTS`] !== "") {
const testsuite = await parseFiles(process.env[`TEST_SUITES_${i}_ARTIFACTS`]);
const result = await addStatsToCommit(testsuite, commit);
result.name = process.env[`TEST_SUITES_${i}_NAME`] || "";
results.push(result);
i++;
}
} else {
const ARTIFACTS = process.env.ARTIFACTS || "**/*.xml";
const testsuite = await parseFiles(ARTIFACTS);
const result = await addStatsToCommit(testsuite, commit);
results.push(result);
}

await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, results)
.then(() => console.log("completed."));
};
58 changes: 33 additions & 25 deletions src/slackNotification.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import {JunitResult} from "./interfaces/junitResult.interface";
import {sendSlackMessage} from "./slack-web-api";
import {flatten} from "lodash";

export const getColor = (result: JunitResult): string => {
if (result.tests_failed > 0) {
export const getColor = (results: JunitResult[]): string => {
if (results.some(result => result.tests_failed > 0)) {
return "#B94A48";
}
const summary = getSummary(result);
if (summary.length === 0) {
const summaries = results.map(result => getSummary(result));
if (flatten(summaries).length === 0) {
return "#B94A48";
}
return "#69A76A";
};

export const getEmoji = (result: JunitResult): string => {
if (result.tests_failed > 0) {
export const getEmoji = (results: JunitResult[]): string => {
if (results.some(result => result.tests_failed > 0)) {
return ":-1: :-1:";
}
const summary = getSummary(result);
if (summary.length === 0) {
const summaries = results.map(result => getSummary(result));
if (flatten(summaries).length === 0) {
return ":-1:";
}
return ":+1:";
Expand All @@ -39,49 +40,56 @@ export const getSummary = (result: JunitResult): string[] => {

export const getTextSummaryLine = (result: JunitResult): string => {
const summary = getSummary(result);
if (summary.length > 0) {
return `Tests ${summary.join(", ")}`;
const name = !result.name? "" : `*${result.name}*: `;
if (summary.length > 0 && result.name) {
return `${name}Tests ${summary.join(", ")}`;
} else if (summary.length > 0 && !result.name) {
return `*Tests ${summary.join(", ")}*`;
}
return "No tests data generated!";
return `${name}*No tests data generated!*`;
};

export const getCommitText = (result: JunitResult): string => {
return `${getEmoji(result)} *${result.buildkite_pipeline} (${result.git_branch_name}) #${result.build_id}*\n${result.git_comment} - ${result.git_username} (${result.git_log})`;
export const getCommitText = (results: JunitResult[]): string => {
return `${getEmoji(results)} *${results[0].buildkite_pipeline} (${results[0].git_branch_name}) #${results[0].build_id}*\n${results[0].git_comment} - ${results[0].git_username} (${results[0].git_log})`;
};

export const getSlackMessageAttachments = (result: JunitResult): unknown => {
export const getSlackMessageAttachments = (results: JunitResult[]): unknown => {
const details = results.map((result) => {
return {
"type": "section",
"text": {
"text": `${getTextSummaryLine(result)}`,
"type": "mrkdwn"
}
};
});

return [
{
"color": getColor(result),
"color": getColor(results),
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": getCommitText(result)
"text": getCommitText(results)
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "View build"
},
"url": result.build_url
"url": results[0].build_url
}
},
{
"type": "section",
"text": {
"text": `*${getTextSummaryLine(result)}*`,
"type": "mrkdwn"
}
}
...details
]
}
];
};

export const sendResultToSlack = async (slackToken: string, channel: string, junitResult: JunitResult): Promise<unknown> => {
export const sendResultToSlack = async (slackToken: string, channel: string, junitResult: JunitResult[]): Promise<unknown> => {
let goodToken = "";
for (let i = 0; i < slackToken.length; i++) {
if (checkChar(slackToken[i])) {
Expand Down
11 changes: 6 additions & 5 deletions src/xmlParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,30 @@ import {promisify} from "util";
import {join} from "path";
import {Parser} from "xml2js";
import {flattenDeep} from "lodash";
import {minimatch} from "minimatch";

Check failure on line 6 in src/xmlParser.ts

View workflow job for this annotation

GitHub Actions / tsc

Cannot find module 'minimatch' or its corresponding type declarations.

// Convert sync functions into async
const fsReaddir = promisify(readdir);
const fsReadFile = promisify(readFile);
const fsStat = promisify(stat);
const xmlParser = new Parser();

export const parseFiles = async () => {
export const parseFiles = async (artifacts: string) => {
const directoryPath = join(__dirname, "../reports");
const allContents = await parseFolder(directoryPath);
const allContents = await parseFolder(directoryPath, artifacts);
return flattenDeep(allContents);
};

export const parseFolder = async (baseDirectory: string): Promise<any[]> => {
export const parseFolder = async (baseDirectory: string, artifacts: string): Promise<any[]> => {
const files: any[] = await fsReaddir(baseDirectory);
return Promise.all(files.map(async fileName => {
const fullPath = join(baseDirectory, fileName);
const stats = await fsStat(fullPath);
if (stats.isDirectory()) {
// recursive
return parseFolder(fullPath);
return parseFolder(fullPath, artifacts);
}
if (stats.isFile()) {
if (stats.isFile() && minimatch(fullPath, artifacts)) {
console.log(`parsing ${fullPath}`);
return parseFile(fullPath);
}
Expand Down
2 changes: 2 additions & 0 deletions test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ line 3`,
"git_comment": "line 1",
"git_log": "0123456",
"git_username": "the-author",
"name": "",
"tests_failed": 0,
"tests_ignored": 0,
"tests_passed": 0
Expand All @@ -83,6 +84,7 @@ line 3`,
"git_comment": "msg",
"git_log": "0123456",
"git_username": "the-author",
"name": "",
"tests_failed": 0,
"tests_ignored": 0,
"tests_passed": 0
Expand Down
93 changes: 82 additions & 11 deletions test/slackNotification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ beforeEach(() => {

describe("Failed test", () => {
const result: JunitResult = {
name: "",
tests_failed: 3,
build_id: 123,
build_url: "https://www.iress.com/mybuild",
Expand All @@ -36,21 +37,21 @@ describe("Failed test", () => {
};

it("should return red", () => {
const actual = getColor(result);
const actual = getColor([result]);

expect(actual).toBe("#B94A48");

});

it("should return negative emoij", () => {
const actual = getEmoji(result);
const actual = getEmoji([result]);

expect(actual).toBe(":-1: :-1:");

});

it("should return summary slack message", () => {
const actual = getSlackMessageAttachments(result);
const actual = getSlackMessageAttachments([result]);

expect(actual).toStrictEqual([
{
Expand Down Expand Up @@ -88,7 +89,7 @@ describe("Failed test", () => {
const SLACK_TOKEN = "xoxb-00000000000-0000000000000-xxxxxxxxxxxxxxxxxxxxxxxx\t";
const SLACK_CHANNEL = "hac-483_testing";

await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, result);
await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, [result]);

expect(mockToken).toEqual("xoxb-00000000000-0000000000000-xxxxxxxxxxxxxxxxxxxxxxxx");

Expand All @@ -98,6 +99,7 @@ describe("Failed test", () => {

describe("Passed test", () => {
const result: JunitResult = {
name: "",
tests_failed: 0,
build_id: 456,
build_url: "https://www.iress.com/myotherbuild",
Expand All @@ -110,16 +112,44 @@ describe("Passed test", () => {
tests_ignored: 0
};

const unitTestResult: JunitResult = {
name: "Unit tests",
tests_failed: 0,
build_id: 456,
build_url: "https://www.iress.com/myotherbuild",
buildkite_pipeline: "My Build other pipeline",
git_branch_name: "hac-483_other_branch",
git_comment: "Second commit",
git_log: "a1b2c3d4",
git_username: "Frankly Chilled",
tests_passed: 300,
tests_ignored: 0
};

const verificationTestResult: JunitResult = {
name: "Verification tests",
tests_failed: 0,
build_id: 456,
build_url: "https://www.iress.com/myotherbuild",
buildkite_pipeline: "My Build other pipeline",
git_branch_name: "hac-483_other_branch",
git_comment: "Second commit",
git_log: "a1b2c3d4",
git_username: "Frankly Chilled",
tests_passed: 20,
tests_ignored: 0
};

it("should return green", () => {

const actual = getColor(result);
const actual = getColor([result]);

expect(actual).toBe("#69A76A");

});

it("should return summary slack message", () => {
const actual = getSlackMessageAttachments(result);
const actual = getSlackMessageAttachments([result]);

expect(actual).toStrictEqual([
{
Expand Down Expand Up @@ -153,18 +183,59 @@ describe("Passed test", () => {

});

it("should return summary slack message of multiple test suites", () => {
const actual = getSlackMessageAttachments([unitTestResult, verificationTestResult]);

expect(actual).toStrictEqual([
{
"blocks": [
{
"accessory": {
"text": {
"text": "View build",
"type": "plain_text"
},
"type": "button",
"url": "https://www.iress.com/myotherbuild"
},
"text": {
"text": ":+1: *My Build other pipeline (hac-483_other_branch) #456*\nSecond commit - Frankly Chilled (a1b2c3d4)",
"type": "mrkdwn"
},
"type": "section"
},
{
"text": {
"text": "*Unit tests*: Tests passed: 300",
"type": "mrkdwn"
},
"type": "section"
},
{
"text": {
"text": "*Verification tests*: Tests passed: 20",
"type": "mrkdwn"
},
"type": "section"
}
],
"color": "#69A76A"
}
]);

});

it("send message to slack channel", async () => {
const SLACK_TOKEN = "xoxb-00000000000-0000000000000-xxxxxxxxxxxxxxxxxxxxxxxx";
const SLACK_CHANNEL = "hac-483_testing";

await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, result);


await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, [result]);
});
});

describe("No tests", () => {
const result: JunitResult = {
name: "",
tests_failed: 0,
build_id: 789,
build_url: "https://www.iress.com/myotherbuild",
Expand All @@ -178,7 +249,7 @@ describe("No tests", () => {
};

it("should return summary slack message", () => {
const actual = getSlackMessageAttachments(result);
const actual = getSlackMessageAttachments([result]);

expect(actual).toStrictEqual([
{
Expand Down Expand Up @@ -216,7 +287,7 @@ describe("No tests", () => {
const SLACK_TOKEN = "xoxb-00000000000-0000000000000-xxxxxxxxxxxxxxxxxxxxxxxx";
const SLACK_CHANNEL = "hac-483_testing";

await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, result);
await sendResultToSlack(SLACK_TOKEN, SLACK_CHANNEL, [result]);

// verify call to chat
const attachments = [
Expand Down
Loading

0 comments on commit fe0078f

Please sign in to comment.