make-error-cause icon indicating copy to clipboard operation
make-error-cause copied to clipboard

Tests failing

Open juliangilbey opened this issue 1 year ago • 2 comments

Hi! Maybe I've done something wrong, but I've tried building this from source and running the tests, but they're both failing:

$ nyc mocha


  make error cause
    1) should render the cause
    2) should render extra properties in the full stack


  0 passing (13ms)
  2 failing

  1) make error cause
       should render the cause:

      AssertionError: expected 'TestError: test boom!\n    at Context…' to equal 'TestError: test boom!\n    at Context…'
      + expected - actual

       TestError: test boom!
           at Context.<anonymous> (/home/jdg/debian/spyder-packages/jupyter-notebook/make-error-cause/node-make-error-cause-2.3.0/src/index.spec.ts:12:23)
           at callFn (/usr/share/nodejs/mocha/lib/runnable.js:364:21)
      -    ... 7 lines matching cause stack trace ...
      -    at processImmediate (node:internal/timers:483:21) {
      -  [cause]: Error: boom!
      -      at Context.<anonymous> (/home/jdg/debian/spyder-packages/jupyter-notebook/make-error-cause/node-make-error-cause-2.3.0/src/index.spec.ts:11:19)
      -      at callFn (/usr/share/nodejs/mocha/lib/runnable.js:364:21)
      -      at Test.Runnable.run (/usr/share/nodejs/mocha/lib/runnable.js:352:5)
      -      at Runner.runTest (/usr/share/nodejs/mocha/lib/runner.js:677:10)
      -      at /usr/share/nodejs/mocha/lib/runner.js:800:12
      -      at next (/usr/share/nodejs/mocha/lib/runner.js:592:14)
      -      at /usr/share/nodejs/mocha/lib/runner.js:602:7
      -      at next (/usr/share/nodejs/mocha/lib/runner.js:485:14)
      -      at Immediate._onImmediate (/usr/share/nodejs/mocha/lib/runner.js:570:5)
      -      at processImmediate (node:internal/timers:483:21)
      -}
      +    at Test.Runnable.run (/usr/share/nodejs/mocha/lib/runnable.js:352:5)
      +    at Runner.runTest (/usr/share/nodejs/mocha/lib/runner.js:677:10)
      +    at /usr/share/nodejs/mocha/lib/runner.js:800:12
      +    at next (/usr/share/nodejs/mocha/lib/runner.js:592:14)
      +    at /usr/share/nodejs/mocha/lib/runner.js:602:7
      +    at next (/usr/share/nodejs/mocha/lib/runner.js:485:14)
      +    at Immediate._onImmediate (/usr/share/nodejs/mocha/lib/runner.js:570:5)
      +    at processImmediate (node:internal/timers:483:21)
       
       The following exception was the direct cause of the above exception:
       
       Error: boom!
      
      at Context.<anonymous> (src/index.spec.ts:19:37)
      at callFn (/usr/share/nodejs/mocha/lib/runnable.js:364:21)
      at Test.Runnable.run (/usr/share/nodejs/mocha/lib/runnable.js:352:5)
      at Runner.runTest (/usr/share/nodejs/mocha/lib/runner.js:677:10)
      at /usr/share/nodejs/mocha/lib/runner.js:800:12
      at next (/usr/share/nodejs/mocha/lib/runner.js:592:14)
      at /usr/share/nodejs/mocha/lib/runner.js:602:7
      at next (/usr/share/nodejs/mocha/lib/runner.js:485:14)
      at Immediate._onImmediate (/usr/share/nodejs/mocha/lib/runner.js:570:5)
      at processImmediate (node:internal/timers:483:21)

  2) make error cause
       should render extra properties in the full stack:
     AssertionError: expected 'TestDataError: test boom!\n    at Con…' to include 'TestDataError: test boom!\n    at Con…'
      at Context.<anonymous> (src/index.spec.ts:58:15)
      at callFn (/usr/share/nodejs/mocha/lib/runnable.js:364:21)
      at Test.Runnable.run (/usr/share/nodejs/mocha/lib/runnable.js:352:5)
      at Runner.runTest (/usr/share/nodejs/mocha/lib/runner.js:677:10)
      at /usr/share/nodejs/mocha/lib/runner.js:800:12
      at next (/usr/share/nodejs/mocha/lib/runner.js:592:14)
      at /usr/share/nodejs/mocha/lib/runner.js:602:7
      at next (/usr/share/nodejs/mocha/lib/runner.js:485:14)
      at Immediate._onImmediate (/usr/share/nodejs/mocha/lib/runner.js:570:5)
      at processImmediate (node:internal/timers:483:21)

I wonder whether it's because make-error has changed its behaviour or something like that?

juliangilbey avatar Sep 11 '24 06:09 juliangilbey

I can confirm the same. Here is a visual comparison of the test failure:

Image

At first, I though that it's due to a difference in Node.js, so I tested with Node.js 16, 18, 20, 22, and 24, and got the same results every time.

The next thing to test is whether a newer version of make-error is causing this. I doubt it, because the latest release of make-error was v1.3.7 in 2020 according to github (5 years ago), although strangely enough, the latest version shown on npm is v1.3.6, also in 2020. I tested with every version of make-error from 1.2.3 to 1.3.6 and they all give the same test failure. Anything older than 1.2.3 results in a Typescript error.

rudolfbyker avatar Jul 25 '25 13:07 rudolfbyker

I still can't figure out when this changed, but since it did, we probably no longer need the custom inspect method, and this whole package could be replaced by a simple class like this:

/**
 * This is similar to https://github.com/blakeembrey/make-error-cause but improved and simplified:
 * - It does not depend on Node.js. See https://github.com/blakeembrey/make-error-cause/pull/61
 * - It supports `unknown` as the type of the `cause` property, since that is what the specification says.
 * - It does not have the custom inspect method, because the default `inspect` already shows the `cause` property.
 * - It does not define the cause property at all when it is `undefined` or `null`
 *   to prevent showing unnecessary `[cause]: undefined` in the stack trace.
 */

import { BaseError } from "make-error";

/**
 * Like `BaseError`, but supports the `cause` property.
 */
export class BaseErrorCause extends BaseError {
  public readonly cause?: unknown;

  constructor(message?: string, cause?: unknown) {
    super(message);

    if (cause !== undefined && cause !== null) {
      Object.defineProperty(this, "cause", {
        value: cause,
        writable: false,
        enumerable: false,
        configurable: false,
      });
    }
  }
}

And here is a unit test file with 100% coverage to go with it:

import { describe, expect, test } from "vitest";
import { BaseErrorCause } from "../src/baseErrorCause";
import { inspect } from "util";

class MyError extends BaseErrorCause {}

function hideStackTraceDetailsForUnitTesting(stack: string): string {
  return (
    stack
      // Remove the file paths, line numbers, and column numbers from the stack trace.
      .replace(/^(\s*at) .*$/gm, "$1 …")

      // Deduplicate lines.
      .split("\n")
      .filter((line, index, lines) => index === 0 || line !== lines[index - 1])
      .join("\n")
  );
}

describe("BaseErrorCause", () => {
  test("MyError without cause", () => {
    const err1 = new MyError("First error");
    expect(err1.message).toBe("First error");
    expect(err1.cause).toBeUndefined();
    expect(hideStackTraceDetailsForUnitTesting(inspect(err1))).toEqual(`\
MyError: First error
    at …`);
  });

  test("MyError caused by MyError", () => {
    const err1 = new MyError("First error");
    const err2 = new MyError("Second error", err1);
    expect(err2.message).toBe("Second error");
    expect(err2.cause).toBe(err1);
    expect(hideStackTraceDetailsForUnitTesting(inspect(err2))).toEqual(`\
MyError: Second error
    at …
    ... 7 lines matching cause stack trace ...
    at …
  [cause]: MyError: First error
      at …
}`);
  });

  test("MyError caused by MyError caused by MyError", () => {
    const err1 = new MyError("First error");
    const err2 = new MyError("Second error", err1);
    const err3 = new MyError("Third error", err2);
    expect(err3.message).toBe("Third error");
    expect(err3.cause).toBe(err2);
    expect(hideStackTraceDetailsForUnitTesting(inspect(err3))).toEqual(`\
MyError: Third error
    at …
    ... 7 lines matching cause stack trace ...
    at …
  [cause]: MyError: Second error
      at …
      ... 7 lines matching cause stack trace ...
      at …
    [cause]: MyError: First error
        at …
  }
}`);
  });

  test("MyError caused by string", () => {
    const err1 = new MyError("The error", "The cause");
    expect(err1.message).toBe("The error");
    expect(err1.cause).toBe("The cause");
    expect(hideStackTraceDetailsForUnitTesting(inspect(err1))).toEqual(`\
MyError: The error
    at …
  [cause]: 'The cause'
}`);
  });
});

So I guess there is no longer a real reason to use this package. Just copy my code above and have fun. :)

rudolfbyker avatar Jul 25 '25 18:07 rudolfbyker