Embarking on an Automation Adventure with Exercism and GitLab CI

Introduction

After dedicating considerable time to the fantastic platform, exercism.io, I’ve learned numerous programming languages and received invaluable advice from mentors. As I accumulated a variety of exercises, I felt the need to automate and streamline the process. This led me to explore the advanced features offered by GitLab CI. Here’s what I’ve learned.

What is Exercism?

Exercism is a community-driven platform where learners can acquire new programming languages and receive expert guidance from volunteer mentors. The platform offers structured learning paths, consisting of a logical sequence of exercises, to ensure steady progression.

All tracks available on exercism.io

Version Control with Git

While Exercism provides a CLI for downloading and uploading exercises, it doesn’t replace the need for version control. I use Git to manage my exercises, each organized within its respective language folder.

Folder Structure

Here’s an example of how the Exercism folder is structured:

exercism
+-- c
¦   +-- hello-world
¦       +-- hello-world.c
¦       +-- hello-world-test.c
+-- go
¦   +-- leap
¦   ¦   +-- leap.go
¦   ¦   +-- leap_test.go
¦   +-- hamming
¦       +-- hamming.go
¦       +-- hamming_test.go
+-- ruby
    +-- sieve
        +-- sieve.rb
        +-- sieve_test.rb

Test-Driven Development

Each exercise comes with tests that adhere to the Test-Driven Development (TDD) methodology. Once the tests pass, you can submit your solution and request mentor feedback.

The Need for Continuous Integration

After accumulating a variety of solutions, I wanted a Continuous Integration (CI) system that would help me maintain and update my exercises, especially when new tests are introduced. I also wanted to adhere to the DRY (Don’t Repeat Yourself) principle, aiming for a CI template that could be applied across exercises in the same language.

After evaluating various CI solutions like Jenkins, CircleCI, Travis CI, and Codeship, I settled on GitLab CI due to its robust features and specifications.

Leveraging GitLab CI’s Parent-Child Pipelines

GitLab offers a free CI service that can be easily integrated into both Git and GitHub projects. One powerful feature is the Parent-Child Pipelines, which allow for the execution of a CI pipeline from within another, effectively enabling the creation of meta-CI pipelines.

Nested CI Levels

My CI setup spans three nested levels:

  1. Root Level: Executes a CI for each programming language.
  2. Language Level: Executes a CI for each exercise within a language.
  3. Exercise Level: Runs the platform-provided tests for each exercise.

Code Snippets

Here are some code snippets to give you an idea of how this works:

Root Level .gitlab-ci.yml

emacs-lisp:
  stage: test
  trigger:
    project: EmilienMottet/exercism-emacs-lisp-sol
    strategy: depend
  rules:
    - changes:
        - emacs-lisp/*

x86-64-assembly:
  stage: test
  trigger:
    project: EmilienMottet/exercism-x86-64-assembly-sol
    strategy: depend
  rules:
    - changes:
        - x86-64-assembly/*

Language Level .generate-config.jsonnet

local exercism_projects = std.map(function(x) std.strReplace(x, '/', ''), std.split(std.extVar('exercism_projects'), '\n'));
local lang = std.extVar('lang');

local JobHandler(name) = {
  ['test_' + lang + '_' + name]: {
    stage: 'test',
    trigger: {
      include: [
        {
          artifact: '.' + lang + '-' + name + '-gitlab-ci.yml',
          job: 'generate_' + lang + '_gitlab_ci',
        },
      ],
      strategy: 'depend',
    },
  },
};

{
  '.generated-config.yml': { ['generate_' + lang + '_gitlab_ci']: {
    stage: 'build',
    image: {
      name: 'bitnami/jsonnet:latest',
      entrypoint: [''],
    },
    script: [
      'DIR_SPLIT=$(echo $DIR_TO_BE_TESTED | tr " " "\n")',
      'jsonnet -m . --ext-str exercism_projects="$DIR_SPLIT" --ext-str lang="' + lang + '" ".' + lang + '-gitlab-ci.jsonnet"',
    ],
    needs: [
      {
        pipeline: '$PARENT_PIPELINE_ID',
        job: 'build_vars',
      },
    ],


    artifacts: {
      paths: [
        '.' + lang + '-*-gitlab-ci.yml',
      ],
    },
  } } + std.foldl(function(x, y) x + y, std.map(JobHandler, exercism_projects), {}),
}

Exercise Level .c-gitlab-ci.jsonnet

local exercism_projects = std.map(function(x) std.strReplace(x, '/', ''), std.split(std.extVar('exercism_projects'), '\n'));
local lang = std.extVar('lang');

local CTestJob(name) = {
  ['.' + lang + '-' + name + '-gitlab-ci.yml']: {
    default: {
      image: 'gcc:latest',
    },
    ['test-' + lang + '-' + name + '-exercism']: {
      script: [
        'cd ' + name,
        'make',
      ],
    },
  },
};


std.foldl(function(x, y) x + y, std.map(CTestJob, exercism_projects), {})

Conclusion

I hope this article inspires you to try out Exercism and perhaps even contribute to the community. Automating your workflow not only saves time but also ensures that you’re always up-to-date, allowing you to focus on what really matters: learning and improving.

Thank you for reading!

Full article : https://dev.to/emilienmottet/a-journey-into-the-depths-of-gitlab-ci-15j5