1. Objectives

1.1. Systematic code review

builds status
Figure 1. Résultats visibles et tracés

1.2. Environment

gitlab-ci-multi-runner 1.6.0.

1.3. eXtreme Programming

Continuous Integration is a software development practice where members of a team integrate their work frequently, […​] leading to multiple integrations per day.
— Martin Fowler

1.4. General Principles

travis
Figure 2. Exemple d’Continuous Integration (github-travis)
arch1
Figure 3. Architecture GitLab-CI (https://about.gitlab.com/gitlab-ci/)

1.5. YAML

YAML: YAML Ain’t Markup Language

Example of .yml file
---
receipt:     Oz-Ware Purchase Invoice
date:        2012-08-06
customer:
    first_name:   Dorothy
    family_name:  Gale

2. Usage

A Continuous Integration server allows:

git [detail]#(pull, checkout, push)#
*build*
Archives
Deploy code
Execute test suites [detail]#(Junit, Audit, test IHM, ...)#
Notify results [detail]#(mail, RSS)#
etc.

3. Services

4. Illustration with GitLab

4.1. Process

  1. Create a file in the main folder: .gitlab-ci.yml

  2. Describe expected treatments

  3. "Push" on the GitLab repo

  4. Control results

4.2. Example

image: node:4.2.2   (1)

all_tests:
  script:           (2)
   - npm install express --save
   - node ./myapp.js
1 Name of the "runner"
2 Instructions for the virtual machine

Results :

gitlabCI 1B2 1
Figure 4. Attention au code infini

Another .gitlab-ci.yml:

   - npm install express --save
   - node ./specs/start.js ./specs/async.spec.js
gitlabCI 1B2 2
Figure 5. Le build est un succès

4.2.1. Code

package org.jmb;
public class HelloWorld
{
   private String name = "";
   public String getName()
   {
      return name;
   }
   public String getMessage()
   {
      if (name == "")
      {
         return "Hello!";
      }
      else
      {
         return "Hello " + name + "!";
      }
   }
   public void setName(String name)
   {
      this.name = name;
   }
}

4.2.2. Testing main

src/Main.java
package org.jmb;


public class Main {
	public static void main(String[] args) {
	      org.jmb.HelloWorld h = new org.jmb.HelloWorld();
	      h.setName("JMB");
	      System.out.println(h.getMessage());
	}
}

4.2.3. Compilation by hand

$ javac -sourcepath src -d bin/ src/Main.java
$ ls bin
HelloWorld.class	Main.class
$ java -cp bin Main
Hello JMB!

4.2.4. Ant build

build.xml
<project>
    <target name="clean">
        <delete dir="bin"/>
    </target>

    <target name="build">
        <mkdir dir="bin"/>
        <javac srcdir="src" destdir="bin"/>
    </target>

    <target name="jar">
        <mkdir dir="bin/jar"/>
        <jar destfile="bin/jar/HelloWorld.jar" basedir="bin">
            <manifest>
                <attribute name="Main-Class" value="Main"/>
            </manifest>
        </jar>
    </target>

    <target name="run">
        <java jar="bin/jar/HelloWorld.jar" fork="true"/>
    </target>

</project>
$ ant clean build jar
$ ant run
Buildfile: /Users/bruel/HelloWorld/build.xml

run:
     [java] Hello JMB!

BUILD SUCCESSFUL
Total time: 0 seconds

4.2.5. Improvements

build.xml (amélioré)
<project name="HelloWorld" basedir="." default="main">

    <property name="src.dir"     value="src"/>
    <property name="build.dir"   value="bin"/>
    <property name="classes.dir" value="${build.dir}/classes"/>
    <property name="jar.dir"     value="${build.dir}/jar"/>

    <property name="main-class"  value="Main"/>



    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}"/>
    </target>

    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
            <manifest>
                <attribute name="Main-Class" value="${main-class}"/>
            </manifest>
        </jar>
    </target>

    <target name="run" depends="jar">
        <java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/>
    </target>

    <target name="clean-build" depends="clean,jar"/>

    <target name="main" depends="clean,run"/>

</project>
$ ant
Buildfile: /Users/bruel/HelloWorld/build.xml

clean:
   [delete] Deleting directory /Users/bruel/HelloWorld/bin

compile:
    [mkdir] Created dir: /Users/bruel/HelloWorld/bin/classes
    [javac] Compiling 2 source files to /Users/bruel/HelloWorld/bin/classes

jar:
    [mkdir] Created dir: /Users/bruel/HelloWorld/bin/jar
      [jar] Building jar: /Users/bruel/HelloWorld/bin/jar/HelloWorld.jar

run:
     [java] Hello JMB!

main:

BUILD SUCCESSFUL
Total time: 1 second

4.2.6. Tests

src/TestHelloWorld.java (failing)
package org.jmb;
public class TestHelloWorld extends junit.framework.TestCase {

    public void testNothing() {
    }

    public void testWillAlwaysFail() {
        fail("An error message");
    }

}
build.xml (external librairies)
<project name="HelloWorld" basedir="." default="main">

    <property name="src.dir"     value="src"/>
    <property name="build.dir"   value="bin"/>
    <property name="classes.dir" value="${build.dir}/classes"/>
    <property name="jar.dir"     value="${build.dir}/jar"/>

    <property name="main-class"  value="Main"/>

    <property name="lib.dir"     value="lib"/>

    <path id="classpath">
        <fileset dir="${lib.dir}" includes="**/*.jar"/>
    </path>

    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}"  classpathref="classpath"/>
    </target>

    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
            <manifest>
                <attribute name="Main-Class" value="${main-class}"/>
            </manifest>
        </jar>
    </target>

    <path id="application" location="${jar.dir}/${ant.project.name}.jar"/>

    <target name="run" depends="jar">
        <java fork="true" classname="${main-class}">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
                <path location="${jar.dir}/${ant.project.name}.jar"/>
            </classpath>
        </java>
    </target>

    <target name="junit" depends="jar">
        <junit printsummary="yes">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
            </classpath>

            <batchtest fork="yes">
                <fileset dir="${src.dir}" includes="TestHelloWorld.java"/>
            </batchtest>
        </junit>
    </target>

    <target name="clean-build" depends="clean,jar"/>

    <target name="main" depends="clean,run"/>

</project>
$ ant junit
Buildfile: /Users/bruel/HelloWorld/build.xml

compile:

jar:

junit:
    [junit] Running TestHelloWorld
    [junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0,003 sec
    [junit] Test TestHelloWorld FAILED

BUILD SUCCESSFUL
Total time: 1 second
src/TestHelloWorld.java
package org.jmb;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;


public class TestHelloWorldReal {

   private org.jmb.HelloWorld h;

   @Before
   public void setUp() throws Exception
   {
      h = new org.jmb.HelloWorld();
   }

   @Test
   public void testHelloEmpty()
   {
      assertEquals(h.getName(),"");
      assertEquals(h.getMessage(),"Hello!");
   }

   @Test
   public void testHelloWorld()
   {
      h.setName("World");
      assertEquals(h.getName(),"World");
      assertEquals(h.getMessage(),"Hello World!");
   }
}
eclipseTest
Figure 6. Un test exécuté sous eclipse
infinitest
Figure 7. Utilisation d’infinitest sous eclipse

4.2.7. Eclipse

ant files generated by Eclipse!

exportAnt
Figure 8. Export du bild ant sous eclipse
build.xml (eclipse)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- WARNING: Eclipse auto-generated file.
              Any modifications will be overwritten.
              To include a user specific buildfile here, simply create one in the same
              directory with the processing instruction <?eclipse.ant.import?>
              as the first entry and export the buildfile again. --><project basedir="." default="build" name="HelloWorld">
    <property environment="env"/>
    <property name="junit.output.dir" value="junit"/>
    <property name="debuglevel" value="source,lines,vars"/>
    <property name="target" value="1.8"/>
    <property name="source" value="1.8"/>
    <path id="JUnit 4.libraryclasspath">
        <pathelement location="../../../.p2/pool/plugins/org.junit_4.12.0.v201504281640/junit.jar"/>
        <pathelement location="../../../.p2/pool/plugins/org.hamcrest.core_1.3.0.v201303031735.jar"/>
    </path>
    <path id="HelloWorld.classpath">
        <pathelement location="bin"/>
        <path refid="JUnit 4.libraryclasspath"/>
    </path>
    <target name="init">
        <mkdir dir="bin"/>
        <copy includeemptydirs="false" todir="bin">
            <fileset dir="src">
                <exclude name="**/*.launch"/>
                <exclude name="**/*.java"/>
            </fileset>
        </copy>
    </target>
    <target name="clean">
        <delete dir="bin"/>
    </target>
    <target depends="clean" name="cleanall"/>
    <target depends="build-subprojects,build-project" name="build"/>
    <target name="build-subprojects"/>
    <target depends="init" name="build-project">
        <echo message="${ant.project.name}: ${ant.file}"/>
        <javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
            <src path="src"/>
            <classpath refid="HelloWorld.classpath"/>
        </javac>
    </target>
    <target description="Build all projects which reference this project. Useful to propagate changes." name="build-refprojects"/>
    <target name="Main">
        <java classname="org.jmb.Main" failonerror="true" fork="yes">
            <classpath refid="HelloWorld.classpath"/>
        </java>
    </target>
    <target name="TestHelloWorld">
        <mkdir dir="${junit.output.dir}"/>
        <junit fork="yes" printsummary="withOutAndErr">
            <formatter type="xml"/>
            <test name="org.jmb.TestHelloWorld" todir="${junit.output.dir}"/>
            <jvmarg line="-ea"/>
            <classpath refid="HelloWorld.classpath"/>
        </junit>
    </target>
    <target name="junitreport">
        <junitreport todir="${junit.output.dir}">
            <fileset dir="${junit.output.dir}">
                <include name="TEST-*.xml"/>
            </fileset>
            <report format="frames" todir="${junit.output.dir}"/>
        </junitreport>
    </target>
</project>
runAnt
Figure 9. Run de ant sous eclipse
junitreport
Figure 10. Présentation des résultats de test

4.2.8. Continuous Integration

 .gitlab-ci.yml (at the root of the git project)
image: openjdk:8

before_script:
  - apt-get update
  - apt-get -y install ant

stages:
  - build
  - test

ci_build:
  stage: build
  script:
    - cd HelloWorld
    - ant clean
    - ant build

ci_test:
  stage: test
  script:
    - cd HelloWorld
    - ant clean build TestHelloWorld

5. Tricks

5.1. When CI is not wanted

git commit -m "Blabla... [ci skip]"

5.2. Check the YAML syntax

lint
Figure 11. Validation de son code YAML