diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 96bce88839..31153a64d6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,7 +1,7 @@ name: 'Run tests' on: pull_request: - branches: [main] + branches: [not_main] # do not run for now schedule: - cron: '0 6 * * *' # Run every day at 6am UTC workflow_dispatch: diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 1e9d847535..17566b68c8 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -27,7 +27,7 @@ env: # In older versions of Next.js both of these need to be set to enable junit reporting DATADOG_TRACE_NEXTJS_TEST: true DATADOG_API_KEY: foo - TEST_CONCURRENCY: 2 + TEST_CONCURRENCY: 1 NEXT_E2E_TEST_TIMEOUT: 300000 NEXT_TELEMETRY_DISABLED: 1 NEXT_SKIP_NATIVE_POSTINSTALL: 1 @@ -59,16 +59,16 @@ jobs: run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then VERSION_SELECTORS=[${{ github.event.inputs.versions }}] - echo "group=[1, 2, 3, 4, 5, 6, 7, 8]" >> $GITHUB_OUTPUT - echo "total=8" >> $GITHUB_OUTPUT + echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" >> $GITHUB_OUTPUT + echo "total=16" >> $GITHUB_OUTPUT elif [ "${{ github.event_name }}" == "pull_request" ]; then VERSION_SELECTORS=[\"latest\"] - echo "group=[1, 2, 3, 4, 5, 6, 7, 8]" >> $GITHUB_OUTPUT - echo "total=8" >> $GITHUB_OUTPUT + echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" >> $GITHUB_OUTPUT + echo "total=16" >> $GITHUB_OUTPUT else VERSION_SELECTORS=[\"latest\",\"canary\"] - echo "group=[1, 2, 3, 4, 5, 6, 7, 8]" >> $GITHUB_OUTPUT - echo "total=8" >> $GITHUB_OUTPUT + echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" >> $GITHUB_OUTPUT + echo "total=16" >> $GITHUB_OUTPUT fi VERSION_SPEC="[" @@ -205,6 +205,7 @@ jobs: # one job may wait for deploys in other jobs (only one deploy may be in progress for # a given alias at a time), resulting in cascading timeouts. DEPLOY_ALIAS: vercel-next-e2e-${{ matrix.version_spec.selector }}-${{ matrix.group }} + # NODE_OPTIONS: "--require /home/runner/work/opennextjs-netlify/opennextjs-netlify/opennextjs-netlify/skip-integrations.cjs" run: node run-tests.js -g ${{ matrix.group }}/${{ needs.setup.outputs.total }} -c ${TEST_CONCURRENCY} --type e2e working-directory: ${{ env.next-path }} diff --git a/skip-integrations.cjs b/skip-integrations.cjs new file mode 100644 index 0000000000..42829410c5 --- /dev/null +++ b/skip-integrations.cjs @@ -0,0 +1,17 @@ +const { http, HttpResponse, passthrough } = require('msw') +// eslint-disable-next-line import/extensions +const { setupServer } = require('msw/node') + +const server = setupServer( + http.get( + 'https://api.netlifysdk.com/team/:accountId/integrations/installations/meta/:siteId', + () => { + return HttpResponse.json([]) + }, + ), + http.get('https://api.netlifysdk.com/site/:siteId/integrations/safe', () => { + return HttpResponse.json([]) + }), + http.all(/.*/, () => passthrough()), +) +server.listen() diff --git a/tests/netlify-deploy.ts b/tests/netlify-deploy.ts index 61ad84e836..a4e065cb02 100644 --- a/tests/netlify-deploy.ts +++ b/tests/netlify-deploy.ts @@ -23,6 +23,7 @@ async function packNextRuntimeImpl() { } let packNextRuntimePromise: ReturnType | null = null +let nextRuntimePacked = false function packNextRuntime() { if (!packNextRuntimePromise) { packNextRuntimePromise = packNextRuntimeImpl() @@ -38,6 +39,8 @@ export class NextDeployInstance extends NextInstance { private _shouldDeleteDeploy: boolean = false private _isCurrentlyDeploying: boolean = false private _deployOutput: string = '' + private _setupStartTime = Date.now() + private _intervalsToClear: NodeJS.Timeout[] = [] public get buildId() { // get deployment ID via fetch since we can't access @@ -45,6 +48,49 @@ export class NextDeployInstance extends NextInstance { return this._buildId } + private packNextRuntime() { + if (!packNextRuntimePromise) { + if (!nextRuntimePacked) { + this._deployOutput += this.getTimestampPrefix() + 'Pack Next Runtime ...\n' + } + packNextRuntimePromise = packNextRuntimeImpl() + packNextRuntimePromise.then(() => { + nextRuntimePacked = true + }) + if (!nextRuntimePacked) { + this._deployOutput += this.getTimestampPrefix() + 'Pack Next Runtime DONE\n' + } + } + + return packNextRuntimePromise + } + + private clearIntervals() { + for (const interval of this._intervalsToClear) { + clearInterval(interval) + } + this._intervalsToClear = [] + } + + private getTimestampPrefix() { + return `[${new Date().toISOString()}] (+${((Date.now() - this._setupStartTime) / 1000).toFixed(3)}s) ` + } + + private ps(pid) { + const netlifyStatusPromise = execa('ps', ['-p', pid]) + + netlifyStatusPromise.stdout.on('data', this.handleOutput.bind(this)) + netlifyStatusPromise.stderr.on('data', this.handleOutput.bind(this)) + } + + private handleOutput(chunk) { + const timestampPrefix = this.getTimestampPrefix() + + this._deployOutput += + (this._deployOutput === '' || this._deployOutput.endsWith('\n') ? timestampPrefix : '') + + chunk.toString().replace(/\n(?=.)/gm, `\n${timestampPrefix}`) + } + public async setup(parentSpan: Span) { if (process.env.SITE_URL && process.env.BUILD_ID) { require('console').log('Using existing deployment: ' + process.env.SITE_URL) @@ -56,10 +102,10 @@ export class NextDeployInstance extends NextInstance { this._isCurrentlyDeploying = true - const setupStartTime = Date.now() - + this._deployOutput += this.getTimestampPrefix() + 'Setting up test dir ...\n' // create the test site await super.createTestDir({ parentSpan, skipInstall: true }) + this._deployOutput += this.getTimestampPrefix() + 'Setting up test dir DONE\n' // If the test fixture has node modules we need to move them aside then merge them in after @@ -67,24 +113,34 @@ export class NextDeployInstance extends NextInstance { const nodeModulesBak = `${nodeModules}.bak` if (fs.existsSync(nodeModules)) { + this._deployOutput += this.getTimestampPrefix() + 'Rename node_modules ...\n' await fs.rename(nodeModules, nodeModulesBak) + this._deployOutput += this.getTimestampPrefix() + 'Rename node_modules DONE\n' } - const { runtimePackageName, runtimePackageTarballPath } = await packNextRuntime() + const { runtimePackageName, runtimePackageTarballPath } = await this.packNextRuntime() // install dependencies - await execa('npm', ['i', runtimePackageTarballPath, '--legacy-peer-deps'], { + this._deployOutput += this.getTimestampPrefix() + 'Install dependencies ...\n' + const installResPromise = execa('npm', ['i', runtimePackageTarballPath, '--legacy-peer-deps'], { cwd: this.testDir, - stdio: 'inherit', }) + installResPromise.stdout.on('data', this.handleOutput.bind(this)) + installResPromise.stderr.on('data', this.handleOutput.bind(this)) + + await installResPromise + this._deployOutput += this.getTimestampPrefix() + 'Install dependencies DONE\n' + if (fs.existsSync(nodeModulesBak)) { // move the contents of the fixture node_modules into the installed modules + this._deployOutput += this.getTimestampPrefix() + 'Move fixture node_modules ...\n' for (const file of await fs.readdir(nodeModulesBak)) { await fs.move(path.join(nodeModulesBak, file), path.join(nodeModules, file), { overwrite: true, }) } + this._deployOutput += this.getTimestampPrefix() + 'Move fixture node_modules DONE\n' } // use next runtime package installed by the test runner @@ -117,7 +173,12 @@ export class NextDeployInstance extends NextInstance { // ensure project is linked try { - await execa('npx', ['netlify', 'status', '--json']) + const netlifyStatusPromise = execa('npx', ['netlify', 'status', '--json']) + + netlifyStatusPromise.stdout.on('data', this.handleOutput.bind(this)) + netlifyStatusPromise.stderr.on('data', this.handleOutput.bind(this)) + + await netlifyStatusPromise } catch (err) { if (err.message.includes("You don't appear to be in a folder that is linked to a site")) { throw new Error(`Site is not linked. Please set "NETLIFY_AUTH_TOKEN" and "NETLIFY_SITE_ID"`) @@ -144,15 +205,75 @@ export class NextDeployInstance extends NextInstance { }, ) - const handleOutput = (chunk) => { - this._deployOutput += chunk - } + this._deployOutput += + this.getTimestampPrefix() + `Started deploy, PID: ${deployResPromise.pid}\n` + require('console').log(`Started deploy, PID: ${deployResPromise.pid}`) + + deployResPromise.stdout.on('data', this.handleOutput.bind(this)) + deployResPromise.stderr.on('data', this.handleOutput.bind(this)) + + deployResPromise.on('error', (err) => { + this._deployOutput += this.getTimestampPrefix() + `Error during deployment: ${err.message}\n` + require('console').error(`Error during deployment: ${err.message}`) + }) + + deployResPromise.on('spawn', (err) => { + this._deployOutput += this.getTimestampPrefix() + `Process spawned\n` + require('console').error(`Process spawned`) + }) + + deployResPromise.on('disconnect', (err) => { + this._deployOutput += this.getTimestampPrefix() + `Process disconnected\n` + require('console').error(`Process disconnected`) + }) - deployResPromise.stdout.on('data', handleOutput) - deployResPromise.stderr.on('data', handleOutput) + deployResPromise.on('close', (code, signal) => { + this._deployOutput += + this.getTimestampPrefix() + `Process closed with code: ${code} / signal: ${signal}\n` + require('console').error(`Process closed with code: ${code} / signal: ${signal}`) + }) + + deployResPromise.on('exit', (code, signal) => { + this._deployOutput += + this.getTimestampPrefix() + `Process exited with code: ${code} / signal: ${signal}\n` + require('console').error(`Process exited with code: ${code} / signal: ${signal}`) + }) + + this._intervalsToClear.push( + setInterval(() => { + this._deployOutput += + this.getTimestampPrefix() + + `Waiting for netlify deploy process to finish ... (killed: ${deployResPromise.killed}, connected: ${deployResPromise.connected})\n` + }, 5000), + ) + + this._intervalsToClear.push( + setInterval(() => { + this.ps(deployResPromise.pid) + }, 30_000), + ) + + deployResPromise + .then((result) => { + require('console').log(`Netlify deploy process finished.`) + this._deployOutput += this.getTimestampPrefix() + 'Netlify deploy process finished.\n' + }) + .catch((err) => { + require('console').log(`Netlify deploy process failed. ` + err) + this._deployOutput += this.getTimestampPrefix() + 'Netlify deploy process failed. ' + err + }) + .finally(() => { + require('console').log(`Netlify deploy process finally.`) + this._deployOutput += this.getTimestampPrefix() + 'Netlify deploy process finally.\n' + this.clearIntervals() + }) const deployRes = await deployResPromise + this.clearIntervals() + + require('console').log(`Deploy finished. Processing output...`) + if (deployRes.exitCode !== 0) { // clear deploy output to avoid printing it again in destroy() this._deployOutput = '' @@ -184,7 +305,7 @@ export class NextDeployInstance extends NextInstance { } } catch (err) { require('console').error(err) - throw new Error(`Failed to parse deploy output: ${deployRes.stdout}`) + throw new Error(`Failed to parse deploy output: "${deployRes.stdout}"`) } this._buildId = ( @@ -195,12 +316,13 @@ export class NextDeployInstance extends NextInstance { ).trim() require('console').log(`Got buildId: ${this._buildId}`) - require('console').log(`Setup time: ${(Date.now() - setupStartTime) / 1000.0}s`) + require('console').log(`Setup time: ${(Date.now() - this._setupStartTime) / 1000.0}s`) this._isCurrentlyDeploying = false } public async destroy(): Promise { + this.clearIntervals() if (this._shouldDeleteDeploy) { require('console').log(`Deleting project with deploy_id ${this._deployId}`)