Skip to content

Commit

Permalink
feat: Emit metadata.exception in test runs
Browse files Browse the repository at this point in the history
Fixes #26
  • Loading branch information
dividedmind committed Nov 25, 2023
1 parent 18d4f48 commit 84bda66
Show file tree
Hide file tree
Showing 16 changed files with 462 additions and 13 deletions.
12 changes: 7 additions & 5 deletions src/AppMap.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ namespace AppMap {
source_location?: SourceLocation;
test_status?: TestStatus;
test_failure?: TestFailure;
exception?: {
/* note this is different from Exception in an event */
class: string;
message?: string;
};
exception?: ExceptionMetadata;
}

interface ExceptionMetadata {
/* note this is different from Exception in an event */
class: string;
message?: string;
}

export interface LanguageMetadata {
Expand Down
2 changes: 1 addition & 1 deletion src/Recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export default class Recording {
const written = this.stream?.close(
compactObject({
classMap: makeClassMap(this.functionsSeen.keys()),
metadata: this.metadata,
metadata: compactObject(this.metadata),
eventUpdates: Object.keys(this.eventUpdates).length > 0 ? this.eventUpdates : undefined,
}),
);
Expand Down
2 changes: 1 addition & 1 deletion src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function paramName(param: ESTree.Parameter | undefined): string | undefined {
}
}

function examineException(exception: unknown): AppMap.Exception[] {
export function examineException(exception: unknown): AppMap.Exception[] {
if (!(typeof exception === "object" && exception)) return [];
const name = getClass(exception);
const message =
Expand Down
12 changes: 10 additions & 2 deletions src/hooks/jest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { pathToFileURL } from "node:url";

import type { Circus } from "@jest/types";
import { simple as walk } from "acorn-walk";
import type { ESTree } from "meriyah";
import type { Circus } from "@jest/types";

import { expressionFor, wrap } from ".";
import AppMap from "../AppMap";
import Recording from "../Recording";
import { call_, identifier } from "../generate";
import { info } from "../message";
import Recording from "../Recording";
import { exceptionMetadata } from "../metadata";
import { recording, start } from "../recorder";
import genericTranform from "../transform";
import { isId } from "../util/isId";
Expand Down Expand Up @@ -51,6 +53,7 @@ function eventHandler(event: Circus.Event) {
break;
case "test_fn_failure":
recording.metadata.test_status = "failed";
recording.metadata.exception = extractTestError(event.test.errors);
return recording.finish();
case "test_fn_success":
recording.metadata.test_status = "succeeded";
Expand All @@ -77,3 +80,8 @@ 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);
}
5 changes: 4 additions & 1 deletion src/hooks/mocha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Recording from "../Recording";
import { call_, this_ } from "../generate";
import { recording, start } from "../recorder";
import { info } from "../message";
import { exceptionMetadata } from "../metadata";

export function shouldInstrument(url: URL): boolean {
return url.pathname.endsWith("/mocha/lib/runner.js");
Expand Down Expand Up @@ -64,8 +65,10 @@ function registerEventListeners(runner: EventEmitter) {
});

// eslint-disable-next-line @typescript-eslint/no-unused-vars
runner.on(EVENT_TEST_FAIL, function (test: Test) {
runner.on(EVENT_TEST_FAIL, function (test: Test, err: unknown) {
console.log(test);
recording.metadata.test_status = "failed";
recording.metadata.exception = exceptionMetadata(err);
recording.finish();
});
}
7 changes: 6 additions & 1 deletion src/hooks/vitest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import worker from "node:worker_threads";
import { simple as walk } from "acorn-walk";
import { type ESTree } from "meriyah";

import Recording from "../Recording";
import { args as args_, assignment, call_, identifier, literal, member, ret } from "../generate";
import { info, warn } from "../message";
import { recording, start } from "../recorder";
import Recording from "../Recording";
import genericTransform from "../transform";

function createInitChannel() {
Expand Down Expand Up @@ -80,6 +80,10 @@ export async function wrapRunTest(
break;
case "fail":
recording.metadata.test_status = "failed";
if (test.result.errors?.length) {
const [{ name, message }] = test.result.errors;
recording.metadata.exception = { class: name, message };
}
break;
default:
warn(`Test result not understood for test ${test.name}: ${test.result?.state}`);
Expand Down Expand Up @@ -188,6 +192,7 @@ interface Suite {
}
interface TaskResult {
state?: "pass" | "fail";
errors?: Error[];
}

function testNames(test: Test): string[] {
Expand Down
6 changes: 6 additions & 0 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { PackageJson } from "type-fest";

import type AppMap from "./AppMap";
import { appName } from "./config";
import { examineException } from "./event";
import pick from "./util/pick";

// cannot use import because it's outside src
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand All @@ -24,3 +26,7 @@ export const defaultMetadata: Partial<AppMap.Metadata> & { client: AppMap.Client
},
app: appName,
};

export function exceptionMetadata(exc: unknown): AppMap.ExceptionMetadata | undefined {
return pick(examineException(exc)[0], "class", "message");
}
7 changes: 7 additions & 0 deletions src/util/pick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function pick<T extends object, K extends keyof T>(
obj: T,
...keys: K[]
): Pick<T, K> {
const filtered = Object.entries(obj).filter(([k]) => keys.includes(k as K)) as [K, unknown][];
return Object.fromEntries(filtered) as Pick<T, K>;
}
80 changes: 80 additions & 0 deletions test/__snapshots__/jest.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,79 @@

exports[`mapping Jest tests 1`] = `
{
"./tmp/appmap/jest/exception handling/intentionally throws.appmap.json": {
"classMap": [
{
"children": [
{
"children": [
{
"location": "test.js:17",
"name": "errorOut",
"static": true,
"type": "function",
},
],
"name": "test",
"type": "class",
},
],
"name": "test",
"type": "package",
},
],
"events": [
{
"defined_class": "",
"event": "call",
"id": 1,
"lineno": 17,
"method_id": "errorOut",
"parameters": [],
"path": "test.js",
"static": true,
"thread_id": 0,
},
{
"elapsed": 31.337,
"event": "return",
"exceptions": [
{
"class": "TestError",
"message": "test error",
"object_id": 2,
},
],
"id": 2,
"parent_id": 1,
"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": "TestError",
"message": "test error",
},
"language": {
"engine": "Node.js",
"name": "javascript",
"version": "test node version",
},
"name": "exception handling intentionally throws",
"recorder": {
"name": "jest",
"type": "tests",
},
"test_status": "failed",
},
"version": "1.12",
},
"./tmp/appmap/jest/sub/subtracts numbers correctly.appmap.json": {
"classMap": [
{
Expand Down Expand Up @@ -65,6 +138,13 @@ exports[`mapping Jest tests 1`] = `
"url": "https://github.com/getappmap/appmap-node",
"version": "test node-appmap version",
},
"exception": {
"class": "JestAssertionError",
"message": "expect(received).toBe(expected) // Object.is equality
Expected: -1
Received: 3",
},
"language": {
"engine": "Node.js",
"name": "javascript",
Expand Down
77 changes: 77 additions & 0 deletions test/__snapshots__/mocha.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,79 @@

exports[`mapping Mocha tests 1`] = `
{
"./tmp/appmap/mocha/exception handling/intentionally throws.appmap.json": {
"classMap": [
{
"children": [
{
"children": [
{
"location": "./test.js:18",
"name": "errorOut",
"static": true,
"type": "function",
},
],
"name": "test",
"type": "class",
},
],
"name": "test",
"type": "package",
},
],
"events": [
{
"defined_class": "",
"event": "call",
"id": 1,
"lineno": 18,
"method_id": "errorOut",
"parameters": [],
"path": "./test.js",
"static": true,
"thread_id": 0,
},
{
"elapsed": 31.337,
"event": "return",
"exceptions": [
{
"class": "TestError",
"message": "test error",
"object_id": 2,
},
],
"id": 2,
"parent_id": 1,
"thread_id": 0,
},
],
"metadata": {
"app": "mocha-appmap-node-test",
"client": {
"name": "appmap-node",
"url": "https://github.com/getappmap/appmap-node",
"version": "test node-appmap version",
},
"exception": {
"class": "TestError",
"message": "test error",
},
"language": {
"engine": "Node.js",
"name": "javascript",
"version": "test node version",
},
"name": "exception handling intentionally throws",
"recorder": {
"name": "mocha",
"type": "tests",
},
"test_status": "failed",
},
"version": "1.12",
},
"./tmp/appmap/mocha/multiply/calculates power correctly.appmap.json": {
"classMap": [
{
Expand Down Expand Up @@ -65,6 +138,10 @@ exports[`mapping Mocha tests 1`] = `
"url": "https://github.com/getappmap/appmap-node",
"version": "test node-appmap version",
},
"exception": {
"class": "AssertionError",
"message": "8 == 9",
},
"language": {
"engine": "Node.js",
"name": "javascript",
Expand Down
Loading

0 comments on commit 84bda66

Please sign in to comment.