Back

Paradise SS13 CI


Introduction

After returning to this project in early 2020 after a break, there was a glaring issue. Our CI was incredibly outdated. There were no unit tests, no validation of the SQL schema, no linting, nothing. This annoyed me a little, so I set myself a task list. This included:

  • Addding in code linting
  • Enforcing LF file endings as opposed to a mix of LF and CRLF
  • Adding unit tests to a language which doesn't natively support them
  • Migrating the CI from Travis CI to GitHub actions

Lets go through these

Adding in code linting

DM is a very old language, and is different to a lot of ways we would tradditionally see languages. It is compiled, but still needs a runner application (Like Java with the JVM), has object types but not strict int/string/bool types, but still has features that modern languages do not (ex: an easy way to iterate all subtypes of a class). There are lots of things in this language which the compiler doesn't pick up on, but still are considered bad practices, such as:

  • if() conditions which are always true
  • Accessing src (the DM version of this.) in global functions (there is no source class)
  • Detecting unreachable code (Lines after a return that can never execute)
  • Invalid keyword arguments

Thankfully, the DM dev community has taken these issues into their own hands, and creating their own linters, the most prominent example being SpacemanDMM. This solves all of the above issues, and more, however, there was one small problem with implementing it. When bringing aggressive code screening into a project, you suddenly have to make the project conform. However, 121 file changes later, the refactoring was done.

Enforcing LF line endings

If you have ever worked with Git, you will know how the mix of line endings makes everything difficult, due to people having different IDE setups, not to mention the mess it becomes with merge conflicts. This issue had annoyed me long enough that I ended up forcing the repository to use LF endings in all files, and any CRLF files detected would fail CI checks. The checking was accomplished using this python script. The diff to make this work was about 230,000 lines, but it needed to be done. After completing this, the repository has had far fewer merge conflicts, which I deem a good metric of success.

Something also relating to this, but not quite the same was enforcing trailing newlines at the end of files. This was achieved in a similar fashion (CI scripts to validate), and this has also had success in reducing extra merge conflicts.

Adding unit tests

Putting unit tests into a language which doesn't support them is no easy task, however it was still doable. By compiling the server with a specific compile flag (#define CIBUILDING), it will do the following:

  1. Prepare the server to run unit tests.
  2. Autostart the game round once the server has initialised.
  3. Iterate every subtype of the unit test class and instance it.
  4. Call Run() on every unit test.
    1. If any of the unit tests call Fail(), the build is declared a failure.
    2. Additionally, if any exceptions are generated on startup, the build is also declared a failure.
  5. If no failures are detected during the test phase, a file called clean_run.lk is saved to disk. CI will then try and cat that file. If it fails due to the file not existing (due to a failed run), the CI build will fail, and inform the PR author why.

The unit test framework is fully modular, and is already used for things, including but not limited to:

  • Validation of SQL schema (syntax, migrations and version)
  • Validation that logs are timestamped with the ISO-8601 format
  • Validation that external libraries are the exact version we need
  • Validation that all game management tickers that are flagged to initialise do successfully initialise

Migrating from Travis to GitHub actions

At some time in early November 2020, Travis CI announced changes to their free CI tier, giving 10,000 "credits" maximum before you would have to pay, despite the project being open source. We had been considering migrating to GitHub actions for a while now, due to tighter integration with the repository among other things, but this was essentially the nail in the coffin. 1 minute of Linux build time consumed 10 credits, and we had 3 jobs in parallel, which took up to 7 minutes each. One single commit consumed 210 credits, which meant we would be able to build 47 commits maximum before running out.

This time restriction set me to work to get our CI migrated over to GitHub actions as soon as possible, which was for the best in the end, as we could now enjoy:

  • Unlimited builds for the project
  • Tighter integration with the repository itself
  • Faster builds (GitHub actions clones the repository in around 3 seconds)
  • Not depending on an external service