diff --git a/server-ce/test/Makefile b/server-ce/test/Makefile index 4f4bf838db..fc773956f4 100644 --- a/server-ce/test/Makefile +++ b/server-ce/test/Makefile @@ -9,6 +9,8 @@ export PWD = $(shell pwd) export TEX_LIVE_DOCKER_IMAGE ?= quay.io/sharelatex/texlive-full:2023.1 export ALL_TEX_LIVE_DOCKER_IMAGES ?= quay.io/sharelatex/texlive-full:2023.1,quay.io/sharelatex/texlive-full:2022.1 export IMAGE_TAG_PRO ?= quay.io/sharelatex/sharelatex-pro:latest +export CYPRESS_SHARD ?= +export COMPOSE_PROJECT_NAME ?= test test-e2e-native: docker compose -f docker-compose.yml -f docker-compose.native.yml up --build --no-log-prefix sharelatex host-admin -d @@ -23,11 +25,27 @@ test-e2e-open: clean: docker compose down --volumes --timeout 0 -prefetch: - docker compose pull e2e mongo redis saml ldap +prefetch: prefetch_default +prefetch_default: prefetch_default_compose +prefetch_default_compose: + docker compose pull e2e mongo redis + +prefetch_default: prefetch_default_compose_build +prefetch_default_compose_build: docker compose build + +prefetch: prefetch_custom +prefetch_custom: prefetch_custom_compose_pull +prefetch_custom_compose_pull: + docker compose pull saml ldap + +prefetch_custom: prefetch_custom_texlive +prefetch_custom_texlive: echo -n "$$ALL_TEX_LIVE_DOCKER_IMAGES" | xargs -d, -I% \ sh -exc 'tag=%; re_tag=quay.io/sharelatex/$${tag#*/}; docker pull $$tag; docker tag $$tag $$re_tag' + +prefetch_custom: prefetch_old +prefetch_old: docker pull $(IMAGE_TAG_PRO:latest=4.2) docker pull $(IMAGE_TAG_PRO:latest=5.0.1-RC1) docker pull $(IMAGE_TAG_PRO:latest=5.0) diff --git a/server-ce/test/accounts.spec.ts b/server-ce/test/accounts.spec.ts index 6e96af3a5f..754f80e71e 100644 --- a/server-ce/test/accounts.spec.ts +++ b/server-ce/test/accounts.spec.ts @@ -1,7 +1,8 @@ import { createMongoUser, ensureUserExists, login } from './helpers/login' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' describe('Accounts', function () { + if (isExcludedBySharding('CE_DEFAULT')) return startWith({}) ensureUserExists({ email: 'user@example.com' }) diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index 7ee079010f..2b17504e92 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -1,4 +1,4 @@ -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { activateUser, ensureUserExists, login } from './helpers/login' import { v4 as uuid } from 'uuid' import { createProject } from './helpers/project' @@ -20,6 +20,7 @@ describe('admin panel', function () { return cy.findByText(projectName).parent().parent() } + if (isExcludedBySharding('PRO_DEFAULT_2')) return startWith({ pro: true, }) diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index ab91390f2f..1bfcfa999a 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -1,9 +1,10 @@ import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { throttledRecompile } from './helpers/compile' describe('Project creation and compilation', function () { + if (isExcludedBySharding('CE_DEFAULT')) return startWith({}) ensureUserExists({ email: 'user@example.com' }) ensureUserExists({ email: 'collaborator@example.com' }) diff --git a/server-ce/test/customization.spec.ts b/server-ce/test/customization.spec.ts index b9c566e827..03c9bc3a5a 100644 --- a/server-ce/test/customization.spec.ts +++ b/server-ce/test/customization.spec.ts @@ -1,6 +1,7 @@ -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' describe('Customization', () => { + if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ vars: { OVERLEAF_APP_NAME: 'CUSTOM APP NAME', diff --git a/server-ce/test/cypress.config.js b/server-ce/test/cypress.config.js index 689cf5ce03..07ea87bc83 100644 --- a/server-ce/test/cypress.config.js +++ b/server-ce/test/cypress.config.js @@ -23,6 +23,6 @@ module.exports = defineConfig({ specPattern, }, retries: { - runMode: 1, + runMode: 3, }, }) diff --git a/server-ce/test/docker-compose.yml b/server-ce/test/docker-compose.yml index bd6b255710..8ac6930c7f 100644 --- a/server-ce/test/docker-compose.yml +++ b/server-ce/test/docker-compose.yml @@ -57,6 +57,7 @@ services: volumes: - ./:/e2e environment: + CYPRESS_SHARD: CYPRESS_BASE_URL: http://sharelatex SPEC_PATTERN: '**/*.spec.{js,jsx,ts,tsx}' depends_on: @@ -76,6 +77,7 @@ services: - /tmp/.X11-unix:/tmp/.X11-unix user: "${DOCKER_USER:-1000:1000}" environment: + CYPRESS_SHARD: CYPRESS_BASE_URL: http://sharelatex SPEC_PATTERN: '**/*.spec.{js,jsx,ts,tsx}' DISPLAY: ${DISPLAY:-:0} @@ -96,6 +98,8 @@ services: stop_grace_period: 0s environment: PWD: + CYPRESS_SHARD: + COMPOSE_PROJECT_NAME: TEX_LIVE_DOCKER_IMAGE: ALL_TEX_LIVE_DOCKER_IMAGES: IMAGE_TAG_CE: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest} diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index cc7378fabe..41c1b06b65 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -1,10 +1,11 @@ import { createProject } from './helpers/project' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { v4 as uuid } from 'uuid' import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' describe('editor', () => { + if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true }) ensureUserExists({ email: 'user@example.com' }) ensureUserExists({ email: 'collaborator@example.com' }) diff --git a/server-ce/test/external-auth.spec.ts b/server-ce/test/external-auth.spec.ts index fae6945ccd..e3608fadcd 100644 --- a/server-ce/test/external-auth.spec.ts +++ b/server-ce/test/external-auth.spec.ts @@ -1,6 +1,7 @@ -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' describe('SAML', () => { + if (isExcludedBySharding('PRO_CUSTOM_1')) return const overleafPublicHost = Cypress.env('OVERLEAF_PUBLIC_HOST') || 'sharelatex' const samlPublicHost = Cypress.env('SAML_PUBLIC_HOST') || 'saml' @@ -36,6 +37,7 @@ describe('SAML', () => { }) describe('LDAP', () => { + if (isExcludedBySharding('PRO_CUSTOM_1')) return startWith({ pro: true, vars: { diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index e6a07e75d1..cb254edeca 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -1,4 +1,4 @@ -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' @@ -19,6 +19,7 @@ describe('git-bridge', function () { Cypress.env('GIT_BRIDGE_PUBLIC_HOST') || 'sharelatex' describe('enabled in Server Pro', function () { + if (isExcludedBySharding('PRO_CUSTOM_1')) return startWith({ pro: true, vars: ENABLED_VARS, @@ -272,6 +273,7 @@ Hello world } describe('disabled in Server Pro', () => { + if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true, }) @@ -279,6 +281,7 @@ Hello world }) describe('unavailable in CE', () => { + if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, vars: ENABLED_VARS, diff --git a/server-ce/test/graceful-shutdown.spec.ts b/server-ce/test/graceful-shutdown.spec.ts index 61dc3f69e1..8201b55b76 100644 --- a/server-ce/test/graceful-shutdown.spec.ts +++ b/server-ce/test/graceful-shutdown.spec.ts @@ -1,14 +1,10 @@ +import { ensureUserExists, login } from './helpers/login' import { - ensureUserExists, - login, - resetCreatedUsersCache, -} from './helpers/login' -import { STARTUP_TIMEOUT, startWith } from './helpers/config' -import { - dockerCompose, - getRedisKeys, - resetData, -} from './helpers/hostAdminClient' + isExcludedBySharding, + STARTUP_TIMEOUT, + startWith, +} from './helpers/config' +import { dockerCompose, getRedisKeys } from './helpers/hostAdminClient' import { createProject } from './helpers/project' import { throttledRecompile } from './helpers/compile' @@ -23,13 +19,11 @@ function bringServerProBackUp() { } describe('GracefulShutdown', function () { - before(async () => { - resetCreatedUsersCache() - await resetData() - }) + if (isExcludedBySharding('PRO_CUSTOM_1')) return startWith({ pro: true, withDataDir: true, + resetData: true, }) ensureUserExists({ email: USER }) diff --git a/server-ce/test/helpers/config.ts b/server-ce/test/helpers/config.ts index e7a59378f4..1cd86db564 100644 --- a/server-ce/test/helpers/config.ts +++ b/server-ce/test/helpers/config.ts @@ -1,8 +1,24 @@ import { reconfigure } from './hostAdminClient' +import { resetCreatedUsersCache } from './login' export const STARTUP_TIMEOUT = parseInt(Cypress.env('STARTUP_TIMEOUT'), 10) || 120_000 +export function isExcludedBySharding( + shard: + | 'CE_DEFAULT' + | 'CE_CUSTOM_1' + | 'CE_CUSTOM_2' + | 'PRO_DEFAULT_1' + | 'PRO_DEFAULT_2' + | 'PRO_CUSTOM_1' + | 'PRO_CUSTOM_2' + | 'PRO_CUSTOM_3' +) { + const SHARD = Cypress.env('SHARD') + return SHARD && shard !== SHARD +} + let lastConfig: string export function startWith({ @@ -11,6 +27,7 @@ export function startWith({ vars = {}, varsFn = () => ({}), withDataDir = false, + resetData = false, }) { before(async function () { Object.assign(vars, varsFn()) @@ -20,10 +37,15 @@ export function startWith({ vars, withDataDir, }) - if (lastConfig === cfg) return + if (resetData) { + resetCreatedUsersCache() + // no return here, always reconfigure when resetting data + } else if (lastConfig === cfg) { + return + } this.timeout(STARTUP_TIMEOUT) - await reconfigure({ pro, version, vars, withDataDir }) + await reconfigure({ pro, version, vars, withDataDir, resetData }) lastConfig = cfg }) } diff --git a/server-ce/test/helpers/hostAdminClient.ts b/server-ce/test/helpers/hostAdminClient.ts index e5fcdd0eb9..8d303d31bd 100644 --- a/server-ce/test/helpers/hostAdminClient.ts +++ b/server-ce/test/helpers/hostAdminClient.ts @@ -9,23 +9,12 @@ export async function dockerCompose(cmd: string, ...args: string[]) { }) } -export async function mongoInit() { - return await fetchJSON(`${hostAdminUrl}/mongo/init`, { - method: 'POST', - }) -} - -export async function resetData() { - return await fetchJSON(`${hostAdminUrl}/reset/data`, { - method: 'POST', - }) -} - export async function reconfigure({ pro = false, version = 'latest', vars = {}, withDataDir = false, + resetData = false, }) { return await fetchJSON(`${hostAdminUrl}/reconfigure`, { method: 'POST', @@ -34,6 +23,7 @@ export async function reconfigure({ version, vars, withDataDir, + resetData, }), }) } diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts index 9f7e6c2bc2..6c55a25311 100644 --- a/server-ce/test/history.spec.ts +++ b/server-ce/test/history.spec.ts @@ -1,9 +1,10 @@ import { createProject } from './helpers/project' import { throttledRecompile } from './helpers/compile' import { ensureUserExists, login } from './helpers/login' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' describe('History', function () { + if (isExcludedBySharding('CE_DEFAULT')) return startWith({}) ensureUserExists({ email: 'user@example.com' }) beforeEach(function () { diff --git a/server-ce/test/host-admin.js b/server-ce/test/host-admin.js index 96201bf2e7..1a56b07df6 100644 --- a/server-ce/test/host-admin.js +++ b/server-ce/test/host-admin.js @@ -10,12 +10,19 @@ const { } = require('celebrate') const YAML = require('js-yaml') +const DATA_DIR = Path.join( + __dirname, + 'data', + // Give each shard their own data dir. + process.env.CYPRESS_SHARD || 'default' +) const PATHS = { DOCKER_COMPOSE_FILE: 'docker-compose.yml', - DOCKER_COMPOSE_OVERRIDE: 'docker-compose.override.yml', + // Give each shard their own override file. + DOCKER_COMPOSE_OVERRIDE: `docker-compose.${process.env.CYPRESS_SHARD || 'override'}.yml`, DOCKER_COMPOSE_NATIVE: 'docker-compose.native.yml', - DATA_DIR: Path.join(__dirname, 'data'), - SANDBOXED_COMPILES_HOST_DIR: Path.join(__dirname, 'data/compiles'), + DATA_DIR, + SANDBOXED_COMPILES_HOST_DIR: Path.join(DATA_DIR, 'compiles'), } const IMAGES = { CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''), @@ -245,7 +252,8 @@ app.post( } ) -function mongoInit(callback) { +function maybeMongoInit(mongoInit, callback) { + if (!mongoInit) return callback() runDockerCompose( 'up', ['--detach', '--wait', 'mongo'], @@ -271,11 +279,30 @@ function mongoInit(callback) { ) } -app.post('/mongo/init', (req, res) => { - mongoInit((error, stdout, stderr) => { - res.json({ error, stdout, stderr }) - }) -}) +function maybeResetData(resetData, callback) { + if (!resetData) return callback() + + runDockerCompose( + 'stop', + ['--timeout=0', 'sharelatex'], + (error, stdout, stderr) => { + if (error) return callback(error, stdout, stderr) + + try { + purgeDataDir() + } catch (error) { + return callback(error) + } + + mongoIsInitialized = false + runDockerCompose( + 'down', + ['--timeout=0', '--volumes', 'mongo', 'redis'], + callback + ) + } + ) +} app.post( '/reconfigure', @@ -286,56 +313,35 @@ app.post( version: Joi.string().required(), vars: allowedVars, withDataDir: Joi.boolean().optional(), + resetData: Joi.boolean().optional(), }, }, { allowUnknown: false } ), (req, res) => { - const { pro, version, vars, withDataDir } = req.body - try { - setVarsDockerCompose({ pro, version, vars, withDataDir }) - } catch (error) { - return res.json({ error }) - } - - const doMongoInit = mongoIsInitialized ? cb => cb() : mongoInit - doMongoInit((error, stdout, stderr) => { - if (error) return res.json({ error, stdout, stderr }) - runDockerCompose( - 'up', - ['--detach', '--wait', 'sharelatex'], - (error, stdout, stderr) => { - res.json({ error, stdout, stderr }) - } - ) - }) - } -) - -app.post('/reset/data', (req, res) => { - runDockerCompose( - 'stop', - ['--timeout=0', 'sharelatex'], - (error, stdout, stderr) => { + const { pro, version, vars, withDataDir, resetData } = req.body + maybeResetData(resetData, (error, stdout, stderr) => { if (error) return res.json({ error, stdout, stderr }) try { - purgeDataDir() + setVarsDockerCompose({ pro, version, vars, withDataDir }) } catch (error) { return res.json({ error }) } - mongoIsInitialized = false - runDockerCompose( - 'down', - ['--timeout=0', '--volumes', 'mongo', 'redis'], - (error, stdout, stderr) => { - res.json({ error, stdout, stderr }) - } - ) - } - ) -}) + maybeMongoInit(!mongoIsInitialized, (error, stdout, stderr) => { + if (error) return res.json({ error, stdout, stderr }) + runDockerCompose( + 'up', + ['--detach', '--wait', 'sharelatex'], + (error, stdout, stderr) => { + res.json({ error, stdout, stderr }) + } + ) + }) + }) + } +) app.get('/redis/keys', (req, res) => { runDockerCompose( @@ -352,7 +358,7 @@ app.use(handleValidationErrors()) purgeDataDir() // Init on startup -mongoInit(err => { +maybeMongoInit(true, err => { if (err) { console.error('mongo init failed', err) process.exit(1) diff --git a/server-ce/test/learn-wiki.spec.ts b/server-ce/test/learn-wiki.spec.ts index aff62c0250..c0cc8729fe 100644 --- a/server-ce/test/learn-wiki.spec.ts +++ b/server-ce/test/learn-wiki.spec.ts @@ -1,4 +1,4 @@ -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { v4 as uuid } from 'uuid' @@ -16,6 +16,7 @@ describe('LearnWiki', function () { ensureUserExists({ email: REGULAR_USER }) describe('enabled in Pro', () => { + if (isExcludedBySharding('PRO_CUSTOM_2')) return startWith({ pro: true, vars: { @@ -64,11 +65,13 @@ describe('LearnWiki', function () { }) describe('disabled in Pro', () => { + if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true }) checkDisabled() }) describe('unavailable in CE', () => { + if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, vars: { diff --git a/server-ce/test/project-list.spec.ts b/server-ce/test/project-list.spec.ts index 54992f0751..e144b147d3 100644 --- a/server-ce/test/project-list.spec.ts +++ b/server-ce/test/project-list.spec.ts @@ -1,12 +1,13 @@ import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { v4 as uuid } from 'uuid' const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com' const REGULAR_USER = 'user@example.com' describe('Project List', () => { + if (isExcludedBySharding('PRO_DEFAULT_2')) return startWith({ pro: true }) const findProjectRow = (projectName: string) => { diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index 63318e9b31..3f2ec7d553 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -1,11 +1,12 @@ import { v4 as uuid } from 'uuid' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' import { throttledRecompile } from './helpers/compile' import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' describe('Project Sharing', function () { + if (isExcludedBySharding('CE_CUSTOM_2')) return ensureUserExists({ email: 'user@example.com' }) startWith({ withDataDir: true }) diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index 69e6292b27..3b158e0166 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -1,6 +1,6 @@ import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { throttledRecompile } from './helpers/compile' import { v4 as uuid } from 'uuid' import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished' @@ -19,6 +19,7 @@ describe('SandboxedCompiles', function () { } describe('enabled in Server Pro', () => { + if (isExcludedBySharding('PRO_CUSTOM_2')) return startWith({ pro: true, vars: enabledVars, @@ -215,6 +216,7 @@ describe('SandboxedCompiles', function () { } describe('disabled in Server Pro', () => { + if (isExcludedBySharding('PRO_DEFAULT_2')) return startWith({ pro: true }) checkUsesDefaultCompiler() @@ -223,6 +225,7 @@ describe('SandboxedCompiles', function () { }) describe.skip('unavailable in CE', () => { + if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, vars: enabledVars }) checkUsesDefaultCompiler() diff --git a/server-ce/test/templates.spec.ts b/server-ce/test/templates.spec.ts index e04f07539e..4f5cc515cd 100644 --- a/server-ce/test/templates.spec.ts +++ b/server-ce/test/templates.spec.ts @@ -1,4 +1,4 @@ -import { startWith } from './helpers/config' +import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' @@ -32,6 +32,7 @@ describe('Templates', () => { } describe('enabled in Server Pro', () => { + if (isExcludedBySharding('PRO_CUSTOM_2')) return startWith({ pro: true, varsFn, @@ -238,11 +239,13 @@ describe('Templates', () => { } describe('disabled Server Pro', () => { + if (isExcludedBySharding('PRO_DEFAULT_2')) return startWith({ pro: true }) checkDisabled() }) describe('unavailable in CE', () => { + if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, varsFn, diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index ae242fc085..f22a4417da 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -1,10 +1,6 @@ -import { - ensureUserExists, - login, - resetCreatedUsersCache, -} from './helpers/login' -import { startWith } from './helpers/config' -import { dockerCompose, resetData, runScript } from './helpers/hostAdminClient' +import { ensureUserExists, login } from './helpers/login' +import { isExcludedBySharding, startWith } from './helpers/config' +import { dockerCompose, runScript } from './helpers/hostAdminClient' import { createProject } from './helpers/project' import { throttledRecompile } from './helpers/compile' import { v4 as uuid } from 'uuid' @@ -13,6 +9,8 @@ const USER = 'user@example.com' const PROJECT_NAME = 'Old Project' describe('Upgrading', function () { + if (isExcludedBySharding('PRO_CUSTOM_3')) return + function testUpgrade( steps: { version: string @@ -24,16 +22,13 @@ describe('Upgrading', function () { const startOptions = steps.shift()! before(async () => { - cy.log('Reset mongo/redis/on-disk data') - resetCreatedUsersCache() - await resetData() - cy.log('Create old instance') }) startWith({ pro: true, version: startOptions.version, withDataDir: true, + resetData: true, vars: startOptions.vars, }) before(function () {