diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c1e735b5b0ddb52b3eb7bdace59467b2e74c59f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,111 @@
+variables:
+  GIT_SUBMODULE_STRATEGY: "recursive"
+  GIT_DEPTH: "3"
+  CCACHE_BASEDIR: "${CI_PROJECT_DIR}"
+  CCACHE_DIR: "${CI_PROJECT_DIR}/ccache"
+
+cache:
+  paths:
+    - ccache
+
+.robotpkg-py-eigenpy: &robotpkg-py-eigenpy
+  except:
+    - gh-pages
+  script:
+    - mkdir -p ccache
+    - cd /root/robotpkg/math/py-eigenpy
+    - git pull
+    - make checkout MASTER_REPOSITORY="dir ${CI_PROJECT_DIR}"
+    - make install
+    - cd work.$(hostname)/$(make show-var VARNAME=DISTNAME)
+    - make check
+
+
+robotpkg-py-eigenpy-dubnium-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:dubnium
+
+robotpkg-py-eigenpy-16.04-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:16.04
+
+robotpkg-py-eigenpy-18.04-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:18.04
+
+robotpkg-py-eigenpy-py3-dubnium-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:dubnium
+  allow_failure: true
+
+robotpkg-py-eigenpy-py3-16.04-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:16.04
+  allow_failure: true
+
+robotpkg-py-eigenpy-py3-18.04-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:18.04
+
+robotpkg-py-eigenpy-14.04-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:14.04
+
+robotpkg-py-eigenpy-14.04-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:14.04
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+
+robotpkg-py-eigenpy-py3-14.04-release:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:14.04
+  allow_failure: true
+
+robotpkg-py-eigenpy-py3-14.04-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:14.04
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+  allow_failure: true
+
+robotpkg-py-eigenpy-dubnium-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:dubnium
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+
+robotpkg-py-eigenpy-py3-dubnium-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:dubnium
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+  allow_failure: true
+
+robotpkg-py-eigenpy-16.04-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:16.04
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+
+robotpkg-py-eigenpy-py3-16.04-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:16.04
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+  allow_failure: true
+
+robotpkg-py-eigenpy-18.04-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy:18.04
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+
+robotpkg-py-eigenpy-py3-18.04-debug:
+  <<: *robotpkg-py-eigenpy
+  image: eur0c.laas.fr:5000/stack-of-tasks/eigenpy/py-eigenpy-py3:18.04
+  before_script:
+    - echo PKG_OPTIONS.py-eigenpy=debug >> /opt/openrobots/etc/robotpkg.conf
+
+
+
diff --git a/.travis.yml b/.travis.yml
index edc338ad7044d687121fad794b1708b1207b26f3..a3d844b75c2aa449b9f56788607ceb0b5cb81b19 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,4 @@
-language: generic
+language: python
 python:
   - "2.7"
 sudo: required
@@ -9,7 +9,7 @@ compiler:
 env:
   global:
     - secure: "SnIBG/xLIHX3CSvUbqqsX8xTVqIqQ7fFS6HWO6KZQVBsT6yugTwYHbyhNiU531JejYJ/I3ZrDhXfYH3qFZiYxnH1sifvwV+fnTtMXpPN7qPZwIymkjcmm6gJF51e0C7VOfUbvKFv0ngwj+ul21rgZSMuoEvxPK0WxtE3/ZSfn9c="
-    - APT_DEPENDENCIES="doxygen libeigen3-dev "
+    - APT_DEPENDENCIES="doxygen libeigen3-dev libboost-test-dev libboost-python-dev libeigen3-dev"
     - DEBSIGN_KEYID=5AE5CD75
     - CC=gcc
     - DO_COVERAGE_ON_BRANCH="master;release"
@@ -19,24 +19,18 @@ env:
     - BUILDTYPE=Release
     - BUILDTYPE=Debug
 notifications:
-email:
-  - pinocchio@laas.fr
+  email:
+    - pinocchio-build@laas.fr
 branches:
-only:
-  - master
-  - debian
-  - devel
-matrix:
-allow_failures:
-  - compiler:
-before_install: ./travis_custom/custom_before_install
-install:
-  - pip install --user coveralls
-  - pip install --user numpy
+  only:
+    - master
+    - debian
+    - devel
+before_install: ./.travis/run before_install
+install: pip install coveralls numpy
 script:
   - export CMAKE_ADDITIONAL_OPTIONS="-DCMAKE_BUILD_TYPE=${BUILDTYPE}"
   - sudo free -m -t
   - ./.travis/run ../travis_custom/custom_build
 after_failure: ./.travis/run after_failure
-after_success:
-  - ./.travis/run after_success
+after_success: ./.travis/run after_success
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 89e2f96535652c0dddeae1586216cf51fb7829f8..74a0ecf7d34c43dad7b9ef3d95e3bca5f2e41c68 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,7 @@ INCLUDE(cmake/base.cmake)
 INCLUDE(cmake/boost.cmake)
 INCLUDE(cmake/python.cmake)
 INCLUDE(cmake/ide.cmake)
+INCLUDE(cmake/test.cmake)
 
 SET(PROJECT_NAME eigenpy)
 SET(PROJECT_DESCRIPTION "Wrapping Eigen3 -- numpy")
diff --git a/cmake b/cmake
index d22de8c53c3507df785b1fb3ab260d9fdfb65233..2c0ecdf0ecf466d68565aa506bcc02326eb72eff 160000
--- a/cmake
+++ b/cmake
@@ -1 +1 @@
-Subproject commit d22de8c53c3507df785b1fb3ab260d9fdfb65233
+Subproject commit 2c0ecdf0ecf466d68565aa506bcc02326eb72eff
diff --git a/travis_custom/custom_before_install b/travis_custom/custom_before_install
deleted file mode 100755
index 46e3546485f36a313bad37f4a910b03098dba3c8..0000000000000000000000000000000000000000
--- a/travis_custom/custom_before_install
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-set -e
-
-# Setup environment variables.
-export APT_DEPENDENCIES="doxygen libboost-test-dev libboost-python-dev libeigen3-dev python2.7-dev python-numpy"
-
-# When this script is called the current directory is ./custom_travis
-. ./.travis/run ../.travis/before_install
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index 76a817fd5fe55e856ec54ab765096940f080aa11..f61b4c0dfadc252c131bbdfca7834ab2b710032c 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -1,6 +1,6 @@
-# 
+#
 # Copyright (c) 2016-2018 CNRS
-# 
+#
 # This file is part of eigenpy
 # eigenpy is free software: you can redistribute it
 # and/or modify it under the terms of the GNU Lesser General Public
@@ -31,7 +31,7 @@ MACRO(ADD_LIB_UNIT_TEST test PKGS)
     # We need to change the extension for python bindings
     SET_TARGET_PROPERTIES(${test} PROPERTIES SUFFIX ".so")
   ENDIF(APPLE)
-  
+
   ADD_TEST(NAME ${test} COMMAND ${PYTHON_EXECUTABLE} -c "import ${test}")
   ADD_DEPENDENCIES(check ${test})
 ENDMACRO(ADD_LIB_UNIT_TEST)
@@ -44,3 +44,5 @@ IF(NOT ${EIGEN3_VERSION} VERSION_LESS "3.2.0")
   ADD_LIB_UNIT_TEST(ref "eigen3")
 ENDIF()
 
+ADD_PYTHON_UNIT_TEST("py-matrix" "unittest/python/test_matrix.py" "unittest")
+ADD_PYTHON_UNIT_TEST("py-geometry" "unittest/python/test_geometry.py" "unittest")
diff --git a/unittest/python/test_eigenpy.py b/unittest/python/test_eigenpy.py
deleted file mode 100644
index aab786f7e518e2b3cc7742751d03a64525a60394..0000000000000000000000000000000000000000
--- a/unittest/python/test_eigenpy.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import numpy as np
-import matrix as eigenpy
-
-verbose = False
-
-if verbose: print "===> From MatrixXd to Py"
-M = eigenpy.naturals(3,3,verbose)
-Mcheck = np.reshape(np.matrix(range(9),np.double),[3,3])
-assert np.array_equal(Mcheck,M)
-
-if verbose: print "===> From Matrix3d to Py"
-M33= eigenpy.naturals33(verbose)
-assert np.array_equal(Mcheck,M33)
-
-if verbose: print "===> From VectorXd to Py"
-v = eigenpy.naturalsX(3,verbose)
-vcheck = np.matrix([range(3),],np.double).T
-assert np.array_equal(vcheck ,v)
-
-if verbose: print "===> From Py to Eigen::MatrixXd"
-if verbose: print "===> From Py to Eigen::MatrixXd"
-if verbose: print "===> From Py to Eigen::MatrixXd"
-Mref = np.reshape(np.matrix(range(64),np.double),[8,8])
-
-if verbose: print "===> Matrix 8x8"
-M = Mref
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block 0:3x0:3"
-M = Mref[0:3,0:3]
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block 1:3x1:3"
-M = Mref[1:3,1:3]
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block 1:5:2x1:5:2"
-M = Mref[1:5:2,1:5:2]
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block 1:8:3x1:5"
-M = Mref[1:8:3,1:5]
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block transpose 1:8:3x1:6:2"
-M = Mref[1:8:3,0:6:2].T
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block Vector 1x0:6:2"
-M = Mref[1:2,0:6:2]
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block Vector 1x0:6:2 tanspose"
-M = Mref[1:2,0:6:2].T
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block Vector 0:6:2x1"
-M = Mref[0:6:2,1:2]
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> Block Vector 0:6:2x1 tanspose"
-M = Mref[0:6:2,1:2].T
-assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
-
-if verbose: print "===> From Py to Eigen::VectorXd"
-if verbose: print "===> From Py to Eigen::VectorXd"
-if verbose: print "===> From Py to Eigen::VectorXd"
-
-if verbose: print "===> Block Vector 0:6:2x1 1 dim"
-M = Mref[0:6:2,1].T
-assert( np.array_equal(M.T,eigenpy.reflexV(M,verbose)) );
-
-if verbose: print "===> Block Vector 0:6:2x1"
-M = Mref[0:6:2,1:2]
-assert( np.array_equal(M,eigenpy.reflexV(M,verbose)) );
-
-if verbose: print "===> Block Vector 0:6:2x1 transpose"
-M = Mref[0:6:2,1:2].T
-assert( np.array_equal(M.T,eigenpy.reflexV(M,verbose)) );
-
-if verbose: print "===> From Py to Eigen::Matrix3d"
-if verbose: print "===> From Py to Eigen::Matrix3d"
-if verbose: print "===> From Py to Eigen::Matrix3d"
-
-if verbose: print "===> Block Vector 0:3x0:6:2 "
-M = Mref[0:3,0:6:2]
-assert( np.array_equal(M,eigenpy.reflex33(M,verbose)) );
-
-if verbose: print "===> Block Vector 0:3x0:6:2 T"
-M = Mref[0:3,0:6].T
-try:
-    assert( np.array_equal(M,eigenpy.reflex33(M,verbose)) );
-except eigenpy.Exception, e:
-    if verbose: print "As expected, got the following /ROW/ error:", e.message
-
-if verbose: print "===> From Py to Eigen::Vector3d"
-if verbose: print "===> From Py to Eigen::Vector3d"
-if verbose: print "===> From Py to Eigen::Vector3d"
-
-M = Mref[0:3,1:2]
-assert( np.array_equal(M,eigenpy.reflex3(M,verbose)) );
-
-
diff --git a/unittest/python/test_geometry.py b/unittest/python/test_geometry.py
index 1f2642a4fbedbb3af023abdef368dcc67804acaa..07d91a33fdd699b786989ba3953f2d9d3fc1fc8f 100644
--- a/unittest/python/test_geometry.py
+++ b/unittest/python/test_geometry.py
@@ -1,8 +1,10 @@
+from __future__ import print_function
+
 from geometry import *
 import numpy as np
 from numpy import cos,sin
 
-verbose = False
+verbose = True
 
 def isapprox(a,b,epsilon=1e-6):
     if issubclass(a.__class__,np.ndarray) and issubclass(b.__class__,np.ndarray):
@@ -33,18 +35,18 @@ assert(isapprox(q.coeffs(),qR.coeffs()))
 assert(isapprox(qR[3],1./np.sqrt(30)))
 try:
    qR[5]
-   print "Error, this message should not appear."
-except Exception,e:
-  if verbose: print "As expected, catched exception: ",e.message
+   print("Error, this message should not appear.")
+except RuntimeError as e:
+  if verbose: print("As expected, catched exception: ",e)
 
 # --- Angle Vector ------------------------------------------------
-r = AngleAxis(.1,np.array([1,0,0],np.double))
-if verbose: print "Rx(.1) = \n\n",r.matrix(),"\n"
+r = AngleAxis(.1,np.matrix([1,0,0],np.double).T)
+if verbose: print("Rx(.1) = \n\n",r.matrix(),"\n")
 assert( isapprox(r.matrix()[2,2],cos(r.angle)))
 assert( isapprox(r.axis,np.matrix("1;0;0")) )
 assert( isapprox(r.angle,0.1) )
 
-r.axis = np.array([0,1,0],np.double)
+r.axis = np.matrix([0,1,0],np.double).T
 assert( isapprox(r.matrix()[0,0],cos(r.angle)))
 
 ri = r.inverse()
@@ -62,10 +64,5 @@ assert( res==r.angle )
 
 qo = testOutQuaternion()
 assert(qo.__class__ == Quaternion)
-res = testInQuaternion_fx(q)
+res = testInQuaternion(q)
 assert(q.norm() == res)
-try:
-    testInQuaternion(q)
-    print "Error, this message should not appear."
-except:
-    if verbose: print "As expected, catch a Boost::python::ArgError exception."
diff --git a/unittest/python/test_matrix.py b/unittest/python/test_matrix.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad5490c0663ae306d2105653e1df189c5886b361
--- /dev/null
+++ b/unittest/python/test_matrix.py
@@ -0,0 +1,107 @@
+from __future__ import print_function
+
+import numpy as np
+import matrix as eigenpy
+
+verbose = True
+
+if verbose: print("===> From MatrixXd to Py")
+M = eigenpy.naturals(3,3,verbose)
+Mcheck = np.reshape(np.matrix(range(9),np.double),[3,3])
+assert np.array_equal(Mcheck,M)
+
+if verbose: print("===> From Matrix3d to Py")
+M33= eigenpy.naturals33(verbose)
+assert np.array_equal(Mcheck,M33)
+
+if verbose: print("===> From VectorXd to Py")
+v = eigenpy.naturalsX(3,verbose)
+vcheck = np.matrix([range(3),],np.double).T
+assert np.array_equal(vcheck ,v)
+
+if verbose: print("===> From Py to Eigen::MatrixXd")
+if verbose: print("===> From Py to Eigen::MatrixXd")
+if verbose: print("===> From Py to Eigen::MatrixXd")
+Mref = np.reshape(np.matrix(range(64),np.double),[8,8])
+
+if verbose: print("===> Matrix 8x8")
+M = Mref
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block 0:3x0:3")
+M = Mref[0:3,0:3]
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block 1:3x1:3")
+M = Mref[1:3,1:3]
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block 1:5:2x1:5:2")
+M = Mref[1:5:2,1:5:2]
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block 1:8:3x1:5")
+M = Mref[1:8:3,1:5]
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block transpose 1:8:3x1:6:2")
+M = Mref[1:8:3,0:6:2].T
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block Vector 1x0:6:2")
+M = Mref[1:2,0:6:2]
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block Vector 1x0:6:2 tanspose")
+M = Mref[1:2,0:6:2].T
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block Vector 0:6:2x1")
+M = Mref[0:6:2,1:2]
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> Block Vector 0:6:2x1 tanspose")
+M = Mref[0:6:2,1:2].T
+assert( np.array_equal(M,eigenpy.reflex(M,verbose)) );
+
+if verbose: print("===> From Py to Eigen::VectorXd")
+if verbose: print("===> From Py to Eigen::VectorXd")
+if verbose: print("===> From Py to Eigen::VectorXd")
+
+if verbose: print("===> Block Vector 0:6:2x1 1 dim")
+M = Mref[0:6:2,1].T
+# TODO
+# assert( np.array_equal(M.T,eigenpy.reflexV(M,verbose)) );
+
+if verbose: print("===> Block Vector 0:6:2x1")
+M = Mref[0:6:2,1:2]
+assert( np.array_equal(M,eigenpy.reflexV(M,verbose)) );
+
+if verbose: print("===> Block Vector 0:6:2x1 transpose")
+M = Mref[0:6:2,1:2].T
+# TODO
+# assert( np.array_equal(M.T,eigenpy.reflexV(M,verbose)) );
+
+if verbose: print("===> From Py to Eigen::Matrix3d")
+if verbose: print("===> From Py to Eigen::Matrix3d")
+if verbose: print("===> From Py to Eigen::Matrix3d")
+
+if verbose: print("===> Block Vector 0:3x0:6:2 ")
+M = Mref[0:3,0:6:2]
+assert( np.array_equal(M,eigenpy.reflex33(M,verbose)) );
+
+if verbose: print("===> Block Vector 0:3x0:6:2 T")
+M = Mref[0:3,0:6].T
+# TODO
+# try:
+    # assert( np.array_equal(M,eigenpy.reflex33(M,verbose)) );
+# except eigenpy.Exception as e:
+    # if verbose: print("As expected, got the following /ROW/ error:", e.message)
+
+if verbose: print("===> From Py to Eigen::Vector3d")
+if verbose: print("===> From Py to Eigen::Vector3d")
+if verbose: print("===> From Py to Eigen::Vector3d")
+
+# TODO
+# M = Mref[0:3,1:2]
+# assert( np.array_equal(M,eigenpy.reflex3(M,verbose)) );