Continuous Integration

Continuous integration is a helpful software engineering practice completed in order to ensure code quality over a long period of time. Continuous integration ensures that multiple tests run on each commit, allowing you to see if your newly committed code may be causing problems. Code which “fails CI” (or doesn’t pass one of the various tests) will not be merged into the master branch, because it causes problems.

Currently, we use GitHub Actions as our CI provider. It provides quick check markers to show which specific tests failed for a given commit.

Architecture and Terminology

digraph CIDiagram {
    fontname="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
    node [
        shape=rounded
        fontname="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
        penwidth="2"
        margin=0.25
    ]
    graph [
        fontname="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
        labeljust = l;
        color = lightgrey;
    ]
    edge [
        fontname="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
    ]

    subgraph blue_nodes {
        node [
            style="rounded,filled"
            fillcolor="#aed6f5"
            color="#349be8"
        ]
        b1 [label="Commit is pushed\n to any branch"]
        b2 [label="Pull request is opened"]
    }

    subgraph green_nodes {
        node [
            style="rounded,filled"
            fillcolor="#cce6bc"
            color="#7fc256"
        ]
        green1 [label="Commit is marked\nas passing"];
        green2 [label="Test passes"];
        green3 [label="Docs preview\nis published"];
    }

    subgraph red_nodes {
        node [
            style="rounded,filled"
            fillcolor="#f7bbba"
            color="#ee5453"
        ]
        red1 [label = "Commit is marked as\nnon-passing"];
        red2 [label = "Test fails"];
        red3 [label = "Bot stops waiting\nand marks job as\nfailed"];
        red4 [label = "Pull request is closed"];
    }

    subgraph gray_nodes {
        node [
            style="rounded,filled"
            fillcolor="#d8dae3"
            color="#666d80"
        ]
        gray1 [label = "Ensure that PR is\nopened by MIL members"];
        gray2 [label = "Bot waits for CI\nto finish"];
        gray3 [label = "Deploy docs to main\ndocumentation site"]
    }

    subgraph decision_nodes {
        node [
            shape=diamond
            style="rounded,filled"
            fillcolor="#f7f7f7"
            color="#d6d6db"
            penwidth="4"
            margin=0.15
        ]
        d1 [label = "Job succeeded?"];
        d2 [label = "pre-commit passes?"];
        d3 [label = "CI passes?"];
        d4 [label = "PR is opened\nby MIL member?"];
        d5 [label = "Is master branch?"];
    }

    subgraph cluster_0 {
        label = "GitHub Actions";
        margin = 30;
        node [
            style="rounded,filled"
            fillcolor="#d8dae3"
            color="#666d80"
        ]
        g1 [label="Install dependencies"]
        g2 [label="Build repository\nthrough catkin_make"]
        g3 [label="Run catkin_make and\nensure tests pass"]
        g4 [label = "Build documentation and\npublish as artifact"]
        // g2 [label="pre-commit.ci"]
        g1 -> g2 -> g3 -> g4;
    }

    subgraph cluster_1 {
        // Labels
        label = "pre-commit.ci";
        margin = 30;

        node [
            style="rounded,filled"
            fillcolor="#d8dae3"
            color="#666d80"
        ]

        p1 [label = "Queue is loaded"];
        p2 [label = "pre-commit hooks\nare evaluated"];
        p1 -> p2;
    }

    b1 -> g1;
    b2 -> p1;
    b2 -> gray1;
    b2 -> gray2;
    g4 -> d1;
    p2 -> d2;
    gray1 -> d3;
    d3 -> green3 [label = "Yes"];
    d3 -> red3 [label = "No"];
    gray2 -> d4;
    d4 -> red4 [label = "No"];
    d2 -> green2 [label = "Yes"];
    d2 -> red2 [label = "No"];
    d1 -> green1 [label = "Yes"];
    green1 -> d5;
    d5 -> gray3 [label = "Yes"];
    d1 -> red1 [label = "No"];
}

Runner Environment

Currently, our continuous integration is run in a Docker container on one of our computers, Shuttle. This container is given unlimited resources to the host machine in order to help jobs complete quickly.

The Docker Compose file for this container is stored locally on Shuttle. To start up the Docker container, you will need to run docker-compose up -d inside ~/runners (where the compose file is stored).

Runs, Jobs, Steps

There are multiple pieces to the GitHub Actions CI. The largest piece is the run. This is the sum of all jobs in the run. Some runs may run more jobs than others. Currently, the CI job that’s spun up on each commit runs around 15 jobs. Inside each job are multiple steps. Similar to a Dockerfile, each step specifies one action to do: setup Python, download the repo, and compile a package are all separate steps. Steps are shared between jobs.

Each run is also associated with a graph showing the relationship between the jobs in the run. This is helpful to see what jobs other jobs depend on, as well as why a job might be failing.

Using CI

Continuous integration aims to be functional and automatic, and fulfills this mission by automatically running when you push a new commit to any branch. However, sometimes you may want more control over the CI process.

To view the output of a job more closely, click on the “Details” tab next to the status check of an action. You will then see each step of the job. You can view the logs of these steps to see what exactly the job executed.

If you want to restart a job, click on the “Re-run all jobs” button. This will allow you to re-run the jobs. If the job is currently running (or stuck), you will likely need to cancel it before you will be allowed to re-run it.

Updating CI

To update the CI process, you will need to change the GitHub Actions file, found in ~/.github/workflows. Whenever you change the file and push your changes to the repo, you can head over to the “Actions” tab of the GitHub repo site to see how your new changes changed the CI process.