Remove fs sync 373 (#376)

* remove fs.existsSync in unit test, close #373

* updating an integration test

* remove unlinkSync from an integration spec

* remove only

* make saved_stage async

* switched some tests to async state

* server: fix more state tests

* working on config to async

* change config mergeDefaults to async

* more .mergeDefaults tests updated

* fix config unit tests

* remove only in config unit tests

* fix server unit test

* fix two more tests

* server: maybe all unit tests fixed

* server: handle NPM 3 vs 4 in exit codes

* fix server start

* fix another server startup in test

* server: messaging and promise.try
This commit is contained in:
Gleb Bahmutov
2017-08-25 11:31:36 -04:00
committed by GitHub
parent a98c282d37
commit 4409698c7f
25 changed files with 592 additions and 411 deletions
+3 -1
View File
@@ -85,7 +85,9 @@ module.exports = {
}, resolve)
open: (browserName, url, options = {}, automation) ->
savedState(options.projectPath).get()
savedState(options.projectPath)
.then (state) ->
state.get()
.then (state) =>
@_render(url, state, options)
.then (win) =>
+34 -31
View File
@@ -1,7 +1,7 @@
_ = require("lodash")
path = require("path")
Promise = require("bluebird")
fs = require("fs")
fs = require("fs-extra")
errors = require("./errors")
scaffold = require("./scaffold")
errors = require("./errors")
@@ -187,11 +187,8 @@ module.exports = {
config = @setParentTestsPaths(config)
config = @setSupportFileAndFolder(config)
config = @setScaffoldPaths(config)
return config
@setSupportFileAndFolder(config)
.then @setScaffoldPaths
setResolvedConfigValues: (config, defaults, resolved) ->
obj = _.clone(config)
@@ -235,18 +232,22 @@ module.exports = {
return obj
# async function
setSupportFileAndFolder: (obj) ->
return obj if not obj.supportFile
return Promise.resolve(obj) if not obj.supportFile
obj = _.clone(obj)
## TODO move this logic to find support file into util/path_helpers
sf = obj.supportFile
log "setting support file #{sf}"
log "for project root #{obj.projectRoot}"
try
Promise
.try ->
## resolve full path with extension
obj.supportFile = require.resolve(sf)
.then () ->
if pathHelpers.checkIfResolveChangedRootFolder(obj.supportFile, sf)
log("require.resolve switched support folder from %s to %s",
sf, obj.supportFile)
@@ -255,36 +256,38 @@ module.exports = {
# which can confuse the rest of the code
# switch it back to "normal" file
obj.supportFile = path.join(sf, path.basename(obj.supportFile))
if not fs.existsSync(obj.supportFile)
errors.throw("SUPPORT_FILE_NOT_FOUND", obj.supportFile)
log("switching to found file %s", obj.supportFile)
catch err
if err.code isnt "MODULE_NOT_FOUND"
throw err
log("support file does not exist")
return fs.pathExists(obj.supportFile)
.then (found) ->
if not found
errors.throw("SUPPORT_FILE_NOT_FOUND", obj.supportFile)
log("switching to found file %s", obj.supportFile)
.catch({code: "MODULE_NOT_FOUND"}, ->
log("support file %s does not exist", sf)
## supportFile doesn't exist on disk
if sf is path.resolve(obj.projectRoot, defaults.supportFile)
log("support file is default, check if #{path.dirname(sf)} exists")
if fs.existsSync(sf)
log("support folder exists, set supportFile to false")
## if the directory exists, set it to false so it's ignored
obj.supportFile = false
return fs.pathExists(sf)
.then (found) ->
if (found)
log("support folder exists, set supportFile to false")
## if the directory exists, set it to false so it's ignored
obj.supportFile = false
else
log("support folder does not exist, set to default index.js")
## otherwise, set it up to be scaffolded later
obj.supportFile = path.join(sf, "index.js")
return obj
else
log("support folder does not exist, set to default index.js")
## otherwise, set it up to be scaffolded later
obj.supportFile = path.join(sf, "index.js")
else
log("support file is not default")
## they have it explicitly set, so it should be there
errors.throw("SUPPORT_FILE_NOT_FOUND", path.resolve(obj.projectRoot, sf))
## set config.supportFolder to its directory
obj.supportFolder = path.dirname(obj.supportFile)
log "set support folder #{obj.supportFolder}"
return obj
)
.then () ->
if obj.supportFile
## set config.supportFolder to its directory
obj.supportFolder = path.dirname(obj.supportFile)
log "set support folder #{obj.supportFolder}"
obj
setParentTestsPaths: (obj) ->
## projectRoot: "/path/to/project"
+1 -1
View File
@@ -1,7 +1,7 @@
require("./util/http_overrides")
require("./fs_warn")(require("fs-extra"))
os = require("os")
fs = require("fs-extra")
cwd = require("./cwd")
Promise = require("bluebird")
+35
View File
@@ -0,0 +1,35 @@
{T, F} = require("ramda")
## warn users if somehow synchronous file methods are invoked
## these methods due to "too many files" errors are a huge pain
warnOnSyncFileSystem = ->
console.error "WARNING: fs sync methods can fail due to EMFILE errors"
console.error "Cypress only works reliably when ALL fs calls are async"
console.error "You should modify these sync calls to be async"
topLines = (from, n) -> (text) ->
text.split("\n").slice(from, n).join("\n")
# just hide this function itself
# stripping top few lines of the stack
getStack = () ->
err = new Error()
topLines(3, 10)(err.stack)
addSyncFileSystemWarnings = (fs) ->
oldExistsSync = fs.existsSync
fs.existsSync = (filename) ->
warnOnSyncFileSystem()
console.error(getStack())
oldExistsSync(filename)
if not fs.pathExists
# pathExists was introduced to fs-extra@3.0.0
# if it does not exist mimic it using async methods
# and convert result into boolean
fs.pathExists = (filename) ->
fs.statAsync(filename)
.then(T)
.catch({code: "ENOENT"}, F)
module.exports = addSyncFileSystemWarnings
+12 -4
View File
@@ -295,7 +295,9 @@ module.exports = {
newState[keys.height] = height
newState[keys.x] = x
newState[keys.y] = y
savedState(projectPath).set(newState)
savedState(projectPath)
.then (state) ->
state.set(newState)
, 500
win.on "moved", _.debounce ->
@@ -305,17 +307,23 @@ module.exports = {
newState = {}
newState[keys.x] = x
newState[keys.y] = y
savedState(projectPath).set(newState)
savedState(projectPath)
.then (state) ->
state.set(newState)
, 500
win.webContents.on "devtools-opened", ->
newState = {}
newState[keys.devTools] = true
savedState(projectPath).set(newState)
savedState(projectPath)
.then (state) ->
state.set(newState)
win.webContents.on "devtools-closed", ->
newState = {}
newState[keys.devTools] = false
savedState(projectPath).set(newState)
savedState(projectPath)
.then (state) ->
state.set(newState)
}
+2 -1
View File
@@ -82,7 +82,8 @@ module.exports = {
bus.emit("menu:item:clicked", "log:out")
})
savedState(options.projectPath).get()
savedState(options.projectPath)
.then (state) -> state.get()
.then (state) =>
Windows.open(@getWindowArgs(state, options))
.then (win) =>
+20 -13
View File
@@ -121,18 +121,22 @@ class Project extends EE
watchSupportFile: (config) ->
if supportFile = config.supportFile
if not fs.existsSync(supportFile)
errors.throw("SUPPORT_FILE_NOT_FOUND", supportFile)
fs.pathExists(supportFile)
.then (found) =>
if not found
errors.throw("SUPPORT_FILE_NOT_FOUND", supportFile)
relativePath = path.relative(config.projectRoot, config.supportFile)
if config.watchForFileChanges isnt false
options = {
onChange: _.bind(@server.onTestFileChange, @server, relativePath)
}
@watchers.watchBundle(relativePath, config, options)
## ignore errors b/c we're just setting up the watching. errors
## are handled by the spec controller
.catch ->
relativePath = path.relative(config.projectRoot, config.supportFile)
if config.watchForFileChanges isnt false
options = {
onChange: _.bind(@server.onTestFileChange, @server, relativePath)
}
@watchers.watchBundle(relativePath, config, options)
## ignore errors b/c we're just setting up the watching. errors
## are handled by the spec controller
.catch ->
else
Promise.resolve()
watchSettings: (onSettingsChanged) ->
## bail if we havent been told to
@@ -244,13 +248,16 @@ class Project extends EE
throw new Error("Missing project config") if not @cfg
throw new Error("Missing project root") if not @projectRoot
newState = _.merge({}, @cfg.state, stateChanges)
savedState(@projectRoot).set(newState)
savedState(@projectRoot)
.then (state) ->
state.set(newState)
.then =>
@cfg.state = newState
newState
_setSavedState: (cfg) ->
savedState(@projectRoot).get()
savedState(@projectRoot)
.then (state) -> state.get()
.then (state) ->
cfg.state = state
cfg
+3
View File
@@ -12,6 +12,8 @@ files = require("./controllers/files")
proxy = require("./controllers/proxy")
driver = require("./controllers/driver")
staticCtrl = require("./controllers/static")
la = require("lazy-ass")
check = require("check-more-types")
module.exports = (app, config, request, getRemoteState, watchers, project) ->
## routing for the actual specs which are processed automatically
@@ -63,6 +65,7 @@ module.exports = (app, config, request, getRemoteState, watchers, project) ->
## TODO: we should additionally send config for the socket.io route, etc
## and any other __cypress namespaced files so that the runner does
## not have to be aware of anything
la(check.unemptyString(config.clientRoute), "missing client route in config", config)
app.get config.clientRoute, (req, res) ->
runner.serve(req, res, config, getRemoteState)
+12 -10
View File
@@ -20,17 +20,19 @@ stateFiles = {}
# project.open().then(project.state).then(state)
# state should have width = 200
# async promise-returning function
findSavedSate = (projectPath) ->
statePath = savedStateUtil.formStatePath(projectPath)
fullStatePath = appData.projectsPath(statePath)
log('full state path %s', fullStatePath)
return stateFiles[fullStatePath] if stateFiles[fullStatePath]
savedStateUtil.formStatePath(projectPath)
.then (statePath) ->
fullStatePath = appData.projectsPath(statePath)
log('full state path %s', fullStatePath)
return stateFiles[fullStatePath] if stateFiles[fullStatePath]
log('making new state file around %s', fullStatePath)
stateFile = new FileUtil({
path: fullStatePath
})
stateFiles[fullStatePath] = stateFile
stateFile
log('making new state file around %s', fullStatePath)
stateFile = new FileUtil({
path: fullStatePath
})
stateFiles[fullStatePath] = stateFile
stateFile
module.exports = findSavedSate
+3
View File
@@ -8,6 +8,8 @@ express = require("express")
Promise = require("bluebird")
evilDns = require("evil-dns")
httpProxy = require("http-proxy")
la = require("lazy-ass")
check = require("check-more-types")
httpsProxy = require("@packages/https-proxy")
log = require("debug")("cypress:server:server")
cors = require("./util/cors")
@@ -102,6 +104,7 @@ class Server
e
open: (config = {}, project) ->
la(_.isPlainObject(config), "expected plain config object", config)
Promise.try =>
## always reset any buffers
## TODO: change buffers to be an instance
+31 -22
View File
@@ -1,9 +1,10 @@
log = require('../log')
cwd = require('../cwd')
fs = require('fs')
log = require('../log')
cwd = require('../cwd')
fs = require('fs-extra')
md5 = require('md5')
sanitize = require("sanitize-filename")
Promise = require("bluebird")
{ basename, join, isAbsolute } = require('path')
md5 = require('md5')
sanitize = require("sanitize-filename")
toHashName = (projectPath) ->
throw new Error("Missing project path") unless projectPath
@@ -12,26 +13,34 @@ toHashName = (projectPath) ->
hash = md5(projectPath)
"#{name}-#{hash}"
# async promise-returning method
formStatePath = (projectPath) ->
log('making saved state from %s', cwd())
if projectPath
log('for project path %s', projectPath)
else
log('missing project path, looking for project here')
cypressJsonPath = cwd('cypress.json')
if fs.existsSync(cypressJsonPath)
log('found cypress file %s', cypressJsonPath)
projectPath = cwd()
Promise.resolve()
.then ->
log('making saved state from %s', cwd())
if projectPath
log('for project path %s', projectPath)
else
log('missing project path, looking for project here')
fileName = "state.json"
if projectPath
log("state path for project #{projectPath}")
statePath = join(toHashName(projectPath), fileName)
else
log("state path for global mode")
statePath = join("__global__", fileName)
cypressJsonPath = cwd('cypress.json')
fs.pathExists(cypressJsonPath)
.then (found) ->
if found
log('found cypress file %s', cypressJsonPath)
projectPath = cwd()
return projectPath
return statePath
.then (projectPath) ->
fileName = "state.json"
if projectPath
log("state path for project #{projectPath}")
statePath = join(toHashName(projectPath), fileName)
else
log("state path for global mode")
statePath = join("__global__", fileName)
return statePath
module.exports = {
toHashName: toHashName,
+2
View File
@@ -39,6 +39,7 @@
"cors": "^2.8.3",
"coveralls": "^2.11.8",
"electron-osx-sign": "^0.3.0",
"execa": "^0.8.0",
"express-session": "^1.14.1",
"express-useragent": "^1.0.4",
"https-proxy-agent": "^1.0.0",
@@ -88,6 +89,7 @@
"cookie-parser": "^1.3.3",
"data-uri-to-buffer": "0.0.4",
"debug": "^2.6.8",
"delay": "^2.0.0",
"electron-context-menu": "^0.8.0",
"electron-positioner": "3.0.0",
"errorhandler": "1.1.1",
+10
View File
@@ -207,3 +207,13 @@ socket.emit("remote:response", "123-a-guid-as-an-id", {a: "new response data obj
- `npm i`
- Open new tab: `npm run watch`
- `node_modules/.bin/nw .`
## Misc
**important** do not use sync file system methods to work with files. They can fail if
there are too many files (the `EMILE` exception). Asynchronous file system methods
all use [graceful-fs](https://github.com/isaacs/node-graceful-fs#readme) to retry and
get around this problem.
* there is `fs.pathExists(filename)` method that is returning a promise, use that
instead of `fs.exists` or `fs.existsSync`.
@@ -1,10 +1,13 @@
require("../spec_helper")
_ = require("lodash")
R = require("ramda")
cp = require("child_process")
pr = require("../support/helpers/process")
pkg = require("../../package.json")
root = require("@packages/root")
execa = require("execa")
semver = require("semver")
anyLineWithCaret = (str) ->
str[0] is ">"
@@ -52,31 +55,46 @@ describe "CLI Interface", ->
## caused false-positives in CI because tests were failing
## but the exit code was always zero
context "exit codes", ->
beforeEach ->
## run the start script directly
## instead of going through npm wrapper
@start = pkg.scripts.start
describe "from start script command", ->
beforeEach ->
## run the start script directly
## instead of going through npm wrapper
@start = pkg.scripts.start
it "exits with code 22", (done) ->
s = cp.exec("#{@start} --exit-with-code=22")
s.on "close", (code) ->
expect(code).to.eq(22)
done()
it "exits with code 22", (done) ->
s = cp.exec("#{@start} --exit-with-code=22")
s.on "close", (code) ->
expect(code).to.eq(22)
done()
it "exits with code 0", (done) ->
s = cp.exec("#{@start} --exit-with-code=0")
s.on "close", (code) ->
expect(code).to.eq(0)
done()
it "exits with code 0", (done) ->
s = cp.exec("#{@start} --exit-with-code=0")
s.on "close", (code) ->
expect(code).to.eq(0)
done()
it "npm slurps up exit value and exits with 1 on failure", (done) ->
s = cp.exec("npm start -- --exit-with-code=10")
s.on "close", (code) ->
expect(code).to.eq(1)
done()
describe "through NPM script", ->
npmVersion = null
it "npm passes on 0 exit code", (done) ->
s = cp.exec("npm start -- --exit-with-code=0")
s.on "close", (code) ->
expect(code).to.eq(0)
done()
isNpmSlurpingCode = () ->
semver.lt(npmVersion, '4.0.0')
beforeEach ->
execa("npm", ["-version"])
.then R.prop("stdout")
.then (version) ->
npmVersion = version
expect(npmVersion).to.be.a.string
it "npm slurps up or not exit value on failure", (done) ->
expectedCode = if isNpmSlurpingCode() then 1 else 10
s = cp.exec("npm start -- --exit-with-code=10")
s.on "close", (code) ->
expect(code).to.eq(expectedCode)
done()
it "npm passes on 0 exit code", (done) ->
s = cp.exec("npm start -- --exit-with-code=0")
s.on "close", (code) ->
expect(code).to.eq(0)
done()
@@ -5,7 +5,6 @@ os = require("os")
cp = require("child_process")
path = require("path")
{ EventEmitter } = require("events")
{ unlinkSync: rm, existsSync: exists } = require("fs")
http = require("http")
Promise = require("bluebird")
electron = require("electron")
@@ -210,12 +209,16 @@ describe "lib/cypress", ->
context "state", ->
statePath = null
beforeEach ->
# TODO switch to async file system calls
statePath = appData.projectsPath(formStatePath(@todosPath))
rm(statePath) if exists(statePath)
formStatePath(@todosPath)
.then (statePathStart) ->
statePath = appData.projectsPath(statePathStart)
fs.pathExists(statePath)
.then (found) ->
if found
fs.unlink(statePath)
afterEach ->
rm(statePath)
fs.unlink(statePath)
it "saves project state", ->
Project.add(@todosPath)
@@ -225,8 +228,10 @@ describe "lib/cypress", ->
@expectExitWith(0)
.then ->
openProject.getProject().saveState()
.then (state) ->
expect(exists(statePath), "Finds saved stage file #{statePath}").to.be.true
.then () ->
fs.pathExists(statePath)
.then (found) ->
expect(found, "Finds saved stage file #{statePath}").to.be.true
it "runs project headlessly and exits with exit code 0 and yells about old version of CLI", ->
Project.add(@todosPath)
@@ -65,59 +65,59 @@ describe "Routes", ->
## get all the config defaults
## and allow us to override them
## for each test
cfg = config.set(obj)
config.set(obj)
.then (cfg) =>
## use a jar for each test
## but reset it automatically
## between test
jar = rp.jar()
## use a jar for each test
## but reset it automatically
## between test
jar = rp.jar()
## use a custom request promise
## to automatically backfill these
## options including our proxy
@rp = (options = {}) =>
if _.isString(options)
url = options
options = {}
## use a custom request promise
## to automatically backfill these
## options including our proxy
@rp = (options = {}) =>
if _.isString(options)
url = options
options = {}
_.defaults options, {
url,
proxy: @proxy,
jar,
simple: false,
followRedirect: false,
resolveWithFullResponse: true
}
rp(options)
_.defaults options, {
url,
proxy: @proxy,
jar,
simple: false,
followRedirect: false,
resolveWithFullResponse: true
}
rp(options)
open = =>
Promise.all([
## open our https server
httpsServer.start(8443),
open = =>
Promise.all([
## open our https server
httpsServer.start(8443),
## and open our cypress server
@server = Server(Watchers())
## and open our cypress server
@server = Server(Watchers())
@server.open(cfg)
.spread (port) =>
if initialUrl
@server._onDomainSet(initialUrl)
@server.open(cfg)
.spread (port) =>
if initialUrl
@server._onDomainSet(initialUrl)
@srv = @server.getHttpServer()
@srv = @server.getHttpServer()
@session = new (Session({app: @srv}))
@session = new (Session({app: @srv}))
@proxy = "http://localhost:" + port
])
@proxy = "http://localhost:" + port
])
if @server
Promise.join(
httpsServer.stop()
@server.close()
)
.then(open)
else
open()
if @server
Promise.join(
httpsServer.stop()
@server.close()
)
.then(open)
else
open()
afterEach ->
evilDns.clear()
@@ -27,61 +27,61 @@ describe "Server", ->
## get all the config defaults
## and allow us to override them
## for each test
cfg = config.set(obj)
config.set(obj)
.then (cfg) =>
## use a jar for each test
## but reset it automatically
## between test
jar = rp.jar()
## use a jar for each test
## but reset it automatically
## between test
jar = rp.jar()
## use a custom request promise
## to automatically backfill these
## options including our proxy
@rp = (options = {}) =>
if _.isString(options)
url = options
options = {}
## use a custom request promise
## to automatically backfill these
## options including our proxy
@rp = (options = {}) =>
if _.isString(options)
url = options
options = {}
_.defaults options, {
url
proxy: @proxy
jar
simple: false
followRedirect: false
resolveWithFullResponse: true
}
rp(options)
_.defaults options, {
url
proxy: @proxy
jar
simple: false
followRedirect: false
resolveWithFullResponse: true
}
rp(options)
open = =>
Promise.all([
## open our https server
httpsServer.start(8443),
open = =>
Promise.all([
## open our https server
httpsServer.start(8443),
## and open our cypress server
@server = Server()
## and open our cypress server
@server = Server()
@server.open(cfg)
.spread (port) =>
if initialUrl
@server._onDomainSet(initialUrl)
@server.open(cfg)
.spread (port) =>
if initialUrl
@server._onDomainSet(initialUrl)
@srv = @server.getHttpServer()
@srv = @server.getHttpServer()
# @session = new (Session({app: @srv}))
# @session = new (Session({app: @srv}))
@proxy = "http://localhost:" + port
@proxy = "http://localhost:" + port
@fileServer = @server._fileServer.address()
])
@fileServer = @server._fileServer.address()
])
if @server
Promise.join(
httpsServer.stop()
@server.close()
)
.then(open)
else
open()
if @server
Promise.join(
httpsServer.stop()
@server.close()
)
.then(open)
else
open()
afterEach ->
nock.cleanAll()
@@ -2,6 +2,9 @@ require("../../spec_helper")
_ = require("lodash")
EE = require("events")
la = require("lazy-ass")
check = require("check-more-types")
menu = require("#{root}../lib/gui/menu")
Windows = require("#{root}../lib/gui/windows")
electron = require("#{root}../lib/browsers/electron")
@@ -31,8 +34,10 @@ describe "lib/browsers/electron", ->
context ".open", ->
beforeEach ->
@sandbox.stub(electron, "_render").resolves(@win)
state = savedState()
@sandbox.stub(state, "get").resolves(@state)
savedState()
.then (state) =>
la(check.fn(state.get), "state is missing .get to stub", state)
@sandbox.stub(state, "get").resolves(@state)
it "calls render with url, state, and options", ->
electron.open("electron", @url, @options, @automation)
+143 -131
View File
@@ -2,6 +2,7 @@ require("../spec_helper")
_ = require("lodash")
path = require("path")
R = require("ramda")
config = require("#{root}lib/config")
configUtil = require("#{root}lib/util/config")
scaffold = require("#{root}lib/scaffold")
@@ -412,7 +413,10 @@ describe "lib/config", ->
beforeEach ->
@defaults = (prop, value, cfg = {}, options = {}) =>
cfg.projectRoot = "/foo/bar/"
expect(config.mergeDefaults(cfg, options)[prop]).to.deep.eq(value)
config.mergeDefaults(cfg, options)
.then R.prop(prop)
.then (result) ->
expect(result).to.deep.eq(value)
it "port=null", ->
@defaults "port", null
@@ -504,29 +508,29 @@ describe "lib/config", ->
@defaults "supportFile", false, {supportFile: false}
it "resets numTestsKeptInMemory to 0 when headless", ->
cfg = config.mergeDefaults({projectRoot: "/foo/bar/"}, {isTextTerminal: true})
expect(cfg.numTestsKeptInMemory).to.eq(0)
config.mergeDefaults({projectRoot: "/foo/bar/"}, {isTextTerminal: true})
.then (cfg) ->
expect(cfg.numTestsKeptInMemory).to.eq(0)
it "resets watchForFileChanges to false when headless", ->
cfg = config.mergeDefaults({projectRoot: "/foo/bar/"}, {isTextTerminal: true})
expect(cfg.watchForFileChanges).to.be.false
config.mergeDefaults({projectRoot: "/foo/bar/"}, {isTextTerminal: true})
.then (cfg) ->
expect(cfg.watchForFileChanges).to.be.false
it "can override morgan in options", ->
cfg = config.mergeDefaults({projectRoot: "/foo/bar/"}, {morgan: false})
expect(cfg.morgan).to.be.false
config.mergeDefaults({projectRoot: "/foo/bar/"}, {morgan: false})
.then (cfg) ->
expect(cfg.morgan).to.be.false
it "can override isTextTerminal in options", ->
cfg = config.mergeDefaults({projectRoot: "/foo/bar/"}, {isTextTerminal: true})
expect(cfg.isTextTerminal).to.be.true
config.mergeDefaults({projectRoot: "/foo/bar/"}, {isTextTerminal: true})
.then (cfg) ->
expect(cfg.isTextTerminal).to.be.true
it "can override socketId in options", ->
cfg = config.mergeDefaults({projectRoot: "/foo/bar/"}, {socketId: 1234})
expect(cfg.socketId).to.eq(1234)
config.mergeDefaults({projectRoot: "/foo/bar/"}, {socketId: 1234})
.then (cfg) ->
expect(cfg.socketId).to.eq(1234)
it "deletes envFile", ->
obj = {
@@ -541,15 +545,15 @@ describe "lib/config", ->
}
}
cfg = config.mergeDefaults(obj)
expect(cfg.environmentVariables).to.deep.eq({
foo: "bar"
bar: "baz"
version: "1.0.1"
})
expect(cfg.env).to.eq(process.env["CYPRESS_ENV"])
expect(cfg).not.to.have.property("envFile")
config.mergeDefaults(obj)
.then (cfg) ->
expect(cfg.environmentVariables).to.deep.eq({
foo: "bar"
bar: "baz"
version: "1.0.1"
})
expect(cfg.env).to.eq(process.env["CYPRESS_ENV"])
expect(cfg).not.to.have.property("envFile")
it "merges env into @config.env", ->
obj = {
@@ -568,14 +572,14 @@ describe "lib/config", ->
}
}
cfg = config.mergeDefaults(obj, options)
expect(cfg.environmentVariables).to.deep.eq({
host: "localhost"
user: "brian"
version: "0.13.1"
foo: "bar"
})
config.mergeDefaults(obj, options)
.then (cfg) ->
expect(cfg.environmentVariables).to.deep.eq({
host: "localhost"
user: "brian"
version: "0.13.1"
foo: "bar"
})
describe ".resolved", ->
it "sets reporter and port to cli", ->
@@ -588,38 +592,38 @@ describe "lib/config", ->
port: 1234
}
cfg = config.mergeDefaults(obj, options)
expect(cfg.resolved).to.deep.eq({
port: { value: 1234, from: "cli" },
hosts: { value: null, from: "default" }
reporter: { value: "json", from: "cli" },
reporterOptions: { value: null, from: "default" },
baseUrl: { value: null, from: "default" },
defaultCommandTimeout: { value: 4000, from: "default" },
pageLoadTimeout: { value: 60000, from: "default" },
requestTimeout: { value: 5000, from: "default" },
responseTimeout: { value: 30000, from: "default" },
execTimeout: { value: 60000, from: "default" },
screenshotOnHeadlessFailure:{ value: true, from: "default" },
numTestsKeptInMemory: { value: 50, from: "default" },
waitForAnimations: { value: true, from: "default" },
animationDistanceThreshold: { value: 5, from: "default" },
trashAssetsBeforeHeadlessRuns: { value: true, from: "default" },
watchForFileChanges: { value: true, from: "default" },
chromeWebSecurity: { value: true, from: "default" },
viewportWidth: { value: 1000, from: "default" },
viewportHeight: { value: 660, from: "default" },
fileServerFolder: { value: "", from: "default" },
videoRecording: { value: true, from: "default" }
videoCompression: { value: 32, from: "default" }
videosFolder: { value: "cypress/videos", from: "default" },
supportFile: { value: "cypress/support", from: "default" },
fixturesFolder: { value: "cypress/fixtures", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
environmentVariables: { }
})
config.mergeDefaults(obj, options)
.then (cfg) ->
expect(cfg.resolved).to.deep.eq({
port: { value: 1234, from: "cli" },
hosts: { value: null, from: "default" }
reporter: { value: "json", from: "cli" },
reporterOptions: { value: null, from: "default" },
baseUrl: { value: null, from: "default" },
defaultCommandTimeout: { value: 4000, from: "default" },
pageLoadTimeout: { value: 60000, from: "default" },
requestTimeout: { value: 5000, from: "default" },
responseTimeout: { value: 30000, from: "default" },
execTimeout: { value: 60000, from: "default" },
screenshotOnHeadlessFailure:{ value: true, from: "default" },
numTestsKeptInMemory: { value: 50, from: "default" },
waitForAnimations: { value: true, from: "default" },
animationDistanceThreshold: { value: 5, from: "default" },
trashAssetsBeforeHeadlessRuns: { value: true, from: "default" },
watchForFileChanges: { value: true, from: "default" },
chromeWebSecurity: { value: true, from: "default" },
viewportWidth: { value: 1000, from: "default" },
viewportHeight: { value: 660, from: "default" },
fileServerFolder: { value: "", from: "default" },
videoRecording: { value: true, from: "default" }
videoCompression: { value: 32, from: "default" }
videosFolder: { value: "cypress/videos", from: "default" },
supportFile: { value: "cypress/support", from: "default" },
fixturesFolder: { value: "cypress/fixtures", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
environmentVariables: { }
})
it "sets config, envFile and env", ->
@sandbox.stub(config, "getProcessEnvVars").returns({quux: "quux"})
@@ -641,55 +645,55 @@ describe "lib/config", ->
options = {}
cfg = config.mergeDefaults(obj, options)
expect(cfg.resolved).to.deep.eq({
port: { value: 2020, from: "config" },
hosts: { value: null, from: "default" }
reporter: { value: "spec", from: "default" },
reporterOptions: { value: null, from: "default" },
baseUrl: { value: "http://localhost:8080", from: "config" },
defaultCommandTimeout: { value: 4000, from: "default" },
pageLoadTimeout: { value: 60000, from: "default" },
requestTimeout: { value: 5000, from: "default" },
responseTimeout: { value: 30000, from: "default" },
execTimeout: { value: 60000, from: "default" },
numTestsKeptInMemory: { value: 50, from: "default" },
waitForAnimations: { value: true, from: "default" },
animationDistanceThreshold: { value: 5, from: "default" },
screenshotOnHeadlessFailure:{ value: true, from: "default" },
trashAssetsBeforeHeadlessRuns: { value: true, from: "default" },
watchForFileChanges: { value: true, from: "default" },
chromeWebSecurity: { value: true, from: "default" },
viewportWidth: { value: 1000, from: "default" },
viewportHeight: { value: 660, from: "default" },
fileServerFolder: { value: "", from: "default" },
videoRecording: { value: true, from: "default" }
videoCompression: { value: 32, from: "default" }
videosFolder: { value: "cypress/videos", from: "default" },
supportFile: { value: "cypress/support", from: "default" },
fixturesFolder: { value: "cypress/fixtures", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
environmentVariables: {
foo: {
value: "foo"
from: "config"
config.mergeDefaults(obj, options)
.then (cfg) ->
expect(cfg.resolved).to.deep.eq({
port: { value: 2020, from: "config" },
hosts: { value: null, from: "default" }
reporter: { value: "spec", from: "default" },
reporterOptions: { value: null, from: "default" },
baseUrl: { value: "http://localhost:8080", from: "config" },
defaultCommandTimeout: { value: 4000, from: "default" },
pageLoadTimeout: { value: 60000, from: "default" },
requestTimeout: { value: 5000, from: "default" },
responseTimeout: { value: 30000, from: "default" },
execTimeout: { value: 60000, from: "default" },
numTestsKeptInMemory: { value: 50, from: "default" },
waitForAnimations: { value: true, from: "default" },
animationDistanceThreshold: { value: 5, from: "default" },
screenshotOnHeadlessFailure:{ value: true, from: "default" },
trashAssetsBeforeHeadlessRuns: { value: true, from: "default" },
watchForFileChanges: { value: true, from: "default" },
chromeWebSecurity: { value: true, from: "default" },
viewportWidth: { value: 1000, from: "default" },
viewportHeight: { value: 660, from: "default" },
fileServerFolder: { value: "", from: "default" },
videoRecording: { value: true, from: "default" }
videoCompression: { value: 32, from: "default" }
videosFolder: { value: "cypress/videos", from: "default" },
supportFile: { value: "cypress/support", from: "default" },
fixturesFolder: { value: "cypress/fixtures", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
environmentVariables: {
foo: {
value: "foo"
from: "config"
}
bar: {
value: "bar"
from: "envFile"
}
baz: {
value: "baz"
from: "cli"
}
quux: {
value: "quux"
from: "env"
}
}
bar: {
value: "bar"
from: "envFile"
}
baz: {
value: "baz"
from: "cli"
}
quux: {
value: "quux"
from: "env"
}
}
})
})
context ".parseEnv", ->
it "merges together env from config, env from file, env from process, and env from CLI", ->
@@ -799,8 +803,9 @@ describe "lib/config", ->
obj = {
projectRoot: "/_test-output/path/to/project"
}
expect(config.setSupportFileAndFolder(obj)).to.eql(obj)
config.setSupportFileAndFolder(obj)
.then (result) ->
expect(result).to.eql(obj)
it "sets the full path to the supportFile and supportFolder if it exists", ->
projectRoot = process.cwd()
@@ -810,14 +815,15 @@ describe "lib/config", ->
supportFile: "test/unit/config_spec.coffee"
})
expect(config.setSupportFileAndFolder(obj)).to.eql({
projectRoot: projectRoot
supportFile: "#{projectRoot}/test/unit/config_spec.coffee"
supportFolder: "#{projectRoot}/test/unit"
})
config.setSupportFileAndFolder(obj)
.then (result) ->
expect(result).to.eql({
projectRoot: projectRoot
supportFile: "#{projectRoot}/test/unit/config_spec.coffee"
supportFolder: "#{projectRoot}/test/unit"
})
it "sets the supportFile to default index.js if it does not exist, support folder does not exist, and supportFile is the default", ->
@sandbox.stub(fs, "existsSync").returns(false)
projectRoot = process.cwd()
obj = config.setAbsolutePaths({
@@ -825,11 +831,13 @@ describe "lib/config", ->
supportFile: "cypress/support"
})
expect(config.setSupportFileAndFolder(obj)).to.eql({
projectRoot: projectRoot
supportFile: "#{projectRoot}/cypress/support/index.js"
supportFolder: "#{projectRoot}/cypress/support"
})
config.setSupportFileAndFolder(obj)
.then (result) ->
expect(result).to.eql({
projectRoot: projectRoot
supportFile: "#{projectRoot}/cypress/support/index.js"
supportFolder: "#{projectRoot}/cypress/support"
})
it "sets the supportFile to false if it does not exist, support folder exists, and supportFile is the default", ->
projectRoot = path.join(process.cwd(), "test/support/fixtures/projects/blank-support")
@@ -839,10 +847,12 @@ describe "lib/config", ->
supportFile: "cypress/support"
})
expect(config.setSupportFileAndFolder(obj)).to.eql({
projectRoot: projectRoot
supportFile: false
})
config.setSupportFileAndFolder(obj)
.then (result) ->
expect(result).to.eql({
projectRoot: projectRoot
supportFile: false
})
it "throws error if supportFile is not default and does not exist", ->
projectRoot = process.cwd()
@@ -852,7 +862,9 @@ describe "lib/config", ->
supportFile: "does/not/exist"
})
expect(-> config.setSupportFileAndFolder(obj)).to.throw("Support file missing or invalid.")
config.setSupportFileAndFolder(obj)
.catch (err) ->
expect(err.message).to.include("Support file missing or invalid.")
context ".setParentTestsPaths", ->
it "sets parentTestsFolder and parentTestsFolderDisplay", ->
@@ -1,6 +1,7 @@
require("../../spec_helper")
_ = require("lodash")
delay = require("delay")
EE = require("events").EventEmitter
BrowserWindow = require("electron").BrowserWindow
Windows = require("#{root}../lib/gui/windows")
@@ -48,17 +49,18 @@ describe "lib/gui/windows", ->
context ".trackState", ->
beforeEach ->
@state = savedState()
@sandbox.stub(@state, "set")
savedState()
.then (@state) =>
@sandbox.stub(@state, "set")
@projectPath = undefined
@keys = {
width: "theWidth"
height: "someHeight"
x: "anX"
y: "aY"
devTools: "whatsUpWithDevTools"
}
@projectPath = undefined
@keys = {
width: "theWidth"
height: "someHeight"
x: "anX"
y: "aY"
devTools: "whatsUpWithDevTools"
}
it "saves size and position when window resizes, debounced", ->
## tried using useFakeTimers here, but it didn't work for some
@@ -69,20 +71,23 @@ describe "lib/gui/windows", ->
@win.emit("resize")
expect(_.debounce).to.be.called
expect(@state.set).to.be.calledWith({
theWidth: 1
someHeight: 2
anX: 3
aY: 4
})
delay(100)
.then () =>
expect(@state.set).to.be.calledWith({
theWidth: 1
someHeight: 2
anX: 3
aY: 4
})
it "returns if window isDestroyed on resize", ->
@win.isDestroyed.returns(true)
Windows.trackState(@projectPath, @win, @keys)
@win.emit("resize")
expect(@state.set).not.to.be.called
delay(100)
.then () =>
expect(@state.set).not.to.be.called
it "saves position when window moves, debounced", ->
## tried using useFakeTimers here, but it didn't work for some
@@ -91,10 +96,12 @@ describe "lib/gui/windows", ->
Windows.trackState(@projectPath, @win, @keys)
@win.emit("moved")
expect(@state.set).to.be.calledWith({
anX: 3
aY: 4
})
delay(100)
.then () =>
expect(@state.set).to.be.calledWith({
anX: 3
aY: 4
})
it "returns if window isDestroyed on moved", ->
@win.isDestroyed.returns(true)
@@ -102,19 +109,25 @@ describe "lib/gui/windows", ->
Windows.trackState(@projectPath, @win, @keys)
@win.emit("moved")
expect(@state.set).not.to.be.called
delay(100)
.then () =>
expect(@state.set).not.to.be.called
it "saves dev tools state when opened", ->
Windows.trackState(@projectPath, @win, @keys)
@win.webContents.emit("devtools-opened")
expect(@state.set).to.be.calledWith({whatsUpWithDevTools: true})
delay(100)
.then () =>
expect(@state.set).to.be.calledWith({whatsUpWithDevTools: true})
it "saves dev tools state when closed", ->
Windows.trackState(@projectPath, @win, @keys)
@win.webContents.emit("devtools-closed")
expect(@state.set).to.be.calledWith({whatsUpWithDevTools: false})
delay(100)
.then () =>
expect(@state.set).to.be.calledWith({whatsUpWithDevTools: false})
context ".automation", ->
beforeEach ->
@@ -0,0 +1,22 @@
require("../spec_helper")
describe "misc tests", ->
beforeEach () ->
@sandbox.spy(console, "error")
it "warns when trying to use fs.existsSync", ->
fs.existsSync(__filename)
warning = "WARNING: fs sync methods can fail due to EMFILE errors"
expect(console.error).to.be.calledWith(warning)
# also print stack trace, maybe check that
context "fs.pathExists", ->
it "finds this file", ->
fs.pathExists(__filename)
.then (found) ->
expect(found).to.be.true
it "does not find non-existent file", ->
fs.pathExists('does-not-exist')
.then (found) ->
expect(found).to.be.false
+46 -39
View File
@@ -29,8 +29,9 @@ describe "lib/project", ->
settings.read(@todosPath).then (obj = {}) =>
{@projectId} = obj
@config = config.set({projectName: "project", projectRoot: "/foo/bar"})
@project = Project(@todosPath)
config.set({projectName: "project", projectRoot: "/foo/bar"})
.then (@config) =>
@project = Project(@todosPath)
afterEach ->
Fixtures.remove()
@@ -51,10 +52,12 @@ describe "lib/project", ->
@sandbox.stub(config, "get").withArgs(@todosPath).resolves({ integrationFolder })
@sandbox.stub(@project, "determineIsNewProject").withArgs(integrationFolder).resolves(false)
@project.cfg = { integrationFolder }
savedState(@project.projectRoot).remove()
savedState(@project.projectRoot)
.then (state) -> state.remove()
afterEach ->
savedState(@project.projectRoot).remove()
savedState(@project.projectRoot)
.then (state) -> state.remove()
it "saves state without modification", ->
@project.saveState()
@@ -88,18 +91,19 @@ describe "lib/project", ->
@sandbox.stub(@project, "determineIsNewProject").withArgs(integrationFolder).resolves(false)
it "calls config.get with projectRoot + options + saved state", ->
state = savedState(@todosPath)
@sandbox.stub(state, "get").resolves({ reporterWidth: 225 })
@project.getConfig({foo: "bar"})
.then (cfg) ->
expect(cfg).to.deep.eq({
integrationFolder
isNewProject: false
baz: "quux"
state: {
reporterWidth: 225
}
})
savedState(@todosPath)
.then (state) =>
@sandbox.stub(state, "get").resolves({ reporterWidth: 225 })
@project.getConfig({foo: "bar"})
.then (cfg) ->
expect(cfg).to.deep.eq({
integrationFolder
isNewProject: false
baz: "quux"
state: {
reporterWidth: 225
}
})
it "resolves if cfg is already set", ->
@project.cfg = {
@@ -115,19 +119,20 @@ describe "lib/project", ->
})
it "sets cfg.isNewProject to true when state.showedOnBoardingModal is true", ->
state = savedState(@todosPath)
@sandbox.stub(state, "get").resolves({ showedOnBoardingModal: true })
savedState(@todosPath)
.then (state) =>
@sandbox.stub(state, "get").resolves({ showedOnBoardingModal: true })
@project.getConfig({foo: "bar"})
.then (cfg) ->
expect(cfg).to.deep.eq({
integrationFolder
isNewProject: false
baz: "quux"
state: {
showedOnBoardingModal: true
}
})
@project.getConfig({foo: "bar"})
.then (cfg) ->
expect(cfg).to.deep.eq({
integrationFolder
isNewProject: false
baz: "quux"
state: {
showedOnBoardingModal: true
}
})
context "#open", ->
beforeEach ->
@@ -143,7 +148,7 @@ describe "lib/project", ->
@project.open(opts).then =>
expect(@project.watchSettingsAndStartWebsockets).to.be.calledWith(opts, @project.cfg)
it "calls #scaffold with server config", ->
it "calls #scaffold with server config promise", ->
@project.open().then =>
expect(@project.scaffold).to.be.calledWith(@config)
@@ -281,7 +286,7 @@ describe "lib/project", ->
context "#watchSupportFile", ->
beforeEach ->
@sandbox.stub(fs, "existsSync").returns(true)
@sandbox.stub(fs, "pathExists").resolves(true)
@project = Project("/_test-output/path/to/project")
@project.server = {onTestFileChange: @sandbox.spy()}
@watchBundle = @sandbox.stub(@project.watchers, "watchBundle").resolves()
@@ -297,24 +302,26 @@ describe "lib/project", ->
it "calls watchers.watchBundle with relative path to file", ->
@project.watchSupportFile(@config)
expect(@watchBundle).to.be.calledWith("foo/bar.js", @config)
.then () =>
expect(@watchBundle).to.be.calledWith("foo/bar.js", @config)
it "calls server.onTestFileChange when file changes", ->
@project.watchSupportFile(@config)
@watchBundle.firstCall.args[2].onChange()
expect(@project.server.onTestFileChange).to.be.calledWith("foo/bar.js")
.then () =>
@watchBundle.firstCall.args[2].onChange()
expect(@project.server.onTestFileChange).to.be.calledWith("foo/bar.js")
it "does not add change listener when {watchForFileChanges: false}", ->
@config.watchForFileChanges = false
@project.watchSupportFile(@config)
expect(@watchBundle.firstCall.args[2]).to.be.undefined
.then () =>
expect(@watchBundle.firstCall.args[2]).to.be.undefined
it "throws when support file does not exist", ->
fs.existsSync.returns(false)
expect(=> @project.watchSupportFile(@config)).to.throw("Support file missing or invalid.")
fs.pathExists.resolves(false)
@project.watchSupportFile(@config)
.catch (e) ->
expect(e.message).to.include("Support file missing or invalid.")
context "#watchSettingsAndStartWebsockets", ->
beforeEach ->
@@ -35,18 +35,24 @@ describe "lib/saved_state", ->
it "is a function", ->
expect(savedState).to.be.a("function")
it "returns an instance of FileUtil", ->
expect(savedState()).to.be.instanceof(FileUtil)
it "resolves with an instance of FileUtil", ->
savedState()
.then (state) ->
expect(state).to.be.instanceof(FileUtil)
it "sets file path to app data path and file name to 'state'", ->
statePath = savedState().path
expected = path.join(appData.path(), "projects", "__global__", "state.json")
expect(statePath).to.equal(expected)
savedState()
.then (state) ->
statePath = state.path
expected = path.join(appData.path(), "projects", "__global__", "state.json")
expect(statePath).to.equal(expected)
it "caches state file instance per path", ->
a = savedState("/foo/bar")
b = savedState("/foo/bar")
expect(a).to.equal(b)
Promise.all([
savedState("/foo/bar"),
savedState("/foo/bar")
]).spread (a, b) ->
expect(a).to.equal(b)
it "returns different state file for different path", ->
a = savedState("/foo/bar")
+4 -2
View File
@@ -16,8 +16,10 @@ mockery.registerMock("morgan", -> morganFn)
describe "lib/server", ->
beforeEach ->
@config = config.set({projectRoot: "/foo/bar/"})
@server = Server()
config.set({projectRoot: "/foo/bar/"})
.then (cfg) =>
@config = cfg
@server = Server()
afterEach ->
@server and @server.close()
@@ -1,6 +1,7 @@
require("../spec_helper")
path = require("path")
R = require("ramda")
settings = require("#{root}lib/util/settings")
projectRoot = process.cwd()
@@ -46,11 +47,16 @@ describe "lib/settings", ->
expect(err.message).to.include("SyntaxError")
expect(err.message).to.include(projectRoot)
noArguments = R.nAry(0)
it "does not write initial file", ->
settings.readEnv(projectRoot)
.then (obj) ->
expect(obj).to.deep.eq({})
expect(fs.existsSync("cypress.env.json")).to.be.false
.then () ->
fs.pathExists("cypress.env.json")
.then (found) ->
expect(found).to.be.false
context ".id", ->
beforeEach ->