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

[Jolt AI] Fix Node.js test parser #133

Closed
wants to merge 2 commits into from
Closed
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
4 changes: 4 additions & 0 deletions constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export const EVENT_TEST_FAIL = 'test:fail'
export const EVENT_TEST_DIAGNOSTIC = 'test:diagnostic'

export const ERROR_CODE_TEST_FAILURE = 'ERR_TEST_FAILURE'

// Event structure validation constants
export const REQUIRED_EVENT_FIELDS = ['type', 'data']
export const REQUIRED_TEST_FIELDS = ['name', 'file', 'details']
5 changes: 4 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ export interface Report {
duration: number,
}

export default function parseReport(report: ReadableStream): Report
/**
* Parses test runner output into a structured report format
*/
export default function parseReport(report: ReadableStream, debug?: boolean): Report
48 changes: 31 additions & 17 deletions parse-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import {
} from './constants.js'
import { URL, fileURLToPath } from 'node:url'

const durationRegex = /duration_ms\s([\d.]+)/
const durationRegex = /duration(?:_ms)?\s*([\d.]+)/i
const debug = process.env.DEBUG ? console.log : () => {}

function validateEvent(event) {
if (!event?.type || !event?.data) throw new Error('Invalid event structure')
return event
}

export default async function parseReport(source) {
const tests = []
Expand Down Expand Up @@ -46,11 +52,12 @@ export default async function parseReport(source) {
}

for await (const event of source) {
debug('Processing event:', event)
validateEvent(event)

switch (event.type) {
case EVENT_TEST_START:
const {
data: { name, file }
} = event
const { data: { name = 'unnamed test', file = '' } = {} } = event

resetDiagnosticMessage()

Expand All @@ -66,9 +73,9 @@ export default async function parseReport(source) {
case EVENT_TEST_FAIL:
const {
data: {
details: { duration_ms: duration, error },
skip,
todo
details: { duration_ms: duration = 0, error } = {},
skip = false,
todo = false
}
} = event

Expand All @@ -78,14 +85,23 @@ export default async function parseReport(source) {
currentTest.todo = todo !== undefined

if (error) {
const {
cause: { code }
} = error

if (code === ERROR_CODE_TEST_FAILURE || code === undefined) {
currentTest.error = error
} else {
try {
const { cause } = error
const code = cause?.code

// Normalize error structure
if (!cause) {
error.cause = { code: ERROR_CODE_TEST_FAILURE }
}

if (code === ERROR_CODE_TEST_FAILURE || code === undefined) {
currentTest.error = error
} else {
currentTest.failure = error
}
currentTest.failure = error
} catch (e) {
console.error('Error processing test failure:', e)
}
}

Expand All @@ -102,9 +118,7 @@ export default async function parseReport(source) {
break

case EVENT_TEST_DIAGNOSTIC:
const {
data: { message }
} = event
const { data: { message = '' } = {} } = event

const durationMatch = message.match(durationRegex)
if (durationMatch) {
Expand Down
8 changes: 8 additions & 0 deletions test/e2e/compare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ remove_variables() {
# Run sample tests and generate the report, ignoring errors
report=$(node --test --test-reporter ./test/resources/reporter.js ./test/resources/sample-tests || true)

# Output debug information if DEBUG is set
if [ ! -z "$DEBUG" ]; then
echo "Raw test output:"
echo "$report"
echo "Processed test output (after removing variables):"
echo "$(remove_variables "$report")"
fi

# Compare with expected results
expected=$(cat ./test/resources/expected.json)
diff <(remove_variables "$report") <(echo "$expected")
27 changes: 27 additions & 0 deletions test/resources/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,33 @@
"duration": 0,
"skip": false,
"todo": true
},
{
"name": "event structure handling",
"file": "test/resources/sample-tests/event-structure.test.js",
"tests": [
{
"name": "throws error on completely invalid event",
"file": "test/resources/sample-tests/event-structure.test.js",
"tests": [],
"duration": 0,
"skip": false,
"todo": false,
"error": {
"failureType": "testCodeFailure",
"cause": { "code": "ERR_TEST_FAILURE" }
}
},
{
"name": "handles missing optional fields with defaults",
"file": "test/resources/sample-tests/event-structure.test.js",
"tests": [],
"duration": 0,
"skip": false,
"todo": false
}
],
"duration": 0
}
],
"duration": 0
Expand Down
44 changes: 44 additions & 0 deletions test/resources/sample-tests/event-structure.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from 'node:assert'
import { describe, it } from 'node:test'
import parseReport from '../../../parse-report.js'

describe('event structure handling', () => {
it('throws error on completely invalid event', async () => {
const invalidStream = new ReadableStream({
async start(controller) {
controller.enqueue({})
controller.close()
}
})

await assert.rejects(() => parseReport(invalidStream), {
message: 'Invalid event structure'
})
})

it('handles missing optional fields with defaults', async () => {
const streamWithMissingFields = new ReadableStream({
async start(controller) {
controller.enqueue({
type: 'test:start',
data: {}
})
controller.enqueue({
type: 'test:pass',
data: {
details: {}
}
})
controller.close()
}
})

const report = await parseReport(streamWithMissingFields)
const test = report.tests[0]

assert.strictEqual(test.name, 'unnamed test')
assert.strictEqual(test.file, '')
assert.strictEqual(test.duration, 0)
assert.strictEqual(test.skip, false)
})
})
Loading