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

error when running a global install #833

Merged
merged 1 commit into from
May 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions features/global_install.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Feature: Global Installs

@global-install
Scenario: executing cucumber from a global install error
Given a file named "features/a.feature" with:
"""
Feature: some feature
Scenario:
When a step is passing
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
import {defineSupportCode} from 'cucumber'

defineSupportCode(({When}) => {
When(/^a step is passing$/, function() {})
})
"""
When I run cucumber.js (installed globally)
Then it fails
And the output contains the text:
"""
You appear to be executing an install of cucumber (most likely a global install)
that is different from your local install (the one required in your support files).
For cucumber to work, you need to execute the same install that is required in your support files.
Please execute the locally installed version to run your tests.
"""
When I run cucumber.js (installed locally)
Then it passes
41 changes: 10 additions & 31 deletions features/step_definitions/cli_steps.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,22 @@
/* eslint-disable babel/new-cap */

import {defineSupportCode} from '../../'
import {execFile} from 'child_process'
import {expect} from 'chai'
import {normalizeText} from '../support/helpers'
import colors from 'colors/safe'
import fs from 'fs'
import path from 'path'
import stringArgv from 'string-argv'

const executablePath = path.join(__dirname, '..', '..', 'bin', 'cucumber.js')

defineSupportCode(function({When, Then}) {
When(/^I run cucumber.js(?: with `(|.+)`)?$/, {timeout: 10000}, function(args, callback) {
When(/^I run cucumber.js(?: with `(|.+)`)?$/, {timeout: 10000}, function(args) {
args = stringArgv(args || '')
args.unshift(executablePath)
args.push('--backtrace', '--format', 'json:out.json')
const cwd = this.tmpDir
execFile('node', args, {cwd}, (error, stdout, stderr) => {
let jsonOutput = []
const jsonOutputPath = path.join(cwd, 'out.json')
if (fs.existsSync(jsonOutputPath)) {
const fileContent = fs.readFileSync(jsonOutputPath, 'utf8')
if (fileContent) {
jsonOutput = JSON.parse(fileContent)
}
}
if (this.debug) {
console.log(stdout + stderr) // eslint-disable-line no-console
}
this.lastRun = {
error,
jsonOutput,
output: colors.strip(stdout) + stderr
}
this.verifiedLastRunError = false
expect(this.lastRun.output).to.not.include('Unhandled rejection')
callback()
})
return this.run(this.localExecutablePath, args)
})

When(/^I run cucumber.js \(installed (locally|globally)\)$/, {timeout: 10000}, function(location) {
if (location === 'locally') {
return this.run(this.localExecutablePath, [])
} else {
return this.run(this.globalExecutablePath, [])
}
})

Then(/^it passes$/, function() {})
Expand Down
27 changes: 23 additions & 4 deletions features/support/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import path from 'path'
import tmp from 'tmp'

const projectPath = path.join(__dirname, '..', '..')
const projectNodeModulesPath = path.join(projectPath, 'node_modules')
const moduleNames = fs.readdirSync(projectNodeModulesPath)

defineSupportCode(function({After, Before}) {
Before('@debug', function () {
Expand All @@ -26,17 +28,34 @@ defineSupportCode(function({After, Before}) {
fsExtra.createSymlinkSync(profileBabelRcPath, tmpDirBabelRcPath)

const tmpDirNodeModulesPath = path.join(this.tmpDir, 'node_modules')
const projectNodeModulesPath = path.join(projectPath, 'node_modules')

const moduleNames = fs.readdirSync(projectNodeModulesPath)
moduleNames.forEach((moduleName) => {
const tmpDirNodeModulePath = path.join(tmpDirNodeModulesPath, moduleName)
const projectNodeModulePath = path.join(projectPath, 'node_modules', moduleName)
fsExtra.createSymlinkSync(projectNodeModulePath, tmpDirNodeModulePath)
})

const tmpDirCucumberPath = path.join(this.tmpDir, 'node_modules', 'cucumber')
const tmpDirCucumberPath = path.join(tmpDirNodeModulesPath, 'cucumber')
fsExtra.createSymlinkSync(projectPath, tmpDirCucumberPath)
this.localExecutablePath = path.join(tmpDirCucumberPath, 'bin', 'cucumber.js')
})

Before('@global-install', function () {
const tmpObject = tmp.dirSync({unsafeCleanup: true})

const globalInstallNodeModulesPath = path.join(tmpObject.name, 'node_modules')
moduleNames.forEach((moduleName) => {
const globalInstallNodeModulePath = path.join(globalInstallNodeModulesPath, moduleName)
const projectNodeModulePath = path.join(projectPath, 'node_modules', moduleName)
fsExtra.createSymlinkSync(projectNodeModulePath, globalInstallNodeModulePath)
})

const globalInstallCucumberPath = path.join(globalInstallNodeModulesPath, 'cucumber')
const itemsToCopy = ['bin', 'lib', 'package.json']
itemsToCopy.forEach((item) => {
fsExtra.copySync(path.join(projectPath, item), path.join(globalInstallCucumberPath, item))
})

this.globalExecutablePath = path.join(globalInstallCucumberPath, 'bin', 'cucumber.js')
})

After(function() {
Expand Down
40 changes: 40 additions & 0 deletions features/support/world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {defineSupportCode} from '../../'
import {execFile} from 'child_process'
import {expect} from 'chai'
import colors from 'colors/safe'
import fs from 'fs'
import path from 'path'

class World {
async run(executablePath, inputArgs) {
const args = [executablePath].concat(inputArgs, ['--backtrace', '--format', 'json:out.json'])
const cwd = this.tmpDir
const result = await new Promise((resolve) => {
execFile('node', args, {cwd}, (error, stdout, stderr) => {
resolve({error, stdout, stderr})
})
})
let jsonOutput = []
const jsonOutputPath = path.join(cwd, 'out.json')
if (fs.existsSync(jsonOutputPath)) {
const fileContent = fs.readFileSync(jsonOutputPath, 'utf8')
if (fileContent) {
jsonOutput = JSON.parse(fileContent)
}
}
if (this.debug) {
console.log(result.stdout + result.stderr) // eslint-disable-line no-console
}
this.lastRun = {
error: result.error,
jsonOutput,
output: colors.strip(result.stdout) + result.stderr
}
this.verifiedLastRunError = false
expect(this.lastRun.output).to.not.include('Unhandled rejection')
}
}

defineSupportCode(({setWorldConstructor}) => {
setWorldConstructor(World)
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"is-stream": "^1.1.0",
"lodash": "^4.0.0",
"mz": "^2.4.0",
"resolve": "^1.3.3",
"stack-chain": "^1.3.5",
"stacktrace-js": "^1.3.0",
"string-argv": "0.0.2",
Expand Down
27 changes: 27 additions & 0 deletions src/cli/install_validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {promisify} from 'bluebird'
import fs from 'mz/fs'
import path from 'path'
import resolve from 'resolve'

export async function validateInstall(cwd) {
const projectPath = path.join(__dirname, '..', '..')
if (projectPath === cwd) {
return // cucumber testing itself
}
const currentCucumberPath = require.resolve(projectPath)
let localCucumberPath = await promisify(resolve)('cucumber', {basedir: cwd})
localCucumberPath = await fs.realpath(localCucumberPath)
if (localCucumberPath !== currentCucumberPath) {
throw new Error(
`
You appear to be executing an install of cucumber (most likely a global install)
that is different from your local install (the one required in your support files).
For cucumber to work, you need to execute the same install that is required in your support files.
Please execute the locally installed version to run your tests.

Executed Path: ${currentCucumberPath}
Local Path: ${localCucumberPath}
`
)
}
}
19 changes: 16 additions & 3 deletions src/cli/run.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import Cli from './'
import VError from 'verror'
import {validateInstall} from './install_validator'

function exitWithError(error) {
console.error(VError.fullStack(error)) // eslint-disable-line no-console
process.exit(1)
}

export default async function run() {
const cwd = process.cwd()

try {
await validateInstall(cwd)
} catch (error) {
exitWithError(error)
}

const cli = new Cli({
argv: process.argv,
cwd: process.cwd(),
cwd,
stdout: process.stdout
})

let success
try {
success = await cli.run()
} catch (error) {
console.error(VError.fullStack(error)) // eslint-disable-line no-console
process.exit(1)
exitWithError(error)
}

const exitCode = success ? 0 : 1
Expand Down
6 changes: 6 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3156,6 +3156,12 @@ resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6:
version "1.2.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c"

resolve@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
dependencies:
path-parse "^1.0.5"

restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
Expand Down