From 30c0df9835b5a9991c6376210c449ab69418761d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Sat, 2 Dec 2023 17:03:45 +0100 Subject: [PATCH] fix: Correctly handle timed out tests in Jest When a test times out, Jest passes a message string back as an error instead of an Error instace. Handle this case. Fixes #46 --- src/hooks/jest.ts | 8 +-- src/metadata.ts | 6 +- test/__snapshots__/jest.test.ts.snap | 82 +++++++++++++++++++++++++++- test/jest/test.js | 7 +++ 4 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/hooks/jest.ts b/src/hooks/jest.ts index 67a2ec80..14573929 100644 --- a/src/hooks/jest.ts +++ b/src/hooks/jest.ts @@ -5,7 +5,6 @@ import { simple as walk } from "acorn-walk"; import type { ESTree } from "meriyah"; import { expressionFor, wrap } from "."; -import AppMap from "../AppMap"; import Recording from "../Recording"; import { call_, identifier } from "../generate"; import { info } from "../message"; @@ -53,7 +52,7 @@ function eventHandler(event: Circus.Event) { break; case "test_fn_failure": recording.metadata.test_status = "failed"; - recording.metadata.exception = extractTestError(event.test.errors); + recording.metadata.exception = exceptionMetadata(event.error); return recording.finish(); case "test_fn_success": recording.metadata.test_status = "succeeded"; @@ -80,8 +79,3 @@ function createRecording(test: Circus.TestEntry): Recording { const recording = new Recording("tests", "jest", ...testNames(test)); return recording; } - -function extractTestError([error]: Circus.TestError[]): AppMap.ExceptionMetadata | undefined { - const exc = (Array.isArray(error) ? error[0] : error) as unknown; - if (exc) return exceptionMetadata(exc); -} diff --git a/src/metadata.ts b/src/metadata.ts index d33c5b73..4fa59b9c 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -27,6 +27,8 @@ export const defaultMetadata: Partial & { client: AppMap.Client app: appName, }; -export function exceptionMetadata(exc: unknown): AppMap.ExceptionMetadata | undefined { - return pick(examineException(exc)[0], "class", "message"); +export function exceptionMetadata(error: unknown): AppMap.ExceptionMetadata | undefined { + const [exc] = examineException(error); + if (exc) return pick(exc, "class", "message"); + if (typeof error === "string") return { message: error, class: "String" }; } diff --git a/test/__snapshots__/jest.test.ts.snap b/test/__snapshots__/jest.test.ts.snap index bd2ba27e..854681c3 100644 --- a/test/__snapshots__/jest.test.ts.snap +++ b/test/__snapshots__/jest.test.ts.snap @@ -9,7 +9,7 @@ exports[`mapping Jest tests 1`] = ` { "children": [ { - "location": "test.js:17", + "location": "test.js:18", "name": "errorOut", "static": true, "type": "function", @@ -28,7 +28,7 @@ exports[`mapping Jest tests 1`] = ` "defined_class": "", "event": "call", "id": 1, - "lineno": 17, + "lineno": 18, "method_id": "errorOut", "parameters": [], "path": "test.js", @@ -75,6 +75,84 @@ exports[`mapping Jest tests 1`] = ` }, "version": "1.12", }, + "./tmp/appmap/jest/exception handling/times out.appmap.json": { + "classMap": [ + { + "children": [ + { + "children": [ + { + "location": "test.js:22", + "name": "wait", + "static": true, + "type": "function", + }, + ], + "name": "test", + "type": "class", + }, + ], + "name": "test", + "type": "package", + }, + ], + "events": [ + { + "defined_class": "", + "event": "call", + "id": 1, + "lineno": 22, + "method_id": "wait", + "parameters": [ + { + "class": "Number", + "name": "ms", + "value": "20", + }, + ], + "path": "test.js", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 2, + "parent_id": 1, + "return_value": { + "class": "Promise", + "object_id": 3, + "value": "Promise { }", + }, + "thread_id": 0, + }, + ], + "metadata": { + "app": "jest-appmap-node-test", + "client": { + "name": "appmap-node", + "url": "https://github.com/getappmap/appmap-node", + "version": "test node-appmap version", + }, + "exception": { + "class": "String", + "message": "Exceeded timeout of 10 ms for a test. +Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout.", + }, + "language": { + "engine": "Node.js", + "name": "javascript", + "version": "test node version", + }, + "name": "exception handling times out", + "recorder": { + "name": "jest", + "type": "tests", + }, + "test_status": "failed", + }, + "version": "1.12", + }, "./tmp/appmap/jest/sub/subtracts numbers correctly.appmap.json": { "classMap": [ { diff --git a/test/jest/test.js b/test/jest/test.js index 12f0aa78..fecbeb9d 100644 --- a/test/jest/test.js +++ b/test/jest/test.js @@ -1,3 +1,4 @@ +const { setTimeout } = require("timers/promises"); const { sum, sub } = require("./calc"); describe(sum, () => { @@ -18,6 +19,12 @@ function errorOut() { throw new TestError("test error"); } +async function wait(ms) { + await setTimeout(ms); +} + describe("exception handling", () => { it("intentionally throws", errorOut); + + it("times out", () => wait(20), 10); });