Skip to content

Commit

Permalink
improved handling information between workers and main process
Browse files Browse the repository at this point in the history
  • Loading branch information
DavertMik committed Jan 19, 2025
1 parent 5b4b9a6 commit 851e690
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 51 deletions.
9 changes: 0 additions & 9 deletions lib/command/run-workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,6 @@ module.exports = async function (workerCount, selectedRuns, options) {
})

workers.on(event.all.result, result => {
event.dispatcher.emit(event.workers.result, {
...result?.simplify(),
suites: suiteArr,
tests: {
passed: passedTestArr,
failed: failedTestArr,
skipped: skippedTestArr,
},
})
workers.printResults()
})

Expand Down
4 changes: 2 additions & 2 deletions lib/command/workers/runTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ function initializeListeners() {
event.dispatcher.on(event.step.passed, step => sendToParentThread({ event: event.step.passed, workerIndex, data: step.simplify() }))
event.dispatcher.on(event.step.failed, step => sendToParentThread({ event: event.step.failed, workerIndex, data: step.simplify() }))

event.dispatcher.on(event.hook.failed, (test, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: { ...test.simplify(), err } }))
event.dispatcher.on(event.hook.passed, (test, err) => sendToParentThread({ event: event.hook.passed, workerIndex, data: { ...test.simplify(), err } }))
event.dispatcher.on(event.hook.failed, (hook, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: { ...hook.simplify(), err } }))
event.dispatcher.on(event.hook.passed, (hook, err) => sendToParentThread({ event: event.hook.passed, workerIndex, data: { ...hook.simplify(), err } }))

event.dispatcher.once(event.all.after, () => {
sendToParentThread({ event: event.all.after, workerIndex, data: container.result().simplify() })
Expand Down
1 change: 0 additions & 1 deletion lib/listener/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ module.exports = function () {
})

event.dispatcher.on(event.test.started, test => {
container.result().addStats({ tests: 1 })
container.result().addTest(test)
})
}
12 changes: 12 additions & 0 deletions lib/mocha/hooks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const event = require('../event')
const { serializeError } = require('../utils')
// const { serializeTest } = require('./test')

class Hook {
constructor(context, error) {
Expand All @@ -13,6 +15,16 @@ class Hook {
return this.constructor.name.replace('Hook', '')
}

simplify() {
return {
hookName: this.hookName,
title: this.title,
// test: this.test ? serializeTest(this.test) : null,
// suite: this.suite ? serializeSuite(this.suite) : null,
error: this.error ? serializeError(this.error) : null,
}
}

toString() {
return this.hookName
}
Expand Down
19 changes: 11 additions & 8 deletions lib/mocha/test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const Test = require('mocha/lib/test')
const Suite = require('mocha/lib/suite')
const { test: testWrapper } = require('./asyncWrapper')
const { enhanceMochaSuite } = require('./suite')
const { enhanceMochaSuite, createSuite } = require('./suite')
const { genTestId, serializeError, clearString } = require('../utils')

const Step = require('../step/base')
/**
* Factory function to create enhanced tests
* @param {string} title - Test title
Expand Down Expand Up @@ -67,15 +67,18 @@ function enhanceMochaTest(test) {
}

function deserializeTest(test) {
test = Object.assign(new Test(test.title || '', () => {}), test)
test = Object.assign(
createTest(test.title || '', () => {}),
test,
)
test.parent = Object.assign(new Suite(test.parent.title), test.parent)
enhanceMochaTest(test)
enhanceMochaSuite(test.parent)
test.steps = test.steps.map(step => Object.assign(new Step(step.title), step))
return test
}

function serializeTest(test, err = null) {
test = { ...test }
// test = { ...test }

if (test.start && !test.duration) {
const end = +new Date()
Expand Down Expand Up @@ -107,14 +110,14 @@ function serializeTest(test, err = null) {
uid: test.uid,
retries: test._retries,
title: test.title,
status: test.status,
state: test.state,
notes: test.notes || [],
meta: test.meta || {},
artifacts: test.artifacts || [],
artifacts: test.artifacts || {},
duration: test.duration || 0,
err,
parent,
steps: [...test.steps].map(step => step.simplify()),
steps: [...test.steps].map(step => (step.simplify ? step.simplify() : step)),
}
}

Expand Down
55 changes: 36 additions & 19 deletions lib/plugin/analyze.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const debug = require('debug')('codeceptjs:analyze')
const { isMainThread } = require('node:worker_threads')
const { arrowRight } = require('figures')
const container = require('../container')
const ai = require('../ai')
const colors = require('chalk')
Expand All @@ -16,10 +17,11 @@ const defaultConfig = {
prompts: {
clusterize: testsAndErrors => {
const serializedFailedTests = testsAndErrors
.map(({ test, error }, index) => {
.map((test, index) => {
if (!test || !test.err) return
return `
TEST #${index + 1}: ${serializeTest(test)}
ERROR: ${serializeError(error).slice(0, MAX_DATA_LENGTH / testsAndErrors.length)}`
ERROR: ${serializeError(test.err).slice(0, MAX_DATA_LENGTH / testsAndErrors.length)}`
})
.join('\n\n---\n\n')

Expand Down Expand Up @@ -96,7 +98,7 @@ const defaultConfig = {
Pick one of the categories of failures and explain it.
Common causes of failures:
Common causes of failures in order of priority:
* Browser connection error / browser crash
* Network errors (server error, timeout, etc)
Expand All @@ -118,9 +120,9 @@ const defaultConfig = {
If you have suggestions for the test, write them in SUMMARY section.
Inside SUMMARY write exact values, if you have suggestions, explain which information you used to suggest.
Be concise, each section should not take more than one sentence.
Response format:
CATEGORY <category_of_failure>
STEPS <step_of_failure>
SUMMARY <explanation_of_failure>
Expand All @@ -141,38 +143,46 @@ const defaultConfig = {
module.exports = function (config = {}) {
config = Object.assign(defaultConfig, config)

let failedTestsAndErrors = []
event.dispatcher.on(event.all.result, async result => {
if (!isMainThread) return // run only on main thread
if (!ai.isEnabled) {
console.log('AI is disabled, no analysis will be performed. Run tests with --ai flag to enable it.')
return
}

event.dispatcher.on(event.test.failed, (test, error) => {
if (!ai.isEnabled) return
failedTestsAndErrors.push({ test, error })
printReport(result)
})

event.dispatcher.on(event.all.result, async () => {
event.dispatcher.on(event.workers.result, async result => {
if (!ai.isEnabled) {
console.log('AI is disabled, no analysis will be performed. Run tests with --ai flag to enable it.')
return
}

printReport(result)
})

async function printReport(result) {
const failedTestsAndErrors = result.tests.filter(t => t.state === 'failed' && t.err)

if (!failedTestsAndErrors.length) return
if (!isMainThread) return // run only on main thread

debug(failedTestsAndErrors.map(e => serializeTest(e.test) + '\n' + serializeError(e.error)))
debug(failedTestsAndErrors.map(t => serializeTest(t) + '\n' + serializeError(t.err)))

console.log()
console.log(colors.bold.white('🪄 AI REPORT:'))

try {
if (failedTestsAndErrors.length >= config.clusterize) {
const response = await clusterize()
const response = await clusterize(failedTestsAndErrors)
console.log(response)
return
}

output.plugin('analyze', `Analyzing first ${config.analyze} failed tests...`)

const uniqueErrors = failedTestsAndErrors.filter((item, index, array) => {
return array.findIndex(t => serializeError(t.error) === serializeError(item.error)) === index
return array.findIndex(t => serializeError(t.err) === serializeError(item.err)) === index
})

for (let i = 0; i < config.analyze; i++) {
Expand All @@ -185,19 +195,20 @@ module.exports = function (config = {}) {

console.log()
console.log('--------------------------------')
console.log(colors.bold.white(uniqueErrors[i].test.fullTitle()))
console.log(arrowRight, colors.bold.white(uniqueErrors[i].fullTitle()))
console.log()
console.log(response)
}
} catch (err) {
console.error('Error analyzing failed tests', err)
}

if (!container.plugins('pageInfo')) {
if (!Object.keys(container.plugins()).includes('pageInfo')) {
console.log('To improve analysis, enable pageInfo plugin to get more context for failed tests.')
}
})
}

async function clusterize() {
async function clusterize(failedTestsAndErrors) {
const spinner = ora('Clusterizing failures...').start()
const prompt = config.prompts.clusterize(failedTestsAndErrors)
try {
Expand All @@ -212,7 +223,7 @@ module.exports = function (config = {}) {

async function analyze(failedTestAndError) {
const spinner = ora('Analyzing failure...').start()
const prompt = config.prompts.analyze(failedTestAndError.test, failedTestAndError.error)
const prompt = config.prompts.analyze(failedTestAndError, failedTestAndError.err)
try {
const response = await ai.createCompletion(prompt)
spinner.stop()
Expand All @@ -225,6 +236,12 @@ module.exports = function (config = {}) {
}

function serializeError(error) {
if (typeof error === 'string') {
return error
}

if (!error) return

let errorMessage = 'ERROR: ' + error.message

if (error.inspect) {
Expand Down
4 changes: 2 additions & 2 deletions lib/plugin/heal.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ module.exports = function (config = {}) {
}
})

event.dispatcher.on(event.workers.result, ({ tests }) => {
event.dispatcher.on(event.workers.result, result => {
const { print } = output

const healedTests = Object.values(tests)
const healedTests = Object.values(result.tests)
.flat()
.filter(test => test.notes.some(note => note.type === 'heal'))
if (!healedTests.length) return
Expand Down
2 changes: 0 additions & 2 deletions lib/plugin/pageInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ function pageStateToMarkdown(pageState) {
markdown += '\n\n'
}

markdown += `### ${ucfirst(humanizeString(key))}\n\n`

return markdown
}

Expand Down
21 changes: 20 additions & 1 deletion lib/result.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const fs = require('fs')
const path = require('path')
const { serializeTest } = require('./mocha/test')

class Result {
constructor() {
Expand Down Expand Up @@ -52,6 +53,12 @@ class Result {
}

addTest(test) {
const existingTestIndex = this._tests.findIndex(t => !!t.uid && t.uid === test.uid)
if (existingTestIndex >= 0) {
this._tests[existingTestIndex] = test
return
}

this._tests.push(test)
}

Expand All @@ -72,12 +79,24 @@ class Result {
return this._endTime ? +this._endTime - +this._startTime : 0
}

get failedTests() {
return this._tests.filter(test => test.state === 'failed')
}

get passedTests() {
return this._tests.filter(test => test.state === 'passed')
}

get skippedTests() {
return this._tests.filter(test => test.state === 'skipped' || test.state === 'pending')
}

simplify() {
return {
hasFailed: this.hasFailed,
stats: this.stats,
duration: this.duration,
tests: this._tests.map(test => test.simplify()),
tests: this._tests.map(test => serializeTest(test)),
failures: this._failures,
}
}
Expand Down
13 changes: 7 additions & 6 deletions lib/step/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,22 +185,23 @@ class Step {
if (step.args) {
for (const arg of step.args) {
// check if arg is a JOI object
if (arg && arg.$_root) {
args.push(JSON.stringify(arg).slice(0, 300))
} else if (arg && typeof arg === 'function') {
if (arg && typeof arg === 'function') {
args.push(arg.name)
} else {
} else if (typeof arg == 'string') {
args.push(arg)
} else {
args.push(JSON.stringify(arg).slice(0, 300))
}
}
}

return {
opts: step.opts || {},
title: step.name,
args: JSON.stringify(args),
args: args,
status: step.status,
duration: step.duration || 0,
startTime: step.startTime,
endTime: step.endTime,
parent,
}
}
Expand Down
8 changes: 7 additions & 1 deletion lib/workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,12 @@ class Workers extends EventEmitter {

switch (message.event) {
case event.all.result:
// we ensure consistency of result by adding tests in the very end
Container.result().addFailures(message.data.failures)
Container.result().addStats(message.data.stats)
message.data.tests.forEach(test => {
Container.result().addTest(deserializeTest(test))
})
break
case event.suite.before:
this.emit(event.suite.before, deserializeSuite(message.data))
Expand Down Expand Up @@ -424,7 +428,7 @@ class Workers extends EventEmitter {
this.emit(event.step.passed, message.data)
break
case event.step.failed:
this.emit(event.step.failed, message.data)
this.emit(event.step.failed, message.data, message.data.error)
break
}
})
Expand All @@ -450,6 +454,7 @@ class Workers extends EventEmitter {
}

this.emit(event.all.result, Container.result())
event.dispatcher.emit(event.workers.result, Container.result())
this.emit('end') // internal event
}

Expand All @@ -473,6 +478,7 @@ class Workers extends EventEmitter {
}

output.result(result.stats.passes, result.stats.failures, result.stats.pending, ms(result.duration), result.stats.failedHooks)

process.env.RUNS_WITH_WORKERS = 'false'
}
}
Expand Down

0 comments on commit 851e690

Please sign in to comment.