Learning objectives
By the end of this module, you should be able to:
- Order fast static checks before slower tests.
- Explain when a matrix is useful.
- Avoid making required quality gates non-blocking.
Quality gates should answer specific risks
Syntax, lint, unit, integration and security checks catch different classes of problems. Name jobs and checks by the evidence they provide so failures are diagnosable.
Matrices test meaningful combinations
A strategy matrix can run the same job against multiple supported runtimes or operating systems. Test combinations you actually support; an oversized matrix can waste time without increasing confidence.
Required checks must really block
continue-on-error is useful for experimental diagnostics, not for a check that is supposed to protect a deployment or merge.
Worked examples
See the idea in practice.
Runtime matrix
strategy:
matrix:
node: [22, 24]
steps:
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
- The same validation executes against two supported Node versions.
- Matrix jobs can run in parallel.
Hands-on lab
Lab — Build a layered quality gate
Goal: Create syntax, unit and integration checks and stop on the first failing required gate.
STEP 1Create application
mkdir -p ~/gnu-cicd-labs/module05 && cd ~/gnu-cicd-labs/module05
printf 'module.exports=(a,b)=>a+b;\n' > calc.js
STEP 2Create unit gate
cat > unit.sh <<'EOF'
#!/usr/bin/env bash
set -e
[ "$(node -e "process.stdout.write(String(require('./calc')(2,3)))")" = "5" ]
EOF
chmod +x unit.sh
STEP 3Create pipeline gate
cat > quality.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
node --check calc.js
./unit.sh
node -e "const f=require('./calc'); if(f(10,-3)!==7) process.exit(1)"
echo 'QUALITY PASS'
EOF
chmod +x quality.sh
STEP 4Run gates
./quality.sh
Verify
- Syntax and both behavior checks pass.
- QUALITY PASS appears only after all required checks succeed.
Expected outcome
- The pipeline has multiple explicit confidence layers.
If it fails
- Run the failing gate directly to narrow the problem.
- Do not hide a required failure with || true or continue-on-error.
Real-world connectionWell-layered CI reduces time-to-diagnosis because developers can see whether a change failed syntax, unit behavior, integration or another specific gate.
Avoid these traps
Common mistakes
- Using one huge test command with no diagnostic separation.
- Creating a matrix for versions the product does not support.
- Marking a required quality gate continue-on-error.
Knowledge check
Can you explain it without looking back?
Open each answer only after you have tried to answer the question yourself.
1Why put fast checks early?
They fail quickly and avoid spending runner time on slower downstream work when basic validation already failed.
2What problem does a matrix solve?
It repeats a job across meaningful supported combinations such as runtime versions or operating systems.
3Should a required deployment gate use continue-on-error?
Normally no, because that would allow a failed required check to be treated as non-blocking.