Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(core): Log amazonq/toolkit tests at the bottom of the logs #6288

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildspec/linuxE2ETests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ phases:
commands:
- export HOME=/home/codebuild-user
# Ignore failure until throttling issues are fixed.
- xvfb-run npm run testE2E; npm run mergeReports -- "$?"
- xvfb-run npm run testE2E; npm run createTestReport -- "$?"
- VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}"
- CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g')
- CI_BUILD_ID="${CODEBUILD_BUILD_ID}"
Expand Down
2 changes: 1 addition & 1 deletion buildspec/linuxIntegrationTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ phases:
build:
commands:
- export HOME=/home/codebuild-user
- xvfb-run npm run testInteg; npm run mergeReports -- "$?"
- xvfb-run npm run testInteg; npm run createTestReport -- "$?"
- VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}"
- CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g')
- CI_BUILD_ID="${CODEBUILD_BUILD_ID}"
Expand Down
2 changes: 1 addition & 1 deletion buildspec/linuxTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ phases:
# Ensure that "foo | run_and_report" fails correctly.
set -o pipefail
. buildspec/shared/common.sh
{ 2>&1 xvfb-run npm test --silent; npm run mergeReports -- "$?"; } | run_and_report 2 \
{ 2>&1 xvfb-run npm test --silent; npm run createTestReport -- "$?"; } | run_and_report 2 \
'rejected promise not handled' \
'This typically indicates a bug. Read https://developer.mozilla.org/docs/Web/JavaScript/Guide/Using_promises#error_handling'
}
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
"buildCustomLintPlugin": "npm run build -w plugins/eslint-plugin-aws-toolkits",
"compile": "npm run compile -w packages/",
"testCompile": "npm run testCompile -w packages/ --if-present",
"test": "npm run test -w packages/ --if-present",
"testWeb": "npm run testWeb -w packages/ --if-present",
"testE2E": "npm run testE2E -w packages/ --if-present",
"testInteg": "npm run testInteg -w packages/ --if-present",
"test": "npm run test -w packages/ --if-present; npm run createTestReport",
"testWeb": "npm run testWeb -w packages/ --if-present; npm run createTestReport",
"testE2E": "npm run testE2E -w packages/ --if-present; npm run createTestReport",
"testInteg": "npm run testInteg -w packages/ --if-present; npm run createTestReport",
"package": "npm run package -w packages/toolkit -w packages/amazonq",
"newChange": "echo 'Must specify subproject/workspace with -w packages/<subproject>' && false",
"createRelease": "echo 'Must specify subproject/workspace with -w packages/<subproject>' && false",
Expand All @@ -37,7 +37,7 @@
"clean": "npm run clean -w packages/ -w plugins/",
"reset": "npm run clean && ts-node ./scripts/clean.ts node_modules && npm install",
"generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present",
"mergeReports": "ts-node ./scripts/mergeReports.ts"
"createTestReport": "ts-node ./scripts/createTestReport.ts"
},
"devDependencies": {
"@aws-toolkits/telemetry": "^1.0.289",
Expand Down
28 changes: 28 additions & 0 deletions packages/amazonq/test/unit/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,32 @@ describe('package validations', function () {
)
assert.deepStrictEqual(packageJson.contributes.icons, corePackageJson.contributes.icons)
})

describe('foo', () => {
it('bar1', () => {
assert.ok(true)
})
describe('fi', () => {
it('bar2', () => {
assert.ok(true)
})
describe('fo234', () => {
it('bar3', () => {
throw new Error('foo')
})
})
})
})

describe('package validations', () => {
it('wee', () => {
assert.ok(true)
})

describe('foo', () => {
it('wee 2', () => {
assert.ok(true)
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ describe('AppNode', () => {

describe('getTreeItem', () => {
it('should return a TreeItem with the correct properties', () => {
assert.fail('failed')
const treeItem = appNode.getTreeItem()
const expextedLabel = path.join('VSCode Example Workspace', 'Project One Root Folder')

Expand Down
242 changes: 242 additions & 0 deletions scripts/createTestReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as fs from 'fs'
import * as xml2js from 'xml2js'

interface TestFailure {
$: {
message: string
}
_: string
}

interface TestCase {
$: {
classname: string
name: string
time: string
}
failure?: TestFailure[]
}

interface TestSuite {
$: {
name: string
tests: string
failures: string
errors: string
time: string
file: string
}
testcase: TestCase[] | undefined
}

interface TestReport {
testsuites: {
testsuite: TestSuite[]
}
}

interface TestSummary {
totalTests: number
totalFailures: number
totalTime: number
failedTests: FailedTest[]
}

interface FailedTest {
suite: string
test: string
message: string
contents: string
path: string[]
}

/**
* Merge all of the packages/ test reports into a single directory
*/
async function createTestReport() {
console.log('Merging test reports')

const packagesDir = `${__dirname}/../packages`

// Get all packages/* directories
const packageDirs = fs.readdirSync(packagesDir).map((dir) => `${packagesDir}/${dir}`)

// Find report.xml files in .test-reports subdirectories
const testReports = packageDirs
.map((dir) => `${dir}/.test-reports/report.xml`)
.filter((file) => fs.existsSync(file))

const mergedReport: TestReport = {
testsuites: {
testsuite: [],
},
}

const failedTests: FailedTest[] = []
let totalTests = 0
let totalFailures = 0
let totalTime = 0

let filePath = ''
let suites = new Set<string>()

/**
* Collect all test reports into a single merged test report object.
* Also keeps track of test count, test failures, and test run time
*/
for (const file of testReports) {
const content = fs.readFileSync(file)
const result: { testsuites: { testsuite: TestSuite[] } } = await xml2js.parseStringPromise(content)
if (result.testsuites && result.testsuites.testsuite) {
for (const suite of result.testsuites.testsuite) {
if (suite.$.file !== filePath) {
filePath = suite.$.file
suites = new Set<string>()
}

for (const testcase of suite.testcase ?? []) {
if (testcase.failure) {
const testPath = parseTestHierarchy(suites, testcase.$.classname, suite.$.name, testcase.$.name)
failedTests.push({
suite: suite.$.name,
test: testcase.$.name,
message: testcase.failure[0].$.message,
contents: testcase.failure[0]._,
path: testPath,
})
}
}

totalTests += parseInt(suite.$.tests, 10)
totalFailures += parseInt(suite.$.failures, 10)
totalTime += parseFloat(suite.$.time)

suites.add(suite.$.name)
}

mergedReport.testsuites.testsuite.push(...result.testsuites.testsuite)
}
}

printTestSummary({
totalTests,
totalFailures,
totalTime,
failedTests,
})

writeReport(mergedReport)
}

/**
* Extracts and constructs a hierarchical test path from a test case identifier
*
* @param suites - Set of known test suite names
* @param className - Name of the test class
* @param suiteName - Name of the test suite
* @param testcaseName - Full name of the test case
* @example
* parseTestHierarchy(new Set(["package validations"]), 'bar1', 'foo', 'package validations foo bar1') -> ["package validations", "bar1", "foo"]
* @returns An array of path components representing the test hierarchy
*/
function parseTestHierarchy(suites: Set<string>, className: string, suiteName: string, testcaseName: string) {
let remainingPath = testcaseName
remainingPath = remainingPath.substring(0, remainingPath.lastIndexOf(className))
remainingPath = remainingPath.substring(0, remainingPath.lastIndexOf(suiteName))

const pathComponents = remainingPath.trim().split(' ')
let index = 0
let currentComponent = pathComponents[0]
const path = []
while (remainingPath.length > 0) {
index++
if (!suites.has(currentComponent)) {
currentComponent = currentComponent + ' ' + pathComponents[index]
} else {
path.push(currentComponent)
remainingPath = remainingPath.substring(currentComponent.length).trim()
currentComponent = pathComponents[index]
}
}

path.push(suiteName)
path.push(className)

return path
}

function printTestSummary({ totalTests, totalFailures, totalTime, failedTests }: TestSummary) {
const passingTests = totalTests - totalFailures
const pendingTests = 0

console.log(`${passingTests} passing (${Math.round(totalTime)}s)`)
if (pendingTests > 0) {
console.log(`${pendingTests} pending`)
}
if (totalFailures > 0) {
console.log(`${totalFailures} failing`)

failedTests.forEach((test, index) => {
let indent = ' '

for (let x = 0; x < test.path.length; x++) {
if (x == 0) {
console.log(`${indent}${index + 1}) ${test.path[x]}`)
indent += ' '
} else {
console.log(`${indent}${test.path[x]}`)
}
indent += ' '
}

if (test.contents) {
// Indent the stack trace
console.log(
test.contents
.split('\n')
.map((line) => `${indent}${line}`)
.join('\n')
)
}
console.log() // Add empty line between failures
})
}
}

function writeReport(mergedReport: TestReport) {
const builder = new xml2js.Builder()
const xml = builder.buildObject(mergedReport)

/**
* Create the new test reports directory and write the test report
*/
const reportsDir = `${__dirname}/../.test-reports`

// Create reports directory if it doesn't exist
if (!fs.existsSync(reportsDir)) {
fs.mkdirSync(reportsDir, { recursive: true })
}

fs.writeFileSync(`${reportsDir}/report.xml`, xml)

const exitCodeArg = process.argv[2]
if (exitCodeArg) {
/**
* Retrieves the exit code from the previous test run execution.
*
* This allows us to:
* 1. Merge and upload test reports regardless of the test execution status
* 2. Preserve the original test run exit code
* 3. Report the test status back to CI
*/
const exitCode = parseInt(exitCodeArg || '0', 10)
process.exit(exitCode)
}
}

createTestReport()
Loading
Loading