vitest icon indicating copy to clipboard operation
vitest copied to clipboard

Duplicate directory names break module mocking

Open ArloL opened this issue 3 years ago • 8 comments

Describe the bug

When mocking a module where the source path contains a module called app and the source is located inside a directory called /app then the mocking stops working.

Consider a project like this:

./app.js <-- module to be mocked
./app.test.js <-- mocks the module

When checked out to /home/app (or /different) the mocking works and the tests pass. When checked out to /app (or /a or /ap) the tests do not pass because the mocking does not work.

Reproduction

https://github.com/ArloL/vitest-mocking-reproduction

System Info

System:
    OS: macOS 12.5.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 129.59 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.8.0 - /opt/homebrew/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 8.18.0 - /opt/homebrew/bin/npm
  Browsers:
    Firefox: 104.0.1
    Safari: 15.6.1
  npmPackages:
    vitest: ^0.22.1 => 0.22.1

Used Package Manager

yarn

Validations

ArloL avatar Sep 04 '22 13:09 ArloL

I think this is duplicate of https://github.com/vitest-dev/vitest/issues/1196

sheremet-va avatar Sep 04 '22 14:09 sheremet-va

Thanks for the pointer. The issues do seem related. Apparently I did not search thoroughly enough - sorry for that.

When I update the vitest version in https://github.com/prowe/vitest-mock-bug everything works as expected - so I guess #1196 can be closed.

Nonetheless that sample helped me to further reduce my sample.

I tested additional working directory name combinations:

  • a ❌
  • aa ✅
  • ab ✅
  • ap ❌
  • apa ✅
  • apb ✅
  • app ❌
  • b ✅
  • c ✅
  • d ✅
  • e ✅
  • f ✅
  • g ✅
  • h ✅
  • work ✅
  • foo ✅
  • bar ✅

ArloL avatar Sep 04 '22 14:09 ArloL

So, the problem is here:

https://github.com/vitest-dev/vitest/blob/aa7eb7d14bf5a5df43e67c2078f035da454f7e31/packages/vitest/src/runtime/mocker.ts#L145

It blindly replaces path, so you get:

- /app/app.js
+ /app.js

- /app.js
+ .js

sheremet-va avatar Sep 04 '22 15:09 sheremet-va

Thank you so much for taking the time to look into this and releasing a fix this fast.

I can confirm that my current reproduction state is fixed with 0.23.1.

Sadly when I move ./app.js and ./app.test.js into a directory called ./app/ the tests still fail. I have updated the repository accordingly.

ArloL avatar Sep 05 '22 09:09 ArloL

I did some more testing. The mocking breaks when the filesystem location and the source code structure match e.g. like this /theRoot/aSubDir/theRoot/aSubDir/moreDirs/app.js. I updated my repository to make it a bit easier to understand.

I also tried building vitest inside a docker container to test my assumption but I couldn't get pnpm run build to work.

Dockerfile:

FROM docker.io/library/node:18@sha256:8a45c95c328809e7e10e8c9ed5bf8374620d62e52de1df7ef8e71a9596ec8676
WORKDIR /test
RUN npm --global install pnpm
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY . ./
RUN pnpm run build
RUN pnpm run test:run
WORKDIR ./test/vite-config
RUN pnpm run test

.dockerignore:

**/node_modules
**/dist

I am no pnpm expert - so I might just be using it wrong.

Part of the error message:

…
#11 2.663 packages/vite-node build: src/server.ts(4,25): error TS2307: Cannot find module 'debug' or its corresponding type declarations.
#11 2.664 packages/vite-node build: [!] (plugin dts) Error: Failed to compile. Check the logs above.
#11 2.664 packages/vite-node build: src/server.ts
#11 2.670 packages/vite-node build: Error: Failed to compile. Check the logs above.
#11 2.671 packages/vite-node build:     at error (/test/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:198:30)
#11 2.671 packages/vite-node build:     at throwPluginError (/test/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:21919:12)
#11 2.671 packages/vite-node build:     at Object.error (/test/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:22641:20)
#11 2.671 packages/vite-node build:     at Object.error (/test/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:22096:42)
#11 2.674 packages/vite-node build:     at Object.transform (/test/node_modules/.pnpm/[email protected]_ihkqvyh37tzxtjmovjyy2yrv7m/node_modules/rollup-plugin-dts/dist/rollup-plugin-dts.cjs:1618:26)
#11 2.674 packages/vite-node build:     at /test/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:22848:40
#11 2.675 packages/ws-client build: Failed
#11 2.676 undefined
#11 2.676 /test/packages/ws-client:
#11 2.676  ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @vitest/[email protected] build: `rimraf dist && rollup -c`
#11 2.676 Exit status 1
#11 2.676  WARN   Local package.json exists, but node_modules missing, did you mean to install?
#11 2.684  ELIFECYCLE  Command failed with exit code 1.

ArloL avatar Sep 14 '22 19:09 ArloL

I updated my repository to make it a bit easier to understand.

This is the function that determines mock path:

https://github.com/vitest-dev/vitest/blob/95da4d66933db46da368d5d66f422afd26fba4c1/packages/vite-node/src/utils.ts#L49

When root is /home/path, It can receive paths:

  • /app.js
  • /home/path/app.js
  • /@fs/home/path/app.js

In all cases it should return /app.js. Try debugging it there.

sheremet-va avatar Sep 15 '22 06:09 sheremet-va

Thanks for the pointer 👍

I'll write down my current state of investigations:

pathFromRoot is working as expected - but it is being called twice.

With root=/foo and path=/foo/foo/app.js this is what happens:

  • mocker.requestWithMock
    • mocker#L337: mocker.resolveMocks
      • mocker.mockPath
        • normalizePath with /foo/foo/app.js and returns /foo/app.js
    • mocker#L340: normalizePath with /foo/app.js and returns /app.js

At least as far as I can tell.

ArloL avatar Sep 15 '22 07:09 ArloL

  • mocker#L340: normalizePath with /foo/app.js and returns /app.js

dep here can be /foo/app.js and /foo/foo/app.js, so maybe we need to path.resolve(root, dep) before passing to normalizePath? The only problem is that it can also be /@fs/foo/foo/app.js, so we need to strip it down.

sheremet-va avatar Sep 15 '22 08:09 sheremet-va

This issue is no longer reproducible with 0.26.0

Thanks!

ArloL avatar Dec 20 '22 16:12 ArloL