Skip to content

Commit c0ef81a

Browse files
committed
test(core): Log amazonq/toolkit tests at the bottom of the logs
1 parent 5d3ade7 commit c0ef81a

File tree

6 files changed

+251
-79
lines changed

6 files changed

+251
-79
lines changed

buildspec/linuxE2ETests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ phases:
3737
commands:
3838
- export HOME=/home/codebuild-user
3939
# Ignore failure until throttling issues are fixed.
40-
- xvfb-run npm run testE2E; npm run mergeReports -- "$?"
40+
- xvfb-run npm run testE2E; npm run createTestReport -- "$?"
4141
- VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}"
4242
- CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g')
4343
- CI_BUILD_ID="${CODEBUILD_BUILD_ID}"

buildspec/linuxIntegrationTests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ phases:
9292
build:
9393
commands:
9494
- export HOME=/home/codebuild-user
95-
- xvfb-run npm run testInteg; npm run mergeReports -- "$?"
95+
- xvfb-run npm run testInteg; npm run createTestReport -- "$?"
9696
- VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}"
9797
- CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g')
9898
- CI_BUILD_ID="${CODEBUILD_BUILD_ID}"

buildspec/linuxTests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ phases:
4141
# Ensure that "foo | run_and_report" fails correctly.
4242
set -o pipefail
4343
. buildspec/shared/common.sh
44-
2>&1 xvfb-run npm test --silent; npm run mergeReports -- "$?" | run_and_report 2 \
44+
2>&1 xvfb-run npm test --silent; npm run createTestReport -- "$?" | run_and_report 2 \
4545
'rejected promise not handled' \
4646
'This typically indicates a bug. Read https://developer.mozilla.org/docs/Web/JavaScript/Guide/Using_promises#error_handling'
4747
}

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
"buildCustomLintPlugin": "npm run build -w plugins/eslint-plugin-aws-toolkits",
2626
"compile": "npm run compile -w packages/",
2727
"testCompile": "npm run testCompile -w packages/ --if-present",
28-
"test": "npm run test -w packages/ --if-present",
29-
"testWeb": "npm run testWeb -w packages/ --if-present",
30-
"testE2E": "npm run testE2E -w packages/ --if-present",
31-
"testInteg": "npm run testInteg -w packages/ --if-present",
28+
"test": "npm run test -w packages/ --if-present; npm run createTestReport",
29+
"testWeb": "npm run testWeb -w packages/ --if-present; npm run createTestReport",
30+
"testE2E": "npm run testE2E -w packages/ --if-present; npm run createTestReport",
31+
"testInteg": "npm run testInteg -w packages/ --if-present; npm run createTestReport",
3232
"package": "npm run package -w packages/toolkit -w packages/amazonq",
3333
"newChange": "echo 'Must specify subproject/workspace with -w packages/<subproject>' && false",
3434
"createRelease": "echo 'Must specify subproject/workspace with -w packages/<subproject>' && false",
@@ -37,7 +37,7 @@
3737
"clean": "npm run clean -w packages/ -w plugins/",
3838
"reset": "npm run clean && ts-node ./scripts/clean.ts node_modules && npm install",
3939
"generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present",
40-
"mergeReports": "ts-node ./scripts/mergeReports.ts"
40+
"createTestReport": "ts-node ./scripts/createTestReport.ts"
4141
},
4242
"devDependencies": {
4343
"@aws-toolkits/telemetry": "^1.0.289",

scripts/createTestReport.ts

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as fs from 'fs'
7+
import * as path from 'path'
8+
import * as xml2js from 'xml2js'
9+
10+
interface TestFailure {
11+
$: {
12+
message: string
13+
}
14+
_: string
15+
}
16+
17+
interface TestCase {
18+
$: {
19+
classname: string
20+
name: string
21+
time: string
22+
}
23+
failure?: TestFailure[]
24+
}
25+
26+
interface TestSuite {
27+
$: {
28+
name: string
29+
tests: string
30+
failures: string
31+
errors: string
32+
time: string
33+
file: string
34+
}
35+
testcase: TestCase[] | undefined
36+
}
37+
38+
interface TestReport {
39+
testsuites: {
40+
testsuite: TestSuite[]
41+
}
42+
}
43+
44+
interface TestSummary {
45+
totalTests: number
46+
totalFailures: number
47+
totalTime: number
48+
failedTests: FailedTest[]
49+
}
50+
51+
interface FailedTest {
52+
suite: string
53+
test: string
54+
message: string
55+
contents: string
56+
path: string[]
57+
}
58+
59+
/**
60+
* Merge all of the packages/ test reports into a single directory
61+
*/
62+
async function createTestReport() {
63+
console.log('Merging test reports')
64+
65+
const packagesDir = path.join(__dirname, '..', 'packages')
66+
67+
// Get all packages/* directories
68+
const packageDirs = fs.readdirSync(packagesDir).map((dir) => path.join(packagesDir, dir))
69+
70+
// Find report.xml files in .test-reports subdirectories
71+
const testReports = packageDirs
72+
.map((dir) => path.join(dir, '.test-reports', 'report.xml'))
73+
.filter((file) => fs.existsSync(file))
74+
75+
const mergedReport: TestReport = {
76+
testsuites: {
77+
testsuite: [],
78+
},
79+
}
80+
81+
const failedTests: FailedTest[] = []
82+
let totalTests = 0
83+
let totalFailures = 0
84+
let totalTime = 0
85+
86+
let filePath = ''
87+
let suites = new Set<string>()
88+
89+
/**
90+
* Collect all test reports into a single merged test report object.
91+
* Also keeps track of test count, test failures, and test run time
92+
*/
93+
for (const file of testReports) {
94+
const content = fs.readFileSync(file)
95+
const result: { testsuites: { testsuite: TestSuite[] } } = await xml2js.parseStringPromise(content)
96+
if (result.testsuites && result.testsuites.testsuite) {
97+
for (const suite of result.testsuites.testsuite) {
98+
if (suite.$.file !== filePath) {
99+
filePath = suite.$.file
100+
suites = new Set<string>()
101+
}
102+
103+
for (const testcase of suite.testcase ?? []) {
104+
if (testcase.failure) {
105+
const testPath = parseTestHierarchy(suites, testcase.$.classname, suite.$.name, testcase.$.name)
106+
failedTests.push({
107+
suite: suite.$.name,
108+
test: testcase.$.name,
109+
message: testcase.failure[0].$.message,
110+
contents: testcase.failure[0]._,
111+
path: testPath,
112+
})
113+
}
114+
}
115+
116+
totalTests += parseInt(suite.$.tests, 10)
117+
totalFailures += parseInt(suite.$.failures, 10)
118+
totalTime += parseFloat(suite.$.time)
119+
120+
suites.add(suite.$.name)
121+
}
122+
123+
mergedReport.testsuites.testsuite.push(...result.testsuites.testsuite)
124+
}
125+
}
126+
127+
printTestSummary({
128+
totalTests,
129+
totalFailures,
130+
totalTime,
131+
failedTests,
132+
})
133+
134+
writeReport(mergedReport)
135+
}
136+
137+
/**
138+
* Extracts and constructs a hierarchical test path from a test case identifier
139+
*
140+
* @param suites - Set of known test suite names
141+
* @param className - Name of the test class
142+
* @param suiteName - Name of the test suite
143+
* @param testcaseName - Full name of the test case
144+
* @example
145+
* parseTestHierarchy(new Set(["package validations"]), 'bar1', 'foo', 'package validations foo bar1') -> ["package validations", "bar1", "foo"]
146+
* @returns An array of path components representing the test hierarchy
147+
*/
148+
function parseTestHierarchy(suites: Set<string>, className: string, suiteName: string, testcaseName: string) {
149+
let remainingPath = testcaseName
150+
remainingPath = remainingPath.substring(0, remainingPath.lastIndexOf(className))
151+
remainingPath = remainingPath.substring(0, remainingPath.lastIndexOf(suiteName))
152+
153+
const pathComponents = remainingPath.trim().split(' ')
154+
let index = 0
155+
let currentComponent = pathComponents[0]
156+
const path = []
157+
while (remainingPath.length > 0) {
158+
index++
159+
if (!suites.has(currentComponent)) {
160+
currentComponent = currentComponent + ' ' + pathComponents[index]
161+
} else {
162+
path.push(currentComponent)
163+
remainingPath = remainingPath.substring(currentComponent.length).trim()
164+
currentComponent = pathComponents[index]
165+
}
166+
}
167+
168+
path.push(suiteName)
169+
path.push(className)
170+
171+
return path
172+
}
173+
174+
function printTestSummary({ totalTests, totalFailures, totalTime, failedTests }: TestSummary) {
175+
const passingTests = totalTests - totalFailures
176+
const pendingTests = 0
177+
178+
console.log(`${passingTests} passing (${Math.round(totalTime)}s)`)
179+
if (pendingTests > 0) {
180+
console.log(`${pendingTests} pending`)
181+
}
182+
if (totalFailures > 0) {
183+
console.log(`${totalFailures} failing`)
184+
185+
failedTests.forEach((test, index) => {
186+
let indent = ' '
187+
188+
for (let x = 0; x < test.path.length; x++) {
189+
if (x == 0) {
190+
console.log(`${indent}${index + 1}) ${test.path[x]}`)
191+
indent += ' '
192+
} else {
193+
console.log(`${indent}${test.path[x]}`)
194+
}
195+
indent += ' '
196+
}
197+
198+
if (test.contents) {
199+
// Indent the stack trace
200+
console.log(
201+
test.contents
202+
.split('\n')
203+
.map((line) => `${indent}${line}`)
204+
.join('\n')
205+
)
206+
}
207+
console.log() // Add empty line between failures
208+
})
209+
}
210+
}
211+
212+
function writeReport(mergedReport: TestReport) {
213+
const builder = new xml2js.Builder()
214+
const xml = builder.buildObject(mergedReport)
215+
216+
/**
217+
* Create the new test reports directory and write the test report
218+
*/
219+
const reportsDir = path.join(__dirname, '..', '.test-reports')
220+
221+
// Create reports directory if it doesn't exist
222+
if (!fs.existsSync(reportsDir)) {
223+
fs.mkdirSync(reportsDir, { recursive: true })
224+
}
225+
226+
fs.writeFileSync(path.join(reportsDir, 'report.xml'), xml)
227+
228+
const exitCodeArg = process.argv[2]
229+
if (exitCodeArg) {
230+
/**
231+
* Retrieves the exit code from the previous test run execution.
232+
*
233+
* This allows us to:
234+
* 1. Merge and upload test reports regardless of the test execution status
235+
* 2. Preserve the original test run exit code
236+
* 3. Report the test status back to CI
237+
*/
238+
const exitCode = parseInt(exitCodeArg || '0', 10)
239+
process.exit(exitCode)
240+
}
241+
}
242+
243+
createTestReport()

scripts/mergeReports.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)