Course outline · 0% complete

0/29 lessons0%

Course overview →

What continuous integration is

lesson 7-1 · ~11 min · 19/29

From the git course, with five teammates pushing to the same repository all day and no automation, a broken push is discovered whenever some human happens to run the tests next, possibly days later.

Git only stores and transports code, it runs nothing, which is the same point lesson 1-1 made about environments.

Until something automatically runs the tests on every push, broken code sits on main silently. That something is CI.

Continuous integration

CI, short for continuous integration, means that every time anyone pushes code, a server automatically gets a fresh copy and runs the project's checks, typically install, lint, tests, and since unit 3 a docker build.

If any check fails, the push is marked with a red ✗ on GitHub, the author is notified, and a pull request can be blocked from merging.

The machinery underneath is already familiar from the terminal course: exit codes. Every command finishes with a number, 0 for success and anything else for failure, readable as $?.

Test outcomeExit codeCI verdict
all tests green0pass
any test red1fail
runner crashednon-zerofail

Test runners follow this convention exactly, so the CI server merely runs the listed commands in order and watches the exit codes. There is no magic understanding of the code, only 0 or not-0.

run: pytest a plain command exit code 0 not 0 green next step runs red job stops here
CI reduces to one number. A step exits 0 and the pipeline continues, anything else marks the run red and stops it.

Exit codes first-hand

Three commands with predictable exit codes, printed through $?, which holds the exit code of the previous command.

true
echo "true exited with: $?"
false
echo "false exited with: $?"
grep -q "hello" <<< "hello world"
echo "grep exited with: $?"

Output

true exited with: 0
false exited with: 1
grep exited with: 0
CommandExit codeReason
true0it exists only to succeed
false1it exists only to fail
grep -q "hello"0the pattern was found

<<< is a here-string, feeding a literal string to a command as its standard input. Reading $? is a one-shot operation, since the next command overwrites it, which is why each echo follows immediately.

A CI server is essentially one if statement

A shell function standing in for a test suite, with an if deciding the verdict the way a CI server does.

run_tests() {
  grep -q "TODO" <<< "no unfinished work here"
}
if run_tests; then
  echo "tests passed"
else
  echo "tests failed"
fi

Output

tests failed
ElementBehavior
if some_command; thentakes the then-branch when the command exits 0
grep -q "TODO"finds no match here, so it exits 1
function exit codethe exit code of its last command

No brackets appear anywhere, because if in bash tests a command rather than an expression. The familiar [ ... ] form is itself just a command, which is why both spellings work.

How a step is judged

A CI server decides a test step passed by checking the exit code of the command, where 0 means pass and anything else means fail.

CI is exit codes all the way down. Any command that exits 0 is a green step, and anything else is red and stops the run.

Step commandExit codeResult
pytest with all tests green0green
pytest with one failure1red, job stops
a shell one-liner ending in false1red

That is why literally any command can go in a pipeline, from a test runner to a shell one-liner. The CI system never inspects the code, it only inspects the number the process returned.

The number that means success

The exit code must be 0.

The Unix convention is 0 for success and any non-zero value for failure, and CI systems are built entirely on that convention. Success is the one specific value, while failure is everything else, and echo $? after a passing command prints it.

Exit codeMeaning
0success
1generic failure
2 and uptool-specific failures

A step that exits 0 is green and anything else is red and stops the job. This is also why a test runner that forgets to propagate a failing exit code silently breaks a pipeline, since the tests report failures on screen while CI still reads a 0 and moves on.