mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-21 22:49:16 -05:00
feat: introduce v8 snapshots to improve startup performance (#24295)
Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> Co-authored-by: Zach Bloomquist <git@chary.us> Co-authored-by: Tyler Biethman <tbiethman@users.noreply.github.com> Co-authored-by: Matt Henkes <mjhenkes@gmail.com> Co-authored-by: Chris Breiding <chrisbreiding@users.noreply.github.com> Co-authored-by: Matt Schile <mschile@cypress.io> Co-authored-by: Mark Noonan <mark@cypress.io> Co-authored-by: Zachary Williams <ZachJW34@gmail.com> Co-authored-by: Ben M <benm@cypress.io> Co-authored-by: Zachary Williams <zachjw34@gmail.com> Co-authored-by: astone123 <adams@cypress.io> Co-authored-by: Bill Glesias <bglesias@gmail.com> Co-authored-by: Emily Rohrbough <emilyrohrbough@yahoo.com> Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Adam Stone <adams@cypress.io> Co-authored-by: Blue F <blue@cypress.io> Co-authored-by: GitStart <1501599+gitstart@users.noreply.github.com> Co-authored-by: Mike Plummer <mike-plummer@users.noreply.github.com> Co-authored-by: Jordan <jordan@jpdesigning.com> Co-authored-by: Sam Goodger <turbo@tailz.dev> Co-authored-by: Colum Ferry <cferry09@gmail.com> Co-authored-by: Stokes Player <stokes@cypress.io> Co-authored-by: Vilhelm Melkstam <vilhelm.melkstam@gmail.com> Co-authored-by: amehta265 <65267668+amehta265@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,9 @@ const { join } = require('path')
|
||||
const glob = require('glob')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const { setupV8Snapshots } = require('@tooling/v8-snapshot')
|
||||
const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')
|
||||
const { cleanup } = require('./binary/binary-cleanup')
|
||||
|
||||
module.exports = async function (params) {
|
||||
console.log('****************************')
|
||||
@@ -44,4 +47,23 @@ module.exports = async function (params) {
|
||||
await fs.copy(distNodeModules, appNodeModules)
|
||||
|
||||
console.log('all node_modules subfolders copied to', outputFolder)
|
||||
|
||||
const exePathPerPlatform = {
|
||||
darwin: join(params.appOutDir, 'Cypress.app', 'Contents', 'MacOS', 'Cypress'),
|
||||
linux: join(params.appOutDir, 'Cypress'),
|
||||
win32: join(params.appOutDir, 'Cypress.exe'),
|
||||
}
|
||||
|
||||
if (!['1', 'true'].includes(process.env.DISABLE_SNAPSHOT_REQUIRE)) {
|
||||
await flipFuses(
|
||||
exePathPerPlatform[os.platform()],
|
||||
{
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: true,
|
||||
},
|
||||
)
|
||||
|
||||
await setupV8Snapshots(params.appOutDir)
|
||||
await cleanup(outputFolder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const { consolidateDeps } = require('@tooling/v8-snapshot')
|
||||
const del = require('del')
|
||||
const esbuild = require('esbuild')
|
||||
const snapshotMetadata = require('@tooling/v8-snapshot/cache/prod-darwin/snapshot-meta.cache.json')
|
||||
const tempDir = require('temp-dir')
|
||||
const workingDir = path.join(tempDir, 'binary-cleanup-workdir')
|
||||
|
||||
fs.ensureDirSync(workingDir)
|
||||
|
||||
async function removeEmptyDirectories (directory) {
|
||||
// lstat does not follow symlinks (in contrast to stat)
|
||||
const fileStats = await fs.lstat(directory)
|
||||
|
||||
if (!fileStats.isDirectory()) {
|
||||
return
|
||||
}
|
||||
|
||||
let fileNames = await fs.readdir(directory)
|
||||
|
||||
if (fileNames.length > 0) {
|
||||
const recursiveRemovalPromises = fileNames.map(
|
||||
(fileName) => removeEmptyDirectories(path.join(directory, fileName)),
|
||||
)
|
||||
|
||||
await Promise.all(recursiveRemovalPromises)
|
||||
|
||||
// re-evaluate fileNames; after deleting subdirectory
|
||||
// we may have parent directory empty now
|
||||
fileNames = await fs.readdir(directory)
|
||||
}
|
||||
|
||||
if (fileNames.length === 0) {
|
||||
await fs.rmdir(directory)
|
||||
}
|
||||
}
|
||||
|
||||
const getDependencyPathsToKeep = async () => {
|
||||
let entryPoints = new Set([
|
||||
// This is the entry point for the server bundle. It will not have access to the snapshot yet. It needs to be kept in the binary
|
||||
require.resolve('@packages/server/index.js'),
|
||||
// This is a dynamic import that is used to load the snapshot require logic. It will not have access to the snapshot yet. It needs to be kept in the binary
|
||||
require.resolve('@packages/server/hook-require.js'),
|
||||
// These dependencies are started in a new process or thread and will not have access to the snapshot. They need to be kept in the binary
|
||||
require.resolve('@packages/server/lib/plugins/child/require_async_child.js'),
|
||||
require.resolve('@packages/server/lib/plugins/child/register_ts_node.js'),
|
||||
require.resolve('@packages/rewriter/lib/threads/worker.ts'),
|
||||
// These dependencies use the `require.resolve(<dependency>, { paths: [<path>] })` pattern where <path> is a path within the cypress monorepo. These will not be
|
||||
// pulled in by esbuild but still need to be kept in the binary.
|
||||
require.resolve('webpack'),
|
||||
require.resolve('webpack-dev-server', { paths: [path.join(__dirname, '..', '..', 'npm', 'webpack-dev-server')] }),
|
||||
require.resolve('html-webpack-plugin-4', { paths: [path.join(__dirname, '..', '..', 'npm', 'webpack-dev-server')] }),
|
||||
require.resolve('html-webpack-plugin-5', { paths: [path.join(__dirname, '..', '..', 'npm', 'webpack-dev-server')] }),
|
||||
// These dependencies are completely dynamic using the pattern `require(`./${name}`)` and will not be pulled in by esbuild but still need to be kept in the binary.
|
||||
...['ibmi',
|
||||
'sunos',
|
||||
'android',
|
||||
'darwin',
|
||||
'freebsd',
|
||||
'linux',
|
||||
'openbsd',
|
||||
'sunos',
|
||||
'win32'].map((platform) => require.resolve(`default-gateway/${platform}`)),
|
||||
])
|
||||
let esbuildResult
|
||||
let newEntryPointsFound = true
|
||||
|
||||
// The general idea here is to run esbuild on entry points that are used outside of the snapshot. If, during the process,
|
||||
// we find places where we do a require.resolve on a module, that should be treated as an additional entry point and we run
|
||||
// esbuild again. We do this until we no longer find any new entry points. The resulting metafile inputs are
|
||||
// the dependency paths that we need to ensure stay in the snapshot.
|
||||
while (newEntryPointsFound) {
|
||||
esbuildResult = await esbuild.build({
|
||||
entryPoints: [...entryPoints],
|
||||
bundle: true,
|
||||
outdir: workingDir,
|
||||
platform: 'node',
|
||||
metafile: true,
|
||||
external: [
|
||||
'./packages/server/server-entry',
|
||||
'fsevents',
|
||||
'pnpapi',
|
||||
'@swc/core',
|
||||
'emitter',
|
||||
],
|
||||
})
|
||||
|
||||
newEntryPointsFound = false
|
||||
esbuildResult.warnings.forEach((warning) => {
|
||||
const matches = warning.text.match(/"(.*)" should be marked as external for use with "require.resolve"/)
|
||||
const warningSubject = matches && matches[1]
|
||||
|
||||
if (warningSubject) {
|
||||
let entryPoint
|
||||
|
||||
if (warningSubject.startsWith('.')) {
|
||||
entryPoint = path.join(__dirname, '..', '..', path.dirname(warning.location.file), warningSubject)
|
||||
} else {
|
||||
entryPoint = require.resolve(warningSubject)
|
||||
}
|
||||
|
||||
if (path.extname(entryPoint) !== '' && !entryPoints.has(entryPoint)) {
|
||||
newEntryPointsFound = true
|
||||
entryPoints.add(entryPoint)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [...Object.keys(esbuildResult.metafile.inputs), ...entryPoints]
|
||||
}
|
||||
|
||||
const cleanup = async (buildAppDir) => {
|
||||
// 1. Retrieve all dependencies that still need to be kept in the binary. In theory, we could use the bundles generated here as single files within the binary,
|
||||
// but for now, we just track on the dependencies that get pulled in
|
||||
const keptDependencies = [...await getDependencyPathsToKeep(), 'package.json', 'packages/server/server-entry.js']
|
||||
|
||||
// 2. Gather the dependencies that could potentially be removed from the binary due to being in the snapshot
|
||||
const potentiallyRemovedDependencies = [...snapshotMetadata.healthy, ...snapshotMetadata.deferred, ...snapshotMetadata.norewrite]
|
||||
|
||||
// 3. Remove all dependencies that are in the snapshot but not in the list of kept dependencies from the binary
|
||||
await Promise.all(potentiallyRemovedDependencies.map(async (dependency) => {
|
||||
// marionette-client requires all of its dependencies in a very non-standard dynamic way. We will keep anything in marionette-client
|
||||
if (!keptDependencies.includes(dependency.slice(2)) && !dependency.includes('marionette-client')) {
|
||||
await fs.remove(path.join(buildAppDir, dependency.replace(/.ts$/, '.js')))
|
||||
}
|
||||
}))
|
||||
|
||||
// 4. Consolidate dependencies that are safe to consolidate (`lodash` and `bluebird`)
|
||||
await consolidateDeps({ projectBaseDir: buildAppDir })
|
||||
|
||||
// 5. Remove various unnecessary files from the binary to further clean things up. Likely, there is additional work that can be done here
|
||||
await del([
|
||||
// Remove test files
|
||||
path.join(buildAppDir, '**', 'test'),
|
||||
path.join(buildAppDir, '**', 'tests'),
|
||||
// What we need of prettier is entirely encapsulated within the v8 snapshot, but has a few leftover large files
|
||||
path.join(buildAppDir, '**', 'prettier', 'esm'),
|
||||
path.join(buildAppDir, '**', 'prettier', 'standalone.js'),
|
||||
path.join(buildAppDir, '**', 'prettier', 'bin-prettier.js'),
|
||||
// ESM files are mostly not needed currently
|
||||
path.join(buildAppDir, '**', '@babel', '**', 'esm'),
|
||||
path.join(buildAppDir, '**', 'ramda', 'es'),
|
||||
path.join(buildAppDir, '**', 'jimp', 'es'),
|
||||
path.join(buildAppDir, '**', '@jimp', '**', 'es'),
|
||||
path.join(buildAppDir, '**', 'nexus', 'dist-esm'),
|
||||
path.join(buildAppDir, '**', '@graphql-tools', '**', '*.mjs'),
|
||||
path.join(buildAppDir, '**', 'graphql', '**', '*.mjs'),
|
||||
// We currently do not use any map files
|
||||
path.join(buildAppDir, '**', '*js.map'),
|
||||
// License files need to be kept
|
||||
path.join(buildAppDir, '**', '!(LICENSE|license|License).md'),
|
||||
// These are type related files that are not used within the binary
|
||||
path.join(buildAppDir, '**', '*.d.ts'),
|
||||
path.join(buildAppDir, '**', 'ajv', 'lib', '**', '*.ts'),
|
||||
path.join(buildAppDir, '**', '*.flow'),
|
||||
// Example files are not needed
|
||||
path.join(buildAppDir, '**', 'jimp', 'browser', 'examples'),
|
||||
// Documentation files are not needed
|
||||
path.join(buildAppDir, '**', 'JSV', 'jsdoc-toolkit'),
|
||||
path.join(buildAppDir, '**', 'JSV', 'docs'),
|
||||
path.join(buildAppDir, '**', 'fluent-ffmpeg', 'doc'),
|
||||
// Files used as part of prebuilding are not necessary
|
||||
path.join(buildAppDir, '**', 'registry-js', 'prebuilds'),
|
||||
path.join(buildAppDir, '**', '*.cc'),
|
||||
path.join(buildAppDir, '**', '*.o'),
|
||||
path.join(buildAppDir, '**', '*.c'),
|
||||
path.join(buildAppDir, '**', '*.h'),
|
||||
// Remove distributions that are not needed in the binary
|
||||
path.join(buildAppDir, '**', 'ramda', 'dist'),
|
||||
path.join(buildAppDir, '**', 'jimp', 'browser'),
|
||||
path.join(buildAppDir, '**', '@jimp', '**', 'src'),
|
||||
path.join(buildAppDir, '**', 'nexus', 'src'),
|
||||
path.join(buildAppDir, '**', 'source-map', 'dist'),
|
||||
path.join(buildAppDir, '**', 'source-map-js', 'dist'),
|
||||
path.join(buildAppDir, '**', 'pako', 'dist'),
|
||||
path.join(buildAppDir, '**', 'node-forge', 'dist'),
|
||||
path.join(buildAppDir, '**', 'pngjs', 'browser.js'),
|
||||
path.join(buildAppDir, '**', 'plist', 'dist'),
|
||||
// Remove yarn locks
|
||||
path.join(buildAppDir, '**', 'yarn.lock'),
|
||||
], { force: true })
|
||||
|
||||
// 6. Remove any empty directories as a result of the rest of the cleanup
|
||||
await removeEmptyDirectories(buildAppDir)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cleanup,
|
||||
}
|
||||
+50
-10
@@ -18,6 +18,7 @@ import { transformRequires } from './util/transform-requires'
|
||||
import execa from 'execa'
|
||||
import { testStaticAssets } from './util/testStaticAssets'
|
||||
import performanceTracking from '../../system-tests/lib/performance'
|
||||
import verify from '../../cli/lib/tasks/verify'
|
||||
|
||||
const globAsync = promisify(glob)
|
||||
|
||||
@@ -173,6 +174,10 @@ export async function buildCypressApp (options: BuildCypressAppOpts) {
|
||||
}, { spaces: 2 })
|
||||
|
||||
fs.writeFileSync(meta.distDir('index.js'), `\
|
||||
${!['1', 'true'].includes(process.env.DISABLE_SNAPSHOT_REQUIRE) ?
|
||||
`if (!global.snapshotResult && process.versions?.electron) {
|
||||
throw new Error('global.snapshotResult is not defined. This binary has been built incorrectly.')
|
||||
}` : ''}
|
||||
process.env.CYPRESS_INTERNAL_ENV = process.env.CYPRESS_INTERNAL_ENV || 'production'
|
||||
require('./packages/server')\
|
||||
`)
|
||||
@@ -196,8 +201,8 @@ require('./packages/server')\
|
||||
|
||||
await transformRequires(meta.distDir())
|
||||
|
||||
log(`#testVersion ${meta.distDir()}`)
|
||||
await testVersion(meta.distDir(), version)
|
||||
log(`#testDistVersion ${meta.distDir()}`)
|
||||
await testDistVersion(meta.distDir(), version)
|
||||
|
||||
log('#testStaticAssets')
|
||||
await testStaticAssets(meta.distDir())
|
||||
@@ -249,9 +254,18 @@ require('./packages/server')\
|
||||
console.log('electron-builder arguments:')
|
||||
console.log(args.join(' '))
|
||||
|
||||
// Update the root package.json with the next app version so that it is snapshot properly
|
||||
fs.writeJSONSync(path.join(CY_ROOT_DIR, 'package.json'), {
|
||||
...jsonRoot,
|
||||
version,
|
||||
}, { spaces: 2 })
|
||||
|
||||
try {
|
||||
await execa('electron-builder', args, {
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
NODE_OPTIONS: '--max_old_space_size=8192',
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
if (!skipSigning) {
|
||||
@@ -259,6 +273,9 @@ require('./packages/server')\
|
||||
}
|
||||
}
|
||||
|
||||
// Revert the root package.json so that subsequent steps will work properly
|
||||
fs.writeJSONSync(path.join(CY_ROOT_DIR, 'package.json'), jsonRoot, { spaces: 2 })
|
||||
|
||||
await checkMaxPathLength()
|
||||
|
||||
// lsDistFolder
|
||||
@@ -268,9 +285,6 @@ require('./packages/server')\
|
||||
|
||||
console.log(stdout)
|
||||
|
||||
// testVersion(buildAppDir)
|
||||
await testVersion(meta.buildAppDir(), version)
|
||||
|
||||
// runSmokeTests
|
||||
let usingXvfb = xvfb.isNeeded()
|
||||
|
||||
@@ -279,6 +293,9 @@ require('./packages/server')\
|
||||
await xvfb.start()
|
||||
}
|
||||
|
||||
log(`#testExecutableVersion ${meta.buildAppExecutable()}`)
|
||||
await testExecutableVersion(meta.buildAppExecutable(), version)
|
||||
|
||||
const executablePath = meta.buildAppExecutable()
|
||||
|
||||
await smoke.test(executablePath)
|
||||
@@ -358,23 +375,46 @@ function getIconFilename () {
|
||||
return iconFilename
|
||||
}
|
||||
|
||||
async function testVersion (dir: string, version: string) {
|
||||
async function testDistVersion (distDir: string, version: string) {
|
||||
log('#testVersion')
|
||||
|
||||
console.log('testing dist package version')
|
||||
console.log('by calling: node index.js --version')
|
||||
console.log('in the folder %s', dir)
|
||||
console.log('in the folder %s', distDir)
|
||||
|
||||
const result = await execa('node', ['index.js', '--version'], {
|
||||
cwd: dir,
|
||||
cwd: distDir,
|
||||
})
|
||||
|
||||
la(result.stdout, 'missing output when getting built version', result)
|
||||
|
||||
console.log('app in %s', dir)
|
||||
console.log('app in %s', distDir)
|
||||
console.log('built app version', result.stdout)
|
||||
la(result.stdout === version, 'different version reported',
|
||||
la(result.stdout.trim() === version.trim(), 'different version reported',
|
||||
result.stdout, 'from input version to build', version)
|
||||
|
||||
console.log('✅ using node --version works')
|
||||
}
|
||||
|
||||
async function testExecutableVersion (buildAppExecutable: string, version: string) {
|
||||
log('#testVersion')
|
||||
|
||||
console.log('testing built app executable version')
|
||||
console.log(`by calling: ${buildAppExecutable} --version`)
|
||||
|
||||
const args = ['--version']
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
const result = await execa(buildAppExecutable, args)
|
||||
|
||||
la(result.stdout, 'missing output when getting built version', result)
|
||||
|
||||
console.log('built app version', result.stdout)
|
||||
la(result.stdout.trim() === version.trim(), 'different version reported',
|
||||
result.stdout, 'from input version to build', version)
|
||||
|
||||
console.log('✅ using --version on the Cypress binary works')
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Safer konfig load for test code. The original konfig changes the
|
||||
* current working directory, thus the tests might be affected
|
||||
* unexpectedly. This function loads the konfig, but then
|
||||
* restores the current working directory.
|
||||
*
|
||||
* The tests should use this function to get `konfig` function like
|
||||
*
|
||||
* @example
|
||||
* const konfig = require('../binary/get-config')()
|
||||
*/
|
||||
const getConfig = () => {
|
||||
const cwd = process.cwd()
|
||||
const konfig = require('../../packages/server/lib/konfig')
|
||||
|
||||
// restore previous cwd in case it was changed by loading "konfig"
|
||||
process.chdir(cwd)
|
||||
|
||||
return konfig
|
||||
}
|
||||
|
||||
module.exports = getConfig
|
||||
@@ -160,6 +160,47 @@ const runFailingProjectTest = function (buildAppExecutable, e2e) {
|
||||
.then(verifyScreenshots)
|
||||
}
|
||||
|
||||
const runV8SnapshotProjectTest = function (buildAppExecutable, e2e) {
|
||||
if (shouldSkipProjectTest()) {
|
||||
console.log('skipping failing project test')
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
console.log('running v8 snapshot project test')
|
||||
|
||||
const spawn = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const env = _.omit(process.env, 'CYPRESS_INTERNAL_ENV')
|
||||
|
||||
const args = [
|
||||
`--run-project=${e2e}`,
|
||||
`--spec=${e2e}/cypress/e2e/simple_v8_snapshot.cy.js`,
|
||||
]
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
const options = {
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
}
|
||||
|
||||
return cp.spawn(buildAppExecutable, args, options)
|
||||
.on('exit', (code) => {
|
||||
if (code === 0) {
|
||||
return resolve()
|
||||
}
|
||||
|
||||
return reject(new Error(`running project tests failed with: '${code}' errors.`))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return spawn()
|
||||
}
|
||||
|
||||
const test = async function (buildAppExecutable) {
|
||||
await scaffoldCommonNodeModules()
|
||||
await Fixtures.scaffoldProject('e2e')
|
||||
@@ -168,6 +209,10 @@ const test = async function (buildAppExecutable) {
|
||||
await runSmokeTest(buildAppExecutable)
|
||||
await runProjectTest(buildAppExecutable, e2e)
|
||||
await runFailingProjectTest(buildAppExecutable, e2e)
|
||||
if (!['1', 'true'].includes(process.env.DISABLE_SNAPSHOT_REQUIRE)) {
|
||||
await runV8SnapshotProjectTest(buildAppExecutable, e2e)
|
||||
}
|
||||
|
||||
Fixtures.remove()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ const os = require('os')
|
||||
const Promise = require('bluebird')
|
||||
const { fromSSO, fromEnv } = require('@aws-sdk/credential-providers')
|
||||
|
||||
const konfig = require('../get-config')()
|
||||
const { purgeCloudflareCache } = require('./purge-cloudflare-cache')
|
||||
|
||||
const CDN_URL = 'https://cdn.cypress.io'
|
||||
|
||||
const getUploadUrl = function () {
|
||||
const url = konfig('cdn_url')
|
||||
const url = CDN_URL
|
||||
|
||||
la(check.url(url), 'could not get CDN url', url)
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ declare global {
|
||||
/**
|
||||
* Gulp is only used for running the application during development. At this point of starting the app,
|
||||
* process.env.CYPRESS_INTERNAL_ENV has not been set yet unless explicitly set on the command line. If not
|
||||
* set on the command line, it is set to 'development' [here](https://github.com/cypress-io/cypress/blob/a5ec234005fead97f6cfdf611abf8d9f4ad0565d/packages/server/lib/environment.js#L22)
|
||||
* set on the command line, it is set to 'development' [here](https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/environment.js#L22)
|
||||
*
|
||||
* When running in a production build, a file is written out to set CYPRESS_INTERNAL_ENV to 'production'
|
||||
* [here](https://github.com/cypress-io/cypress/blob/a5ec234005fead97f6cfdf611abf8d9f4ad0565d/scripts/binary/build.ts#L176).
|
||||
* [here](https://github.com/cypress-io/cypress/blob/develop/scripts/binary/build.ts#L176).
|
||||
* However, running in production will not use the code in this file.
|
||||
*/
|
||||
|
||||
@@ -34,14 +34,14 @@ export const ENV_VARS = {
|
||||
|
||||
// Uses the "built" vite assets, not the served ones
|
||||
DEV_OPEN: {
|
||||
CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig
|
||||
CYPRESS_CONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove config
|
||||
CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV,
|
||||
CYPRESS_INTERNAL_EVENT_COLLECTOR_ENV: DEFAULT_INTERNAL_EVENT_COLLECTOR_ENV,
|
||||
},
|
||||
|
||||
// Used when we're running Cypress in true "development" mode
|
||||
DEV: {
|
||||
CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig
|
||||
CYPRESS_CONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove config
|
||||
CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV,
|
||||
CYPRESS_INTERNAL_EVENT_COLLECTOR_ENV: DEFAULT_INTERNAL_EVENT_COLLECTOR_ENV,
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ export const monorepoPaths = {
|
||||
pkgLaunchpad: path.join(__dirname, '../../packages/launchpad'),
|
||||
pkgNetStubbing: path.join(__dirname, '../../packages/net-stubbing'),
|
||||
pkgNetwork: path.join(__dirname, '../../packages/network'),
|
||||
pkgPackherdRequire: path.join(__dirname, '../../packages/packherd-require'),
|
||||
pkgProxy: path.join(__dirname, '../../packages/proxy'),
|
||||
pkgReporter: path.join(__dirname, '../../packages/reporter'),
|
||||
pkgResolveDist: path.join(__dirname, '../../packages/resolve-dist'),
|
||||
@@ -32,5 +33,6 @@ export const monorepoPaths = {
|
||||
pkgSocket: path.join(__dirname, '../../packages/socket'),
|
||||
pkgTs: path.join(__dirname, '../../packages/ts'),
|
||||
pkgTypes: path.join(__dirname, '../../packages/types'),
|
||||
pkgV8SnapshotRequire: path.join(__dirname, '../../packages/v8-snapshot-require'),
|
||||
pkgWebConfig: path.join(__dirname, '../../packages/web-config')
|
||||
} as const
|
||||
|
||||
@@ -3,7 +3,7 @@ const { execSync } = require('child_process')
|
||||
const executionEnv = process.env.CI ? 'ci' : 'local'
|
||||
|
||||
const postInstallCommands = {
|
||||
local: 'patch-package && yarn-deduplicate --strategy=highest && yarn clean && gulp postinstall && yarn build',
|
||||
local: 'patch-package && yarn-deduplicate --strategy=highest && yarn clean && gulp postinstall && yarn build && yarn build-v8-snapshot-dev',
|
||||
ci: 'patch-package && yarn clean && gulp postinstall',
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
const la = require('lazy-ass')
|
||||
const is = require('check-more-types')
|
||||
const { join } = require('path')
|
||||
|
||||
/* eslint-env mocha */
|
||||
describe('konfig check', () => {
|
||||
/*
|
||||
script tests should NOT suddenly change the current working directory to
|
||||
packages/server - otherwise the local path filenames might be all wrong
|
||||
and unexpected. The current working directory changes when we
|
||||
require `packages/server/lib/konfig` which in turn requires
|
||||
`lib/cwd` which changes CWD.
|
||||
|
||||
From the scripts unit tests we should not use `lib/konfig` directly,
|
||||
instead we should use `binary/get-config` script to get the konfig function.
|
||||
*/
|
||||
|
||||
let cwd
|
||||
|
||||
before(() => {
|
||||
cwd = process.cwd()
|
||||
la(
|
||||
!cwd.includes(join('packages', 'server')),
|
||||
'process CWD is set to',
|
||||
cwd,
|
||||
'for some reason',
|
||||
)
|
||||
// if the above assertion breaks, it means some script in binary scripts
|
||||
// loads "lib/konfig" directly, which unexpectedly changes the CWD.
|
||||
})
|
||||
|
||||
it('does not change CWD on load', () => {
|
||||
const konfig = require('../binary/get-config')()
|
||||
const cwdAfter = process.cwd()
|
||||
|
||||
la(
|
||||
cwd === cwdAfter,
|
||||
'previous cwd',
|
||||
cwd,
|
||||
'differs after loading konfig',
|
||||
cwdAfter,
|
||||
)
|
||||
|
||||
la(is.fn(konfig), 'expected konfig to be a function', konfig)
|
||||
})
|
||||
})
|
||||
@@ -3,89 +3,79 @@ const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { verifyMochaResults } = require('../verify-mocha-results')
|
||||
|
||||
describe('verify-mocha-results', () => {
|
||||
let cachedEnv = { ...process.env }
|
||||
if (process.platform !== 'win32') {
|
||||
describe('verify-mocha-results', () => {
|
||||
let cachedEnv = { ...process.env }
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// skip the rest of the tests
|
||||
return it('fails on windows', async () => {
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.equal('verifyMochaResults not supported on Windows')
|
||||
}
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
Object.assign(process.env, cachedEnv)
|
||||
})
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
Object.assign(process.env, cachedEnv)
|
||||
})
|
||||
beforeEach(() => {
|
||||
process.env.CIRCLE_INTERNAL_CONFIG = '/foo.json'
|
||||
sinon.stub(fs, 'readFile')
|
||||
.withArgs('/foo.json').resolves(JSON.stringify({
|
||||
Dispatched: { TaskInfo: { Environment: { somekey: 'someval' } } },
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.CIRCLE_INTERNAL_CONFIG = '/foo.json'
|
||||
sinon.stub(fs, 'readFile')
|
||||
.withArgs('/foo.json').resolves(JSON.stringify({
|
||||
Dispatched: { TaskInfo: { Environment: { somekey: 'someval' } } },
|
||||
}))
|
||||
|
||||
sinon.stub(fs, 'readdir').withArgs('/tmp/cypress/junit').resolves([
|
||||
'report.xml',
|
||||
])
|
||||
})
|
||||
|
||||
it('does not fail with normal report', async () => {
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="10" failures="0">')
|
||||
|
||||
await verifyMochaResults()
|
||||
})
|
||||
|
||||
context('env checking', () => {
|
||||
it('checks for protected env and fails and removes results when found', async () => {
|
||||
const spy = sinon.stub(fs, 'rm').withArgs('/tmp/cypress/junit', { recursive: true, force: true })
|
||||
sinon.stub(fs, 'readdir').withArgs('/tmp/cypress/junit').resolves([
|
||||
'report.xml',
|
||||
])
|
||||
})
|
||||
|
||||
it('does not fail with normal report', async () => {
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="10" failures="0">someval')
|
||||
.resolves('<testsuites name="foo" time="1" tests="10" failures="0">')
|
||||
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.include('somekey').and.not.include('someval')
|
||||
expect(spy.getCalls().length).to.equal(1)
|
||||
}
|
||||
await verifyMochaResults()
|
||||
})
|
||||
|
||||
context('env checking', () => {
|
||||
it('checks for protected env and fails and removes results when found', async () => {
|
||||
const spy = sinon.stub(fs, 'rm').withArgs('/tmp/cypress/junit', { recursive: true, force: true })
|
||||
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="10" failures="0">someval')
|
||||
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.include('somekey').and.not.include('someval')
|
||||
expect(spy.getCalls().length).to.equal(1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
context('test result checking', () => {
|
||||
it('checks for non-passing tests and fails when found', async () => {
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="10" failures="3">')
|
||||
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.include('Expected the number of failures to be equal to 0')
|
||||
}
|
||||
})
|
||||
|
||||
it('checks for 0 tests run and fails when found', async () => {
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="0" failures="0">')
|
||||
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.include('Expected the total number of tests to be >0')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('test result checking', () => {
|
||||
it('checks for non-passing tests and fails when found', async () => {
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="10" failures="3">')
|
||||
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.include('Expected the number of failures to be equal to 0')
|
||||
}
|
||||
})
|
||||
|
||||
it('checks for 0 tests run and fails when found', async () => {
|
||||
fs.readFile
|
||||
.withArgs('/tmp/cypress/junit/report.xml')
|
||||
.resolves('<testsuites name="foo" time="1" tests="0" failures="0">')
|
||||
|
||||
try {
|
||||
await verifyMochaResults()
|
||||
throw new Error('should not reach')
|
||||
} catch (err) {
|
||||
expect(err.message).to.include('Expected the total number of tests to be >0')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ async function checkReportFiles (filenames) {
|
||||
}
|
||||
|
||||
async function verifyMochaResults () {
|
||||
if (process.platform === 'win32') throw new Error('verifyMochaResults not supported on Windows')
|
||||
|
||||
try {
|
||||
const filenames = await fs.readdir(REPORTS_PATH)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user