jest: Cannot load native module when running under jest

šŸ› Bug Report

I can load a native module in a regular script just fine, but I cannot do the same in a spec being executed by jest.

To Reproduce

Steps to reproduce the behavior:

Install native module like this:

$ npm config set @sap:registry https://npm.sap.com
$ npm install @sap/hana-client

Reproducible example:

$ cat package.json
{
  "name": "foo",
  "version": "1.0.0",
  "description": "",
  "main": "foobar.spec.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@sap/hana-client": "^2.3.123",
    "jest": "^23.6.0"
  }
}
$ cat foobar.spec.js
require("@sap/hana-client");
$ node -v
v8.12.0
$ DEBUG=*
$ node foobar.spec.js
  @sap/hana-client:index Starting index.js +0ms
  @sap/hana-client:index Attempting to load Hana node-hdbcapi driver +3ms
  @sap/hana-client:index ... Trying user-built copy... +0ms
  @sap/hana-client:index ... Looking for user-built copy in /Users/else/code/foo/node_modules/@sap/hana-client/build/Release/hana-client.node ...  +0ms
  @sap/hana-client:index Not found. +0ms
  @sap/hana-client:index ... Trying prebuilt copy... +0ms
  @sap/hana-client:index ... Looking for prebuilt copy in /Users/else/code/foo/node_modules/@sap/hana-client/prebuilt/darwinintel64-xcode7/hana-client_v8.node ...  +0ms
  @sap/hana-client:index Loaded. +50ms
  @sap/hana-client:index Success. +0ms
$ echo $?
0
$ node_modules/.bin/jest --runInBand
  @sap/hana-client:index Starting index.js +0ms
  @sap/hana-client:index Attempting to load Hana node-hdbcapi driver +1ms
  @sap/hana-client:index ... Trying user-built copy... +0ms
  @sap/hana-client:index ... Looking for user-built copy in /Users/else/code/foo/node_modules/@sap/hana-client/build/Release/hana-client.node ...  +0ms
  @sap/hana-client:index Not found. +0ms
  @sap/hana-client:index ... Trying prebuilt copy... +0ms
  @sap/hana-client:index ... Looking for prebuilt copy in /Users/else/code/foo/node_modules/@sap/hana-client/prebuilt/darwinintel64-xcode7/hana-client_v8.node ...  +0ms
  @sap/hana-client:index Failed to load DBCAPI. +2ms
  @sap/hana-client:index Could not load: Prebuilt copy did not satisfy requirements. +0ms
  @sap/hana-client:index Could not load modules for Platform: 'darwin', Process Arch: 'x64', and Version: 'v8.12.0' +0ms
 FAIL  ./foobar.spec.js
  ā— Test suite failed to run

    Failed to load DBCAPI.

      at /Users/else/code/foo/node_modules/jest-runtime/build/index.js.requireModule (../node_modules/jest-runtime/build/index.js:372:31)
      at /Users/else/code/foo/node_modules/@sap/hana-client/lib/index.js.Object.<anonymous> (node_modules/@sap/hana-client/lib/index.js:127:14)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.022s
Ran all test suites.

Expected behavior

No error related to module loading

Link to repl or repo (highly encouraged)

https://transfer.sh/(/JOeLK/example.tar.gz).tar.gz

Run npx envinfo --preset jest

Paste the results here:

$ npx envinfo --preset jest
npx: installed 1 in 3.457s

  System:
    OS: macOS 10.14
    CPU: (4) x64 Intel(R) Core(TM) i7-5557U CPU @ 3.10GHz
  Binaries:
    Node: 8.12.0 - ~/.nvm/versions/node/v8.12.0/bin/node
    Yarn: 1.10.1 - ~/.nvm/versions/node/v8.12.0/bin/yarn
    npm: 6.4.1 - ~/.nvm/versions/node/v8.12.0/bin/npm
  npmPackages:
    jest: ^23.6.0 => 23.6.0

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 23 (6 by maintainers)

Most upvoted comments

We were having the same issues combining the SAP Hana client library with Jest.

Setting environment variables was no solution for us, since we are working on different systems (Windows, Linux, Mac). The following steps solved it for us:

  1. set the global setup parameter in the jest config file:
globalSetup: "<rootDir>/sapHana.config.js",
  1. create a config file (in this example: sapHana.config.js in the root folder) which sets the environment variable:
var path = require('path');

module.exports = async () => {
    var extensions = {
        'darwin': 'dylib',
        'linux': 'so',
        'win32': 'dll'
    };
    
    // Look for prebuilt binary and DBCAPI based on platform
    var pb_subdir = null;
    if (process.platform === 'linux') {
        if (process.arch === 'x64') {
            pb_subdir = 'linuxx86_64-gcc48';
        } else if (process.arch.toLowerCase().indexOf('ppc') != -1 && os.endianness() === 'LE') {
            pb_subdir = 'linuxppc64le-gcc48';
        } else {
            pb_subdir = 'linuxppc64-gcc48';
        }
    } else if (process.platform === 'win32') {
        pb_subdir = 'ntamd64-msvc2010';
    } else if (process.platform === 'darwin') {
        pb_subdir = 'darwinintel64-xcode7';
    }
    
    var modpath = path.dirname(require.resolve("@sap/hana-client/README.md"));
    var pb_path = path.join(modpath, 'prebuilt', pb_subdir);
    var dbcapi = process.env['DBCAPI_API_DLL'] || path.join(pb_path, 'libdbcapiHDB.' + extensions[process.platform]);
    
    process.env['DBCAPI_API_DLL'] = dbcapi;
  };

Please note that this was tested with version 2.4.126 of @sap/hana-client and things might change with newer versions.

Thanks for sharing, IMO itā€™s much easier though to just require("@sap/hana-client/build.js"); in your jest config.

Is there anything under the scenes that would explain why the native (nodejs) require for native modules would behave different via jest?

The problem here is with process.env.

Every test file is run inside VM module. But VM context does not have Node globals(require, process and etc), so Jest modifies these globals and then injects them in. VM and these modifications are needed to ensure all test files run in isolation. a.test.js should not affect the outcome of b.test.js.

process.env is one of the globals which is modified. Jest makes a new object from the prototype of process.env and reimplements the behaviour. Problem is that process.env is a bit special since Node stores env variables in C++ core and passes it down to child processes and native bindings if needed.

So if you do this in normal Node process

process.env['DBCAPI_API_DLL'] = "/path_to_my_project/node_modules/@sap/hana-client/prebuilt/darwinintel64-xcode7/libdbcapiHDB.dylib";

All the child processes and native bindings can see this value in their Environment Variables. This is how hana-client works. It read this value from Environment Variables and then tries to load the file.

The reason it does not work with Jest is because Jestā€™s process.env is just a Javascript object. When you set a value on it, this update only exists in Javascriptā€™s context. Child processes and Native bindings cannot see it. Since hana-client cannot see this value it fails. But if you do it outside of test context, like in global setup it should work.

I donā€™t think this issue is solvable from Jestā€™s codebase, you will have to use the workarounds described above. Nodejs only has one environment per process. if you run your code in VM module, you get a new Javascript Context but you cannot create new environment without creating a new process.

It seems the issue still exists. It fails even when hard coding the DBCAPI_API_DLL and loading the hana-client like below.

process.env['DBCAPI_API_DLL'] = "/path_to_my_project/node_modules/@sap/hana-client/prebuilt/darwinintel64-xcode7/libdbcapiHDB.dylib";
require('/path_to_my_project/node_modules/@sap/hana-client/prebuilt/darwinintel64-xcode7/hana-client_v10.node');

seems that the native module loading done thru jest, behaves different than not via jest.

The stack trace is as below:

Error: Failed to load DBCAPI.
    at internal/modules/cjs/loader.js.Module._extensions..node (internal/modules/cjs/loader.js:807:18)
    at internal/modules/cjs/loader.js.Module.load (internal/modules/cjs/loader.js:653:32)
    at internal/modules/cjs/loader.js.tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at internal/modules/cjs/loader.js.Module._load (internal/modules/cjs/loader.js:585:3)
    at internal/modules/cjs/loader.js.Module.require (internal/modules/cjs/loader.js:692:17)
    at internal/modules/cjs/helpers.js.require (internal/modules/cjs/helpers.js:25:18)
    at /path_to_my_project/node_modules/jest-runtime/build/index.js._loadModule (/path_to_my_project/node_modules/jest-runtime/build/index.js:673:29)
    at /path_to_my_project/node_modules/jest-runtime/build/index.js.requireModule (/path_to_my_project/node_modules/jest-runtime/build/index.js:536:10)
    at /path_to_my_project/node_modules/jest-runtime/build/index.js.requireModuleOrMock (/path_to_my_project/node_modules/jest-runtime/build/index.js:699:21)
    at /path_to_my_project/node_modules/@sap/hana-client/lib/index.js.Object.<anonymous> (/path_to_my_project/node_modules/@sap/hana-client/lib/index.js:112:14)

Is there anything under the scenes that would explain why the native (nodejs) require for native modules would behave different via jest? Or there are more variables required to be set for loading that hana-client module.

Oh my bad.

https://github.com/facebook/jest/blob/master/packages/jest-util/src/createProcessObject.js#L19 I changed a return statement of this function with return process.env