Jest: sequentially run tests are failed

Does anybody know what is the reason of failing test that are run sequentially ( with runInBand flag). The error message is “Unable to register default event loop: Failed to add external loop. Capacity is 1 [-1]”. Is there workaround how to manage this issue?

Hi @Bvv,

The Node.js Aerospike Client use Mocha and Chai as libraries to do testing. If you are trying the run the unit tests for the Node.js Aerospike Client, --runInBand is not a flag that exists in Mocha, so it cannot be used.

If you are writing your own tests with Jest that leverage the Node.js Aerospike Client, I would need to see the tests to determine why the issue is happening. Feel free to post a reproducible example here, and I can comment on potential fixes then.

Hello @dpelini Thank you very much for your response. I’m trying to create integration tests with Jest. I prepared repo to reproduce the issue GitHub - vbuzivskoy/aerospike-client-jest There are two jest tests that could be run in parallel and in serial. To reproduce the issue please run the tests in serial.

Hello @dpelini. Have you had a chance to reproduce the issue?

Hello @Bvv / @dpelini , Do we have any update on this issue ?

@Katyar_Samir @Bvv I have reproduced the issue, but I have not found a solution yet. I will provide another update later this week on the status. Sorry for the delay.

@dpelini One more thing I’ve noticed is that if I use -detectOpenHandles, this issue is also happening without the --runInBand option.

jest --detectOpenHandles

“aerospike”: “^3.13.0” “jest”: “^26.6.3”

Node : v10.23.0

I have the same issue when running Jest tests for multiple different modules. After investigation I understood that the problem appears only when executing tests serially, parallel execution works fine. The reason is that during parallel execution Jest runs each test in a separate OS process. Aerospike nodejs library initialises the event loop once register_as_event_loop C function. This function creates a native underlying event loop and forces 1 instance of it. Jest cleans up nodejs modules after each run, so for subsequent modules the Aerospike nodejs module is reloaded and during this the Aerospike nodejs module tries to re-create the event loop. If it runs in the same process, the underlying native event loop already exists and register_as_event_loop function fails with this error.

Theoretically I could use Client.close(true) call, which, if called with ‘true’ should force releasing native event loop. Unfortunately due to a bug (?) in the native Aerospike C client it does not. The issue is in the line aerospike-client-c/src/main/aerospike/as_event.c at master · aerospike/aerospike-client-c · GitHub the call to as_event_destroy_loops() is done inside if (as_event_threads_created && status), but Aerospike nodejs client calls as_event_set_external_loop_capacity(1) before creating the event loop, which in turn always sets as_event_threads_created to false. I guess as_event_destroy_loops() should be always called after joining threads in if(as_event_threads_created && status) {...} block.

As a result, the first native event loop created from nodejs is never released, and any subsequent attempts to re-create it fail.

Hello all,

Sorry for the delay, but I have developed a solution that should be suitable.

The Aerospike Module relies on the Node.js require cache to maintain certain information such as how many clients have been opened or if the eventLoop has been initialized. For more info on the require cache, see here

As a testing platform, Jest does not maintain the require cache between tests, presumably to provide a clean state for each test file. This is problematic when using the Aerospike Client because eventLoop initialization status need to be maintained to know if a new eventLoop should be initialized or the existing eventLoop should be referenced.

We can workaround this behavior by using a custom Jest environment to maintain the state of the Aerospike module.

const NodeEnvironment = require('jest-environment-node').TestEnvironment;
const Aerospike = require('aerospike');
class CustomEnvironment extends NodeEnvironment {
  constructor(config, context) {
    super(config, context);
    this.testPath = context.testPath;
    this.docblockPragmas = context.docblockPragmas;
  }

  async setup() {
    await super.setup();
    this.global.__SHARED_MODULE__ = Aerospike;

  }

  async teardown() {
    this.global.__SHARED_MODULE__.releaseEventLoop()
    await super.teardown();
  }

  getVmContext() {
    return super.getVmContext();
  }

}

module.exports = CustomEnvironment;

This allows Aerospike to cache the eventLoop status between test runs, while allowing multiple client to open and close. The eventLoop is released inside the teardown() function.

The Aerospike module can be required in the test files by doing the following:

/**
 * @jest-environment ./tests/aerospikeEnvironment.js
 */
const { sleep } = require('./util');

let Aerospike

beforeAll(() => {
  Aerospike = global.__SHARED_MODULE__
});
describe("Test Suite B", () => {
    it("Test Case B", async () => {

The file is specified using the @jest-environment tag, and you can use the aerospike module through the global.__SHARED_MODULE object.

Using these steps, I was able to get jest to run with the --runInband flag as well as with parallel execution. The solution code is available in a Github branch here.

@Matviy Your understanding of the problem is completely correct. However, the eventLoop is only intended to be closed upon process shut down, and not intended to be reopened. This is not a bug with the Aerospike module, but rather a jest configuration issue.

If you are having any issues with the solution provided, please let me know and I can continue to troubleshoot. Again, sorry for the delay.

cc @Katyar_Samir @Bvv

Additional sources:

Bending Jest to Our Will: Caching Modules Across Tests

testEnvironment jest docs

1 Like