diff --git a/.github/workflows/acme-ci.yml b/.github/workflows/acme-ci.yml index 0f1adca..2914783 100644 --- a/.github/workflows/acme-ci.yml +++ b/.github/workflows/acme-ci.yml @@ -76,8 +76,8 @@ jobs: run: npm run test:integration env: DATABASE_URL: postgresql://acme:acme@localhost:55432/acme_test - DB_STRESS_WORKERS: auto - DB_POOL_SIZE: 4 + INTEGRATION_WORKERS: auto + APP_DB_POOL_SIZE: 4 docker-build: name: Docker build (${{ matrix.platform }}) diff --git a/solutions-engineering/migration-lab/CLAUDE.md b/solutions-engineering/migration-lab/CLAUDE.md new file mode 100644 index 0000000..ff300c0 --- /dev/null +++ b/solutions-engineering/migration-lab/CLAUDE.md @@ -0,0 +1,10 @@ +# Solutions Engineering Migration Lab + +This directory is the scope of the Solutions Engineering migration lab. + +When using an AI coding tool for this exercise: + +- Work from `solutions-engineering/migration-lab` and `.github/workflows/acme-ci.yml`. +- Do not inspect or run other interview exercises in this repository unless the interviewer explicitly asks you to. +- Prefer evidence from workflow files, CI logs, Blacksmith Run History, runner metrics, and SSH debugging over broad repository exploration. +- Keep changes focused on the migration lab. diff --git a/solutions-engineering/migration-lab/app/package.json b/solutions-engineering/migration-lab/app/package.json index ff7e9e3..def65ae 100644 --- a/solutions-engineering/migration-lab/app/package.json +++ b/solutions-engineering/migration-lab/app/package.json @@ -7,7 +7,7 @@ "build": "node scripts/build.js", "lint": "node scripts/lint.js", "test:unit": "node scripts/shard-tests.js", - "test:integration": "node scripts/integration-db-stress.js", + "test:integration": "node scripts/run-integration-suite.js", "docker:arch-check": "node scripts/docker-arch-check.js", "profile:idle": "node scripts/resource-profile.js idle", "profile:cpu": "node scripts/resource-profile.js cpu" diff --git a/solutions-engineering/migration-lab/app/scripts/lint.js b/solutions-engineering/migration-lab/app/scripts/lint.js index 634f973..5493c03 100644 --- a/solutions-engineering/migration-lab/app/scripts/lint.js +++ b/solutions-engineering/migration-lab/app/scripts/lint.js @@ -5,7 +5,7 @@ const files = [ path.join(__dirname, '..', 'src', 'server.js'), path.join(__dirname, 'build.js'), path.join(__dirname, 'shard-tests.js'), - path.join(__dirname, 'integration-db-stress.js'), + path.join(__dirname, 'run-integration-suite.js'), path.join(__dirname, 'docker-arch-check.js'), path.join(__dirname, 'docker-build-workload.js'), path.join(__dirname, 'resource-profile.js'), diff --git a/solutions-engineering/migration-lab/app/scripts/integration-db-stress.js b/solutions-engineering/migration-lab/app/scripts/run-integration-suite.js similarity index 66% rename from solutions-engineering/migration-lab/app/scripts/integration-db-stress.js rename to solutions-engineering/migration-lab/app/scripts/run-integration-suite.js index c7e2028..98eeea4 100644 --- a/solutions-engineering/migration-lab/app/scripts/integration-db-stress.js +++ b/solutions-engineering/migration-lab/app/scripts/run-integration-suite.js @@ -1,86 +1,85 @@ -const os = require('os'); -const { Pool } = require('pg'); - -function parseWorkers() { - const configured = process.env.DB_STRESS_WORKERS || 'auto'; - if (configured === 'auto') { - return Math.max(4, os.cpus().length * 3); - } - - const workers = Number(configured); - if (!Number.isInteger(workers) || workers < 1) { - console.error('invalid DB_STRESS_WORKERS value'); - process.exit(1); - } - - return workers; -} - -function connectionString() { - return process.env.DATABASE_URL || 'postgresql://acme:acme@localhost:55432/acme_test'; -} - -async function runWorker(workerId, poolSize) { - const pool = new Pool({ - connectionString: connectionString(), - max: poolSize, - connectionTimeoutMillis: 1500, - idleTimeoutMillis: 1000, - }); - - try { - await Promise.all( - Array.from({ length: poolSize }, async () => { - await pool.query('select pg_sleep(0.4)'); - }), - ); - await pool.query('insert into test_events (worker_id) values ($1)', [workerId]); - return { workerId, ok: true }; - } finally { - await pool.end(); - } -} - -async function prepareDatabase() { - const pool = new Pool({ - connectionString: connectionString(), - max: 1, - connectionTimeoutMillis: 1500, - idleTimeoutMillis: 1000, - }); - - try { - await pool.query('create table if not exists test_events (id serial primary key, worker_id int, created_at timestamptz default now())'); - } finally { - await pool.end(); - } -} - -async function main() { - const workers = parseWorkers(); - const poolSize = Number(process.env.DB_POOL_SIZE || 4); - - console.log(`starting integration suite with workers=${workers} pool_size=${poolSize}`); - console.log(`detected_cpu_count=${os.cpus().length}`); - - await prepareDatabase(); - - const results = await Promise.allSettled( - Array.from({ length: workers }, (_, index) => runWorker(index + 1, poolSize)), - ); - - const failures = results.filter((result) => result.status === 'rejected'); - - if (failures.length > 0) { - console.error(`integration suite failed during database setup: ${failures.length} tenants could not initialize`); - console.error('database setup did not become ready before the test timeout'); - process.exit(1); - } - - console.log(`integration suite completed with ${workers} workers`); -} - -main().catch((error) => { - console.error(error.stack || error.message); - process.exit(1); -}); +const os = require('os'); +const { Pool } = require('pg'); + +function parseConcurrency() { + const configured = process.env.INTEGRATION_WORKERS || 'auto'; + if (configured === 'auto') { + return Math.max(4, os.cpus().length * 3); + } + + const workers = Number(configured); + if (!Number.isInteger(workers) || workers < 1) { + console.error('invalid INTEGRATION_WORKERS value'); + process.exit(1); + } + + return workers; +} + +function connectionString() { + return process.env.DATABASE_URL || 'postgresql://acme:acme@localhost:55432/acme_test'; +} + +async function runWorker(workerId, poolSize) { + const pool = new Pool({ + connectionString: connectionString(), + max: poolSize, + connectionTimeoutMillis: 1500, + idleTimeoutMillis: 1000, + }); + + try { + await Promise.all( + Array.from({ length: poolSize }, async () => { + await pool.query('select pg_sleep(0.4)'); + }), + ); + await pool.query('insert into test_events (worker_id) values ($1)', [workerId]); + return { workerId, ok: true }; + } finally { + await pool.end(); + } +} + +async function prepareDatabase() { + const pool = new Pool({ + connectionString: connectionString(), + max: 1, + connectionTimeoutMillis: 1500, + idleTimeoutMillis: 1000, + }); + + try { + await pool.query('create table if not exists test_events (id serial primary key, worker_id int, created_at timestamptz default now())'); + } finally { + await pool.end(); + } +} + +async function main() { + const concurrency = parseConcurrency(); + const poolSize = Number(process.env.APP_DB_POOL_SIZE || 4); + + console.log('starting integration suite'); + + await prepareDatabase(); + + const results = await Promise.allSettled( + Array.from({ length: concurrency }, (_, index) => runWorker(index + 1, poolSize)), + ); + + const failures = results.filter((result) => result.status === 'rejected'); + + if (failures.length > 0) { + console.error('tenant bootstrap failed before the readiness deadline'); + console.error('inspect runtime service logs from the runner for more detail'); + process.exit(1); + } + + console.log(`integration suite completed`); +} + +main().catch((error) => { + console.error(error.stack || error.message); + process.exit(1); +});