1. Objectives
1.1. Systematic code review
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
Figure 2. Exemple d’Continuous Integration (github-travis)
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.
4. Illustration with GitLab
4.1. Process
-
Create a file in the main folder:
.gitlab-ci.yml
-
Describe expected treatments
-
"Push" on the GitLab repo
-
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 :
Figure 4. Attention au code infini
Another .gitlab-ci.yml
:
- npm install express --save - node ./specs/start.js ./specs/async.spec.js
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!");
}
}
Figure 6. Un test exécuté sous eclipse
Figure 7. Utilisation d’infinitest sous eclipse
4.2.7. Eclipse
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>
Figure 9. Run de ant sous eclipse
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]"