self-pipeline.test.js (3385B)
1 // test/self-pipeline.test.js - the pipeline this project jobs on itself 2 // 3 // .conductor.yml is not covered by examples.test.js, and it is the one 4 // pipeline whose breakage stops the project from building at all. It also 5 // encodes decisions that are easy to undo by accident: which tasks may run 6 // on any worker, and which need a docker socket. 7 8 import test from 'node:test'; 9 import assert from 'node:assert/strict'; 10 import fs from 'node:fs/promises'; 11 import path from 'node:path'; 12 import { fileURLToPath } from 'node:url'; 13 14 import { compilePipeline } from '../src/lib/pipeline/index.js'; 15 16 const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); 17 18 async function selfPipeline(arches = ['x86_64'], ref = 'refs/heads/main') { 19 const text = await fs.readFile(path.join(ROOT, '.conductor.yml'), 'utf8'); 20 return compilePipeline(text, { source: '.conductor.yml', arches, ref }); 21 } 22 23 test('the pipeline this project jobs on itself compiles', async () => { 24 const { tasks } = await selfPipeline(); 25 assert.ok(tasks.length > 0, '.conductor.yml produced no tasks'); 26 }); 27 28 test('its tasks declare the features they actually need', async () => { 29 const { tasks } = await selfPipeline(); 30 const byName = new Map(tasks.map((task) => [task.name, task])); 31 32 for (const name of ['style', 'test', 'integration', 'images', 'smoke', 'publish']) { 33 assert.ok(byName.has(name), `expected a ${name} task`); 34 } 35 36 // The fast task has to actually skip the container-backed tests, or it 37 // fails on any worker without a docker socket. 38 assert.equal(byName.get('test').env.CONDUCTOR_TEST_NO_DOCKER, '1'); 39 40 // Anything starting containers has to say so, otherwise it gets handed 41 // to a worker that cannot job it. 42 for (const name of ['integration', 'images', 'smoke', 'publish']) { 43 assert.ok( 44 byName.get(name).requires.includes('docker'), 45 `${name} starts containers and must require the docker feature`, 46 ); 47 } 48 49 // Conversely, the tasks meant to run anywhere must not demand features. 50 for (const name of ['style', 'test']) { 51 assert.deepEqual(byName.get(name).requires, [], `${name} should run on any worker`); 52 } 53 }); 54 55 test('publishing is restricted to main and release tags', async () => { 56 // The cost of getting this wrong is a branch build overwriting :latest 57 // on Docker Hub, which is not something a test should leave to trust. 58 const onMain = await selfPipeline(); 59 assert.ok(onMain.tasks.some((j) => j.name === 'publish')); 60 61 for (const ref of ['refs/heads/feature', 'refs/heads/main-2', 'refs/pull/7/head', '']) { 62 const { tasks } = compilePipeline( 63 await fs.readFile(path.join(ROOT, '.conductor.yml'), 'utf8'), 64 { source: '.conductor.yml', arches: ['x86_64'], ref }, 65 ); 66 assert.ok( 67 !tasks.some((j) => j.name === 'publish'), 68 `publish must not run for ${JSON.stringify(ref)}`, 69 ); 70 // The rest of the pipeline still has to run on a branch. 71 assert.ok(tasks.some((j) => j.name === 'test'), `test should still run for ${JSON.stringify(ref)}`); 72 } 73 74 for (const ref of ['refs/heads/main', 'refs/tags/v1.2.0']) { 75 const { tasks } = compilePipeline( 76 await fs.readFile(path.join(ROOT, '.conductor.yml'), 'utf8'), 77 { source: '.conductor.yml', arches: ['x86_64'], ref }, 78 ); 79 assert.ok(tasks.some((j) => j.name === 'publish'), `publish should run for ${ref}`); 80 } 81 });