Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Slow jest test memory leak #2161

Open
vespertilian opened this issue Aug 31, 2023 · 13 comments
Open

[Bug]: Slow jest test memory leak #2161

vespertilian opened this issue Aug 31, 2023 · 13 comments

Comments

@vespertilian
Copy link

Version

13.1.0

Steps to reproduce

Clone repo https://github.com/vespertilian/jest-preset-angular-mem-usage
npm install
npm run test-jest

Expected behavior

I would expect for simple tests memory to not increase at an alarming rate.

Actual behavior

Testing Angular seem to leak memory which makes the tests run really slow on older computers when you have multiple libraries

image

Some more info here NX

nrwl/nx#18926

Thanks for all your had work on this!

Additional context

No response

Environment

System:
    OS: Windows 10 10.0.22621
    CPU: (32) x64 13th Gen Intel(R) Core(TM) i9-13900HX
  Binaries:
    Node: 18.17.1 - C:\Program Files\nodejs\node.EXE
    npm: 9.6.7 - C:\Program Files\nodejs\npm.CMD
  npmPackages:
    jest: ^29.6.4 => 29.6.4

This is the faster computer the other computers we have are a lot slower
@haskelcurry
Copy link

This is extremely critical, it's literally unusable now. Any temporary workarounds, please?

@platov
Copy link

platov commented Sep 12, 2023

I've tried to run your reproducing repo with chrome inspector and do not see any memory leaks. Yes, jest reports memory growth for each file BUT it is normal behaviour because of the Garbage Collector didn't triggered yet. If i manually trigger GC then memory footprint becomes ~160MB.

The slowness that i see happens because of for each spec file Jest executes setup-jest.js file that does this:

require('zone.js/bundles/zone-testing-bundle.umd');
const { getTestBed } = require('@angular/core/testing');
const {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting,
} = require('@angular/platform-browser-dynamic/testing');

const testEnvironmentOptions = globalThis.ngJest?.testEnvironmentOptions ?? Object.create(null);

getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), testEnvironmentOptions);

As we can see that for each simple spec file:

describe('AppComponent', () => {
    it('dummy test', () => {
        expect(1).toEqual(1);
    });
});

we bootstrap the Angulars testing module.

Without extra bootstrap logic tests pass in 13 seconds instead of 40 seconds in my laptop.

Just to confirm my assumptions regarding memory leak we can launch nodejs instance with the hard limit by available memory to force V8 GC to run much frequently. Lets limit heap size to 300MB:

jest-preset-angular-mem-usage % node --max-old-space-size=300 node_modules/.bin/jest --runInBand --logHeapUsage  --detectOpenHandles

 PASS  src/app/app.component.94.spec.ts (97 MB heap size)
 PASS  src/app/app.component.26.spec.ts (120 MB heap size)
 PASS  src/app/app.component.73.spec.ts (153 MB heap size)
 PASS  src/app/app.component.76.spec.ts (157 MB heap size)
 PASS  src/app/app.component.1.spec.ts (192 MB heap size)
 PASS  src/app/app.component.11.spec.ts (194 MB heap size)
 PASS  src/app/app.component.96.spec.ts (204 MB heap size)
 PASS  src/app/app.component.100.spec.ts (237 MB heap size)
 PASS  src/app/app.component.78.spec.ts (242 MB heap size)
 PASS  src/app/app.component.79.spec.ts (160 MB heap size)
 PASS  src/app/app.component.63.spec.ts (159 MB heap size)
 PASS  src/app/app.component.5.spec.ts (197 MB heap size)
 PASS  src/app/app.component.4.spec.ts (186 MB heap size)
 PASS  src/app/app.component.77.spec.ts (196 MB heap size)
 PASS  src/app/app.component.23.spec.ts (228 MB heap size)
 PASS  src/app/app.component.39.spec.ts (234 MB heap size)
 PASS  src/app/app.component.24.spec.ts (238 MB heap size)
 PASS  src/app/app.component.87.spec.ts (152 MB heap size)
 PASS  src/app/app.component.57.spec.ts (154 MB heap size)
 PASS  src/app/app.component.58.spec.ts (185 MB heap size)
 PASS  src/app/app.component.55.spec.ts (168 MB heap size)
 PASS  src/app/app.component.64.spec.ts (202 MB heap size)
 PASS  src/app/app.component.37.spec.ts (207 MB heap size)
 PASS  src/app/app.component.56.spec.ts (230 MB heap size)
 PASS  src/app/app.component.75.spec.ts (239 MB heap size)
 PASS  src/app/app.component.13.spec.ts (244 MB heap size)
 PASS  src/app/app.component.97.spec.ts (155 MB heap size)
 PASS  src/app/app.component.95.spec.ts (162 MB heap size)
 PASS  src/app/app.component.48.spec.ts (195 MB heap size)
 PASS  src/app/app.component.15.spec.ts (171 MB heap size)
 PASS  src/app/app.component.16.spec.ts (203 MB heap size)
 PASS  src/app/app.component.10.spec.ts (209 MB heap size)
 PASS  src/app/app.component.80.spec.ts (230 MB heap size)
 PASS  src/app/app.component.spec.ts (211 MB heap size)
 PASS  src/app/app.component.9.spec.ts (219 MB heap size)
 PASS  src/app/app.component.74.spec.ts (252 MB heap size)
 PASS  src/app/app.component.61.spec.ts (243 MB heap size)
 PASS  src/app/app.component.68.spec.ts (144 MB heap size)
 PASS  src/app/app.component.21.spec.ts (145 MB heap size)
 PASS  src/app/app.component.41.spec.ts (178 MB heap size)
 PASS  src/app/app.component.52.spec.ts (172 MB heap size)
 PASS  src/app/app.component.31.spec.ts (207 MB heap size)
 PASS  src/app/app.component.53.spec.ts (212 MB heap size)
 PASS  src/app/app.component.42.spec.ts (234 MB heap size)
 PASS  src/app/app.component.65.spec.ts (215 MB heap size)
 PASS  src/app/app.component.27.spec.ts (223 MB heap size)
 PASS  src/app/app.component.46.spec.ts (256 MB heap size)
 PASS  src/app/app.component.40.spec.ts (245 MB heap size)
 PASS  src/app/app.component.34.spec.ts (146 MB heap size)
 PASS  src/app/app.component.3.spec.ts (155 MB heap size)
 PASS  src/app/app.component.71.spec.ts (180 MB heap size)
 PASS  src/app/app.component.43.spec.ts (181 MB heap size)
 PASS  src/app/app.component.92.spec.ts (192 MB heap size)
 PASS  src/app/app.component.50.spec.ts (223 MB heap size)
 PASS  src/app/app.component.90.spec.ts (230 MB heap size)
 PASS  src/app/app.component.18.spec.ts (253 MB heap size)
 PASS  src/app/app.component.14.spec.ts (221 MB heap size)
 PASS  src/app/app.component.12.spec.ts (201 MB heap size)
 PASS  src/app/app.component.81.spec.ts (208 MB heap size)
 PASS  src/app/app.component.59.spec.ts (239 MB heap size)
 PASS  src/app/app.component.49.spec.ts (200 MB heap size)
 PASS  src/app/app.component.88.spec.ts (193 MB heap size)
 PASS  src/app/app.component.62.spec.ts (224 MB heap size)
 PASS  src/app/app.component.66.spec.ts (223 MB heap size)
 PASS  src/app/app.component.60.spec.ts (247 MB heap size)
 PASS  src/app/app.component.67.spec.ts (241 MB heap size)
 PASS  src/app/app.component.44.spec.ts (147 MB heap size)
 PASS  src/app/app.component.83.spec.ts (152 MB heap size)
 PASS  src/app/app.component.28.spec.ts (185 MB heap size)
 PASS  src/app/app.component.17.spec.ts (181 MB heap size)
 PASS  src/app/app.component.93.spec.ts (213 MB heap size)
 PASS  src/app/app.component.54.spec.ts (206 MB heap size)
 PASS  src/app/app.component.51.spec.ts (239 MB heap size)
 PASS  src/app/app.component.85.spec.ts (248 MB heap size)
 PASS  src/app/app.component.82.spec.ts (205 MB heap size)
 PASS  src/app/app.component.45.spec.ts (210 MB heap size)
 PASS  src/app/app.component.36.spec.ts (241 MB heap size)
 PASS  src/app/app.component.25.spec.ts (249 MB heap size)
 PASS  src/app/app.component.19.spec.ts (251 MB heap size)
 PASS  src/app/app.component.91.spec.ts (151 MB heap size)
 PASS  src/app/app.component.84.spec.ts (164 MB heap size)
 PASS  src/app/app.component.8.spec.ts (198 MB heap size)
 PASS  src/app/app.component.30.spec.ts (189 MB heap size)
 PASS  src/app/app.component.47.spec.ts (201 MB heap size)
 PASS  src/app/app.component.2.spec.ts (232 MB heap size)
 PASS  src/app/app.component.89.spec.ts (238 MB heap size)
 PASS  src/app/app.component.86.spec.ts (244 MB heap size)
 PASS  src/app/app.component.6.spec.ts (170 MB heap size)
 PASS  src/app/app.component.33.spec.ts (168 MB heap size)
 PASS  src/app/app.component.69.spec.ts (204 MB heap size)
 PASS  src/app/app.component.20.spec.ts (193 MB heap size)
 PASS  src/app/app.component.70.spec.ts (199 MB heap size)
 PASS  src/app/app.component.72.spec.ts (230 MB heap size)
 PASS  src/app/app.component.7.spec.ts (237 MB heap size)
 PASS  src/app/app.component.32.spec.ts (240 MB heap size)
 PASS  src/app/app.component.29.spec.ts (167 MB heap size)
 PASS  src/app/app.component.38.spec.ts (161 MB heap size)
 PASS  src/app/app.component.22.spec.ts (195 MB heap size)
 PASS  src/app/app.component.99.spec.ts (192 MB heap size)
 PASS  src/app/app.component.98.spec.ts (215 MB heap size)
 PASS  src/app/app.component.35.spec.ts (214 MB heap size)

Test Suites: 101 passed, 101 total
Tests:       101 passed, 101 total
Snapshots:   0 total
Time:        41.712 s
Ran all test suites.

@vespertilian
Copy link
Author

vespertilian commented Sep 13, 2023

@platov Thanks for looking into this.

Yes if you lower the max memory memory garbage collection will be triggered before it grows beyond the limit. However I think it is still leaking, checkout this article:

https://chanind.github.io/javascript/2019/10/12/jest-tests-memory-leak.html

The memory should be mostly constant especially for simple test like the ones in the sample repo.

setup-jest.js I don't think is in my sample repo. It is in this jest-preset-angular repo.

Search for file in my repo

*I forked this sample repo FYI. If you can find the file for me that would be great.

Search for file in this repo

Looks like you just changed it 18 minutes ago

#2163

I will retest and see if this has improved the situation.

@platov
Copy link

platov commented Sep 13, 2023

@vespertilian, i agree that Jest has memory leak itself that is not related to jest-preset-angular.
Details can be found in this ticket.

To solve Jest memory leak issue in my project helped usage of the --no-compilation-cache flag for nodejs.
Try to run your test like in the following example:
node --no-compilation-cache node_modules/jest/bin/jest

setup-jest.js I don't think is in my sample repo. It is in this jest-preset-angular repo.

Sorry i was not clear. The bootstrap logic of Angular testing module is placed here. If You remove import 'jest-preset-angular/setup-jest'; line then sample tests will be executed faster.

@vespertilian
Copy link
Author

@platov Thanks for this.

Yes moving out setup jest does speed it up, however you need setup jest if you want to test anything meaningful, like a component. I want to dig into this a bit more, I won't have time until later this week.

Thanks again. Stay tuned.

@BernhardBehrendt
Copy link

BernhardBehrendt commented Dec 5, 2023

@vespertilian Did you find something that's worth to share?
I'm in this rabbithole for a while now and was happy to find at least this issue.

What I've found out so far was updating to node 20.10.x brought some improvements but more as a positive side effect due to optimizations on "fs"

But all in all the tests feel much too slow for what's really happening and it's very time consuming in the hooks/pipeline.
And it's impossible to execute on older laptops

Bildschirmfoto 2023-12-05 um 14 31 22 1

@vespertilian
Copy link
Author

@BernhardBehrendt

I have been looking at it again today. No luck yet. I will probably do a bit more on Monday or Tuesday.

Weirdly this

node --no-compilation-cache --expose-gc node_modules/jest/bin/jest --logHeapUsage

Seems to fix the issue in my test repo but you need --expose-gc ass well as --no-compilation-cache

Are you using NX or just the Angular CLI?

@BernhardBehrendt
Copy link

BernhardBehrendt commented Dec 18, 2023

Hey @vespertilian I'm using Angular using NX and all in the latest version.

Edit:
I've tried your provided command and it's more less the same as before. My Computer (MBPM2) gets so overloaded that even music starts to fragmentize.

@ricardomiguelmoura
Copy link

ricardomiguelmoura commented Dec 28, 2023

@platov Thanks for this.

Yes moving out setup jest does speed it up, however you need setup jest if you want to test anything meaningful, like a component. I want to dig into this a bit more, I won't have time until later this week.

Thanks again. Stay tuned.

Weirdly I tried this suggestion and improved my memory times. Do you know the why ?!?!

@BernhardBehrendt

I have been looking at it again today. No luck yet. I will probably do a bit more on Monday or Tuesday.

Weirdly this

node --no-compilation-cache --expose-gc node_modules/jest/bin/jest --logHeapUsage

Seems to fix the issue in my test repo but you need --expose-gc ass well as --no-compilation-cache

Are you using NX or just the Angular CLI?

Weirdly I tried this suggestion and improved my memory times. Do you know the why @BernhardBehrendt ?!?!

@BernhardBehrendt
Copy link

Just made the upgrade to 14.0.0 and it's still no noticeable improvement.
@ricardomiguelmoura retried also
node --no-compilation-cache --expose-gc node_modules/jest/bin/jest --logHeapUsage
but it's totally "killing" my machine.

Have actually no more idea and hope that angular will move jest esbuild support soon out of experimental so that it bet better integation into nx.

@ricardomiguelmoura
Copy link

ricardomiguelmoura commented Jan 16, 2024

@BernhardBehrendt but your problem is memory or cpu ? Because @vespertilian solution could ensure me that in regards to memory after some time i see that memory between tests isnt increasing so much as without the flags mentioned. It seems that it does garbage collection test after test ... it really improves in regards to memory while tests are running .. In my case helped me a lot

@BernhardBehrendt
Copy link

@ricardomiguelmoura it's still memory in my case. CPU is fine
Bildschirmfoto 2024-01-17 um 12 22 36

@jpv-os
Copy link

jpv-os commented May 14, 2024

I am experiencing this problem as well in my nx workspace with a mix of Angular and NestJS apps/libraries. During development it is hardly noticed that the tests leak memory. I am not using the most recent versions of any package used, but I have read multiple times that this issue prevailed through multiple versions of Angular and nx. My investigation is described in the following notes:

The CI pipeline executes all tests in the workspace by running nx run-many --all --target=test. Eventually, the runners were hitting their memory limits and I began investigating their memory usage.

First step is to demonstrate the growing memory usage. We can execute jest directly, make it execute tests one at a time and log the heap usage:

./node_modules/.bin/jest --runInBand --logHeapUsage

For apps with many tests, the memory usage hits the limits of the pipeline runners but not my local machine, so i can verify for sure that the memory usage is way higher than it should be (several gigabytes at some point).

To narrow it down, I have an example app with just a couple of tests. It consistently shows increasing memory usage with some ups and downs of course.

 ./node_modules/.bin/jest apps/error-logging-app-frontend --runInBand --logHeapUsage 

 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/modules/error-logs/pages/error-log-details-page/error-log-details-page.component.spec.ts (381 MB heap size)
 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/modules/error-logs/pages/error-logs-page/error-logs-page.component.spec.ts (377 MB heap size)
 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/modules/user-management/pages/user-management-page/user-management-page.component.spec.ts (448 MB heap size)
 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/app.component.spec.ts (499 MB heap size)
 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/modules/login/pages/login-page/login-page.component.spec.ts (565 MB heap size)
 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/modules/user-management/dialogs/create-user-dialog/create-user-dialog.component.spec.ts (626 MB heap size)
 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/modules/e2e/e2e-hidden-page/e2e-hidden-page.component.spec.ts (468 MB heap size)

Test Suites: 7 passed, 7 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        4.502 s, estimated 5 s
Ran all test suites matching /apps\/error-logging-app-frontend/i.

I read about similar problems online and found a couple of approaches that I all tried, most importantly running the garbage collection manually with --expose-gc and adding afterAll(() => global.gc && global.gc()) to the global jest setup. These were good for confirming the issue, but did not help solving it, so I continued to narrow it down.

Next I ran the test command for a single test file:

./node_modules/.bin/jest apps/error-logging-app-frontend/src/app/app.component.spec.ts  --runInBand --logHeapUsage             

 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/app.component.spec.ts (357 MB heap size)
  AppComponent
    ✓ should create the app (78 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.012 s
Ran all test suites matching /apps\/error-logging-app-frontend\/src\/app\/app.component.spec.ts/i.

The test runs successfully. However, jest has the experimental feature --detectLeaks:

./node_modules/.bin/jest apps/error-logging-app-frontend/src/app/app.component.spec.ts  --runInBand --logHeapUsage  --detectLeaks

 FAIL   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/app.component.spec.ts
  ● Test suite failed to run

    EXPERIMENTAL FEATURE!
    Your test suite is leaking memory. Please ensure all references are cleaned.

    There is a number of things that can leak memory:
      - Async operations that have not finished (e.g. fs.readFile).
      - Timers not properly mocked (e.g. setInterval, setTimeout).
      - Keeping references to the global scope.

      at onResult (../../node_modules/@jest/core/build/TestScheduler.js:150:18)
      at ../../node_modules/@jest/core/build/TestScheduler.js:254:19
      at ../../node_modules/emittery/index.js:363:13
          at Array.map (<anonymous>)
      at Emittery.emit (../../node_modules/emittery/index.js:361:23)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.193 s
Ran all test suites matching /apps\/error-logging-app-frontend\/src\/app\/app.component.spec.ts/i.

This is an experimental feature of course, but at this point it is all I have available. For the next step, we will look at the test and try to locate the problem by eliminating code:

describe('AppComponent', () => {
  beforeEach(async () => {
    // await TestBed.configureTestingModule({
    //   imports: [RouterTestingModule],
    //   declarations: [AppComponent],
    // }).compileComponents();
  });

  it('should create the app', () => {
    // const fixture = TestBed.createComponent(AppComponent);
    // const app = fixture.componentInstance;
    // expect(app).toBeTruthy();
    expect(true).toBeTruthy();
  });
});

All Angular-related code is removed and the memory issue still exists. Next, I removed the jest-preset-angular import from the test-setup.ts file in the project directory:

// import 'jest-preset-angular/setup-jest';

Now the test succeeds without memory errors:

./node_modules/.bin/jest apps/error-logging-app-frontend/src/app/app.component.spec.ts  --runInBand --logHeapUsage  --detectLeaks

 PASS   error-logging-app-frontend  apps/error-logging-app-frontend/src/app/app.component.spec.ts (291 MB heap size)
  AppComponent
    ✓ should create the app (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.408 s
Ran all test suites matching /apps\/error-logging-app-frontend\/src\/app\/app.component.spec.ts/i.

My result so far is: it seems that jest-preset-angular inside my quite standard jest project causes the memory leaks.

For more information, I also tried debugging the memory usage of node directly with node --inspect-brk --expose-gc ./node_modules/.bin/jest /apps/error-logging-app-frontend/ --runInBand --logHeapUsage and using the Chrome debugger tools. It revealed large memory allocations for angular source files, compiled code and so on. I was looking for patterns of repeated allocations of the same data, but in the tens of thousands of allocations to scroll through, I haven't found anything revealing yet.

I will gladly go into more detail on each of my steps, any help to reduce the memory usage is greatly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants