Jest: RangeError: Maximum call stack size exceeded with `.toBe` on a large string

Created on 10 Sep 2019  ·  10Comments  ·  Source: facebook/jest

🐛 Bug Report

    expect(["....big string here"]).toBe("another 3MB string");

Writing code like this, jest will throw RangeError, here is the error message:

  ● Test suite failed to run

    RangeError: Maximum call stack size exceeded
        at String.match (<anonymous>)

      at exports.separateMessageFromStack.content (node_modules/jest-message-util/build/index.js:368:32)
          at Array.map (<anonymous>)

the Array expect function got has an item that is an about 3MB string (base64 image), and toBe got the same base64 String.

To Reproduce

like above.

Expected behavior

should fail the test, but not throw range error

Link to repl or repo (highly encouraged)

envinfo

  System:
    OS: macOS 10.14.6
    CPU: (4) x64 Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
  Binaries:
    Node: 11.2.0 - ~/.nvm/versions/node/v11.2.0/bin/node
    Yarn: 1.12.3 - /usr/local/bin/yarn
    npm: 6.4.1 - ~/.nvm/versions/node/v11.2.0/bin/npm
  npmPackages:
    jest: ^23.6.0 => 23.6.0 
Bug

All 10 comments

Does it also happen with jest@24 or jest@next? cc @pedrottimark.
Also a repro would be very helpful

node.js doesn't seem to like really long strings. I'm able to reproduce the bug and I think I found a fix. But testing this takes ages. Especially with my fix, as the console output grinds to a halt.

Anyway: string.match() crashes, probably due to a recursive approach. I'm not sure if the used regular expression can be changed to avoid this recursion.

  // Problematic regex:
  const messageMatch = content.match(/(^(.|\n)*?(?=\n\s*at\s.*\:\d*\:\d*))/); 
  let message = messageMatch ? messageMatch[0] : 'Error';
  const stack = messageMatch ? content.slice(message.length) : content;

My monkeypatched fix:

  const callstackPos = content.search(/((\n\s*at\s.*\:\d*\:\d*))/);
  let message = callstackPos ? content.substring(0, callstackPos) : 'Error';
  const stack = callstackPos ? content.substring(callstackPos+1) : content; 

That should work the same, but cuts match() out, instead relying on cutting by hand.

Reproducion:

it("compares the strings correcty", () => {
    expect(string1 + "w").toBe(string1);
});

whereas string1 is just a 3000000 character long string I randomly generated (be aware: some IDEs might not like this).

While it fails here https://github.com/facebook/jest/blob/736edd2ea6c9aadfb6e8794ecdc8a726f8a76b1a/packages/jest-message-util/src/index.ts#L358-L360, I think it makes more sense to avoid huge strings here: https://github.com/facebook/jest/blob/736edd2ea6c9aadfb6e8794ecdc8a726f8a76b1a/packages/jest-matcher-utils/src/index.ts#L307-L313 rather than trying to fix the regex part. This ends up generating a huge string, which is used when throwing the assertion error, which we later run the regex against. I think such a huge string is not useful anyways

That said, the change @StringEpsilon suggests seems reasonable regardless?

Yes, the way Jest hangs is as annoying as #1772 and #5392 but for different reason

Because base64 strings do not contain newlines, it was:

  • not a problem with line diff, for a change
  • not a problem with substring diff, because guarded with MAX_DIFF_STRING_LENGTH

Can y’all suggest any good examples of software tools that Jest might follow to limit:

  • length of strings
  • number of lines

in the report when a matcher fails?

@Genuifx To help me think what is relevant information that Jest should provide, the type mismatch of array as received value and string as expected value was which of the following:

  • error in the test: how the assertion was written or an incorrect assumption about behavior
  • error in the code: return an array instead of a string

I'm not entirely sure if a unit test suite does that, but think a good way of dealing with overly long lines would just be to chop to the relevant section. That would be the start of the differences + whatever surroundings can be added reasonably.

Samples for comparing against SBjb21wcmVoZW5zaXZlIEphdmFTY3JpcHQgdGVzdGluZyBzb2x1dGlvbi4gV29ya3Mgb3V0IG9mIHRoZSBib3ggZm9yIG1vc3QgSmF2YVNjcmlwdCBwcm9qZWN0cy4K

Expected: "SBjb21wcmVoZW5zaXZlIEphdmFTY3JpcHQgdGVz" + [88]
Received: "_Z_Bjb21wcmVoZW5zaXZlIEphdmFTY3JpcHQgdGVz" + [88]

Expected: [66] + "Mgb3V0I_G9ml_HRoZSBib3ggZm9yIG1vc" + [31 ]
Receveived: [66] + "Mgb3V0I_G9ML_HRoZSBib3ggZm9yIG1vc" + [31]

Expected: [80] + "ZSBib3ggZm9yIG1vc3QgSmF2YVNjcmlwdCBwcm9qZWN0cy4_K_"
Received: [80] + "ZSBib3ggZm9yIG1vc3QgSmF2YVNjcmlwdCBwcm9qZWN0cy4_L_"

It's not entirely uncommon for compilers or intrepeters to chop lines to the relevant section. I'm not entirely sure how much overhead this would introduce. I'm also not sure about the UX aspects of chopping strings that should be equal but aren't (i.E. introducing headaches to the guy debugging that test)

@pedrottimark error in the test is preferred. Actually, That was my bug while mocking some kind of function.

@StringEpsilon I was doing redux compatible work for mini-app, one case needs consider is the store's data is too large to connect the page (every single time setData call only 1024kb data can be transferred between threads), so I used jest writing some related test suites.

I was getting this error with .toMatchObject so to solve this I JSON.stringified the object and checked toMatch instead of checking the object.

This happen when I use functions with two ways. This will be generate call stack error because Javascript is single thread, example:

const a = () => {
const b = b();
const c = "hello"
return b + c
}

const b = () => {
const a = a()
const c = "hello"
return a + c
}

Function a call function b and function b call function a, this will fill the call stack and generate this error

Was this page helpful?
0 / 5 - 0 ratings