Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Aug 11, 2022. It is now read-only.

Conversation

exogen
Copy link
Contributor

@exogen exogen commented May 16, 2017

This adds two test cases to demonstrate how npm 4.1.0+ (including the latest version) fails to prune in certain cases (likely introduced in #15090). A fix is included, but will need to be reviewed carefully (I'm not sure whether cycle detection or checking userRequired is necessary, or if that's already taken care of by isExtraneous).

prune misbehaving is likely the cause of some of the funny business on display in issues #15727, #15669, #15646.

My interest in this stems from the fact that broken pruning causes postinstall-build to break on the latest versions of npm ever since 4.1.0.

The Tests

  • Call npm prune --production on a module with ONLY these dependencies:

    {
      "devDependencies": {
        "test-package-with-one-dep": "0.0.0",
        "test-package": "0.0.0"
      }
    }

    ...which looks like this before pruning:

    ├── [email protected]
    └─┬ [email protected]
      └── [email protected]  deduped

    npm prune --production SHOULD remove both packages, resulting in an empty node_modules tree, but it fails to remove test-package (because it's a prod dep of test-package-with-one-dep, but that shouldn't matter because test-package-with-one-dep itself is a dev dep).

  • Call prune --production with the same package in both dependencies and devDependencies. The package SHOULD NOT be removed.

The Fix

In --production mode, packages are now pruned if they are only reachable from dev dependencies (the actual logic is: NOT reachable from prod or opt dependencies).

/cc @iarna

@isaacs isaacs added the review label May 16, 2017
@exogen exogen changed the title test: Add prune --production test with only devDependencies Add prune test to demonstrate brokenness in npm 4.1.0+ May 16, 2017
@exogen
Copy link
Contributor Author

exogen commented May 16, 2017

    var isChildDev = function (parent) { return isDev(parent, childName) }
    return child.requiredBy.every(isChildDev)

This appears to be the logic at fault. If I understand correctly, it only prunes if child is a dev dependency in every package it's required by, but that's not right: it should prune if it's a dev dependency of the top-level package and/or only required by other dev dependencies. It's perfectly fine to prune if it's a prod dependency of a dev dependency.

@exogen exogen changed the title Add prune test to demonstrate brokenness in npm 4.1.0+ Fix prune, add tests to demonstrate brokenness in npm 4.1.0+ May 16, 2017
@exogen
Copy link
Contributor Author

exogen commented May 16, 2017

I came up with a fix and updated the PR description. There are now two test cases; CI shows that they both demonstrate the failure until this fix is applied. 😄

@exogen
Copy link
Contributor Author

exogen commented May 17, 2017

Some testing shows that this does indeed need to take cycles into account.

IMO it would be nice if this behavior was baked into isExtraneous in the first place (by adding a production flag). In production mode, devDeps should be considered extraneous. Then prune's job is to simply remove anything marked extraneous...no additional logic.

Copy link
Contributor

@iarna iarna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With and without your changes I'm seeing this test failure:

EDIT: never mind this, I misread my own edited output =D

t.is(code, 0, 'prune finished successfully')
t.equal(stderr,
'- [email protected] node_modules/a\n' +
'- [email protected] node_modules/b\n')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't expect this to ever pass because that output is printed to stdout not stderr.

'--loglevel', 'silent',
'--production', 'false'
], EXEC_OPTS, function (err, code, stdout, stderr) {
t.ifErr(err, 'install finished successfully')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you likely just copied this from another test, but it was wrong there too. =D

The test label says 'install finished successfully' but that's not what not getting an err value means. err will ONLY be set if something catastrophic happened like we couldn't open a new process. This would be better written as:

if (err) throw err

(The story of how any tests started doing the ifError thing is an interesting bit of code archaeology, but the long and short of it is that this is never an appropriate check in this context. It was introduced due to either errors when converting between two types of test, or by someone who cargo culted it from another similar but quite different context.)

], EXEC_OPTS, function (err, code, stderr) {
t.ifErr(err, 'prune finished successfully')
t.notOk(code, 'exit ok')
t.notOk(stderr, 'Should not get data on stderr: ' + stderr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not stderr that is being checked here, this is stdout.

I'm honestly not sure what the expected behavior for this one is? In my testing with the bleeding edge, it removes this:

    #     {
    #       "action": "remove",
    #       "name": "test-package",
    #       "version": "0.0.0",
    #       "path": "node_modules/test-package"
    #     }

Is that correct, or wrong?

Copy link
Contributor Author

@exogen exogen Jun 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, that is wrong: test-package should remain, because the result of npm prune --production should be "anything NOT required by production dependencies is removed", but test-package is in dependencies, so removing it would break the install.

], EXEC_OPTS, function (err, code, stderr) {
t.ifErr(err, 'prune finished successfully')
t.notOk(code, 'exit ok')
t.equal(stderr,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable holds STDOUT, not STDERR.

'--loglevel', 'silent',
'--production', 'false'
], EXEC_OPTS, function (err, code, stdout, stderr) {
t.ifErr(err, 'prune finished successfully')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments about ifErr apply

@iarna
Copy link
Contributor

iarna commented Jun 30, 2017

Ok, so don't worry about making any changes to this, I wanted to give you the feedback, but I've gone and made them myself when I was making this merge clean w/ the branch that will be release-next tomorrow. (or released tomorrow, not sure =D)

This is what'll be merged: lock-missing-dep-fixes...iarna/pr/16637

@iarna iarna self-assigned this Jun 30, 2017
@exogen
Copy link
Contributor Author

exogen commented Jun 30, 2017

@iarna Thanks for the feedback! I was indeed confused about the stderr/stdout discrepancies myself when making these changes – the code is copied from existing tests that had the same mistake. And yes, I left this with new test cases that were failing, never got around to solving it completely.

@iarna
Copy link
Contributor

iarna commented Jul 1, 2017

This was merged into release-next as 8a2d739 and 0712ac6.

@iarna iarna closed this Jul 1, 2017
zkat pushed a commit that referenced this pull request Jul 5, 2017
zkat pushed a commit that referenced this pull request Jul 5, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants