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

Coverage providers are prone to out-of-memory errors #4476

Closed
6 tasks done
AriPerkkio opened this issue Nov 11, 2023 · 1 comment · Fixed by #4603
Closed
6 tasks done

Coverage providers are prone to out-of-memory errors #4476

AriPerkkio opened this issue Nov 11, 2023 · 1 comment · Fixed by #4603
Assignees
Labels
feat: coverage Issues and PRs related to the coverage feature

Comments

@AriPerkkio
Copy link
Member

Describe the bug

Vitest runner sends coverage reports to main thread which stores them in memory during test run:

/**
* Coverage objects collected from workers.
* Some istanbul utilizers write these into file system instead of storing in memory.
* If storing in memory causes issues, we can simply write these into fs in `onAfterSuiteRun`
* and read them back when merging coverage objects in `onAfterAllFilesRun`.
*/
coverages = new Map<ProjectName, CoverageByTransformMode>()

coverages = new Map<ProjectName, CoverageByTransformMode>()

This is fine for most projects but larger projects can run into Node's out-of-memory issues. This is common problem for coverage tools. As commented in the Istanbul's provider.ts, this was only a matter of time before Vitest started to run into these. Similar reports form others tools:

Instead of storing reports in memory, we should write them on file system. This should be done in main thread here without blocking the runner.

onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta) {
if (transformMode !== 'web' && transformMode !== 'ssr')
throw new Error(`Invalid transform mode: ${transformMode}`)
let entry = this.coverages.get(projectName || DEFAULT_PROJECT)
if (!entry) {
entry = { web: [], ssr: [] }
this.coverages.set(projectName || DEFAULT_PROJECT, entry)
}
entry[transformMode].push(coverage as CoverageMapData)
}

Example below demonstrates the idea. Note that this is just a simplified example. Generating reports is much more complicated:

import * as fs from "node:fs/promises";

export class IstanbulCoverageProvider {
  pendingPromises = []

  onAfterSuiteRun({ coverage, transformMode, projectName }) {
    // Write reports to file system. Do not block the runner by `await`'in this call.
    // Instead store the promise for later awaiting
    const promise = fs.writeFile('some-filename.json', JSON.stringify({ coverage, transformMode, projectName }))

    this.pendingPromises.push(promise)
  }

  async reportCoverage() {
    // All tests have run, time to generate report
    await Promise.all(this.pendingPromises);

    const coverageMap = { ... };

    // Read results from file system one by one to avoid causing OOMs
    for (const filename of fs.readdir(...)) {
      const coverage = JSON.stringify(fs.readFileSync(filename));
      coverageMap.merge(coverage);
    }
  }
}

This should be possible to implement without causing breaking changes. Only the coverage providers need changes.

Reproduction

https://github.com/vitest-tests/coverage-large

Increase these to trigger the error: https://github.com/vitest-tests/coverage-large/blob/4bb1e194fa51c0ae03b9a7c76952a59b43540e49/generate-files.mjs#L4-L6

System Info

Vitest 0.34.x and anything below 1.0.0-beta.4

Used Package Manager

pnpm

Validations

@AriPerkkio AriPerkkio added bug feat: coverage Issues and PRs related to the coverage feature labels Nov 11, 2023
@AriPerkkio AriPerkkio self-assigned this Nov 19, 2023
AriPerkkio added a commit to AriPerkkio/vitest that referenced this issue Nov 19, 2023
@AriPerkkio
Copy link
Member Author

Also need to check if vitenode.transformRequest is caching uncovered files and if there's a way to disable it:

const transformResult = await this.ctx.vitenode.transformRequest(filename)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feat: coverage Issues and PRs related to the coverage feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant