Index: Jenkinsfile
===================================================================
--- Jenkinsfile	(revision bd87a9ad8b09259bd23d09b16c0739c92a40d5f6)
+++ Jenkinsfile	(revision 256728f45d421a919828a8272b7cf937c0a46ae9)
@@ -2,4 +2,7 @@
 
 import groovy.transform.Field
+
+// For skipping stages
+import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
 
 //===========================================================================================================
@@ -82,5 +85,5 @@
 //===========================================================================================================
 def clean() {
-	build_stage('Cleanup') {
+	build_stage('Cleanup', true) {
 		// clean the build by wipping the build directory
 		dir(BuildDir) {
@@ -92,5 +95,5 @@
 //Compilation script is done here but environnement set-up and error handling is done in main loop
 def checkout() {
-	build_stage('Checkout') {
+	build_stage('Checkout', true) {
 		//checkout the source code and clean the repo
 		final scmVars = checkout scm
@@ -103,5 +106,5 @@
 
 def build() {
-	build_stage('Build') {
+	build_stage('Build', true) {
 		// Build outside of the src tree to ease cleaning
 		dir (BuildDir) {
@@ -125,15 +128,16 @@
 
 def test() {
-	build_stage('Test') {
-
+	build_stage('Test: short', !Settings.RunAllTests) {
 		dir (BuildDir) {
 			//Run the tests from the tests directory
-			if ( Settings.RunAllTests ) {
-				sh 'make --no-print-directory -C tests timeouts="--timeout=1200" all-tests debug=yes'
-				sh 'make --no-print-directory -C tests timeouts="--timeout=1200" all-tests debug=no '
-			}
-			else {
-				sh 'make --no-print-directory -C tests'
-			}
+			sh 'make --no-print-directory -C tests'
+		}
+	}
+
+	build_stage('Test: full', Settings.RunAllTests) {
+		dir (BuildDir) {
+			//Run the tests from the tests directory
+			sh 'make --no-print-directory -C tests timeouts="--timeout=600 --global-timeout=14400" all-tests debug=yes'
+			sh 'make --no-print-directory -C tests timeouts="--timeout=600 --global-timeout=14400" all-tests debug=no '
 		}
 	}
@@ -141,8 +145,5 @@
 
 def benchmark() {
-	build_stage('Benchmark') {
-
-		if( !Settings.RunBenchmark ) return
-
+	build_stage('Benchmark', Settings.RunBenchmark) {
 		dir (BuildDir) {
 			//Append bench results
@@ -153,8 +154,5 @@
 
 def build_doc() {
-	build_stage('Documentation') {
-
-		if( !Settings.BuildDocumentation ) return
-
+	build_stage('Documentation', Settings.BuildDocumentation) {
 		dir ('doc/user') {
 			make_doc()
@@ -168,5 +166,5 @@
 
 def publish() {
-	build_stage('Publish') {
+	build_stage('Publish', true) {
 
 		if( Settings.Publish && !Settings.RunBenchmark ) { echo 'No results to publish!!!' }
@@ -412,8 +410,12 @@
 }
 
-def build_stage(String name, Closure block ) {
+def build_stage(String name, boolean run, Closure block ) {
 	StageName = name
 	echo " -------- ${StageName} -------- "
-	stage(name, block)
+	if(run) {
+		stage(name, block)
+	} else {
+		stage(name) { Utils.markStageSkippedForConditional(STAGE_NAME) }
+	}
 }
 
Index: tests/pybin/tools.py
===================================================================
--- tests/pybin/tools.py	(revision bd87a9ad8b09259bd23d09b16c0739c92a40d5f6)
+++ tests/pybin/tools.py	(revision 256728f45d421a919828a8272b7cf937c0a46ae9)
@@ -179,4 +179,15 @@
 			os.chdir(cwd)
 
+def killgroup():
+	try:
+		os.killpg(os.getpgrp(), signal.SIGINT)
+	except KeyboardInterrupt:
+		pass # expected
+	except Exception as exc:
+		print("Unexpected exception", file=sys.stderr)
+		print(exc, file=sys.stderr)
+		sys.stderr.flush()
+		sys.exit(2)
+
 ################################################################################
 #               file handling
@@ -301,2 +312,8 @@
         self.end = time.time()
         self.duration = self.end - self.start
+
+def timed(src, timeout):
+	expire = time.time() + timeout
+	i = iter(src)
+	while True:
+		yield i.next(max(expire - time.time(), 0))
Index: tests/test.py
===================================================================
--- tests/test.py	(revision bd87a9ad8b09259bd23d09b16c0739c92a40d5f6)
+++ tests/test.py	(revision 256728f45d421a919828a8272b7cf937c0a46ae9)
@@ -202,13 +202,13 @@
 		if error :
 			text = text + '\n' + error
-			out = sys.stderr
-
-		print(text, file = out)
-		sys.stdout.flush()
+
+		return retcode == TestResult.SUCCESS, text
+	except KeyboardInterrupt:
+		return False, ""
+	except:
+		print("Unexpected error in worker thread", file=sys.stderr)
 		sys.stderr.flush()
-
-		return retcode != TestResult.SUCCESS
-	except KeyboardInterrupt:
-		False
+		return False, ""
+
 
 # run the given list of tests with the given parameters
@@ -220,24 +220,43 @@
 	pool = multiprocessing.Pool(jobs)
 
+	failed = False
+
 	# for each test to run
 	try :
-		results = pool.map_async(
+		num = len(tests)
+		fancy = sys.stdout.isatty()
+		results = pool.imap_unordered(
 			run_test_worker,
 			tests,
 			chunksize = 1
-		).get(settings.timeout.total)
+		)
+
+		for i, (succ, txt) in enumerate(timed(results, timeout = settings.timeout.total), 1) :
+			if not succ :
+				failed = True
+
+			print("       " + txt)
+
+			if(fancy and i != num):
+				print("%d/%d" % (i, num), end='\r')
+				sys.stdout.flush()
+
 	except KeyboardInterrupt:
+		print("Tests interrupted by user", file=sys.stderr)
 		pool.terminate()
-		print("Tests interrupted by user")
-		sys.exit(1)
+		pool.join()
+		failed = True
+	except multiprocessing.TimeoutError:
+		print("ERROR: Test suite timed out", file=sys.stderr)
+		pool.terminate()
+		pool.join()
+		failed = True
+		killgroup() # needed to cleanly kill all children
+
 
 	# clean the workspace
 	make('clean', output=subprocess.DEVNULL, error=subprocess.DEVNULL)
 
-	for failed in results:
-		if failed :
-			return 1
-
-	return 0
+	return 1 if failed else 0
 
 
