Index: tests/pybin/test_run.py
===================================================================
--- tests/pybin/test_run.py	(revision 4d23dd2e7dcddf0d36dcf4f5dce569634bce9d2e)
+++ tests/pybin/test_run.py	(revision 76de075ab15b34a5f9bd5ec67d323d1c79114dde)
@@ -69,13 +69,4 @@
 			else :						text = "FAILED with code %d" % retcode
 
-		text += "    C%s - R%s" % (cls.fmtDur(duration[0]), cls.fmtDur(duration[1]))
+		text += "    C%s - R%s" % (fmtDur(duration[0]), fmtDur(duration[1]))
 		return text
-
-	@staticmethod
-	def fmtDur( duration ):
-		if duration :
-			hours, rem = divmod(duration, 3600)
-			minutes, rem = divmod(rem, 60)
-			seconds, millis = divmod(rem, 1)
-			return "%2d:%02d.%03d" % (minutes, seconds, millis * 1000)
-		return " n/a"
Index: tests/pybin/tools.py
===================================================================
--- tests/pybin/tools.py	(revision 4d23dd2e7dcddf0d36dcf4f5dce569634bce9d2e)
+++ tests/pybin/tools.py	(revision 76de075ab15b34a5f9bd5ec67d323d1c79114dde)
@@ -387,2 +387,10 @@
 		while True:
 			yield i.next(max(expire - time.time(), 0))
+
+def fmtDur( duration ):
+	if duration :
+		hours, rem = divmod(duration, 3600)
+		minutes, rem = divmod(rem, 60)
+		seconds, millis = divmod(rem, 1)
+		return "%2d:%02d.%03d" % (minutes, seconds, millis * 1000)
+	return " n/a"
Index: tests/test.py
===================================================================
--- tests/test.py	(revision 4d23dd2e7dcddf0d36dcf4f5dce569634bce9d2e)
+++ tests/test.py	(revision 76de075ab15b34a5f9bd5ec67d323d1c79114dde)
@@ -361,34 +361,35 @@
 
 		# for each build configurations, run the test
-		for arch, debug, install in itertools.product(settings.all_arch, settings.all_debug, settings.all_install):
-			settings.arch    = arch
-			settings.debug   = debug
-			settings.install = install
-
-			# filter out the tests for a different architecture
-			# tests are the same across debug/install
-			local_tests = settings.arch.filter( tests )
-			options.jobs, forceJobs = job_count( options, local_tests )
-			settings.update_make_cmd(forceJobs, options.jobs)
-
-			# check the build configuration works
-			settings.validate()
-
-			# print configuration
-			print('%s %i tests on %i cores (%s:%s)' % (
-				'Regenerating' if settings.generating else 'Running',
-				len(local_tests),
-				options.jobs,
-				settings.arch.string,
-				settings.debug.string
-			))
-
-			# otherwise run all tests and make sure to return the correct error code
-			failed = run_tests(local_tests, options.jobs)
-			if failed:
-				result = 1
-				if not settings.continue_:
-					break
-
-
+		with Timed() as total_dur:
+			for arch, debug, install in itertools.product(settings.all_arch, settings.all_debug, settings.all_install):
+				settings.arch    = arch
+				settings.debug   = debug
+				settings.install = install
+
+				# filter out the tests for a different architecture
+				# tests are the same across debug/install
+				local_tests = settings.arch.filter( tests )
+				options.jobs, forceJobs = job_count( options, local_tests )
+				settings.update_make_cmd(forceJobs, options.jobs)
+
+				# check the build configuration works
+				settings.validate()
+
+				# print configuration
+				print('%s %i tests on %i cores (%s:%s)' % (
+					'Regenerating' if settings.generating else 'Running',
+					len(local_tests),
+					options.jobs,
+					settings.arch.string,
+					settings.debug.string
+				))
+
+				# otherwise run all tests and make sure to return the correct error code
+				failed = run_tests(local_tests, options.jobs)
+				if failed:
+					result = 1
+					if not settings.continue_:
+						break
+
+		print('Tests took %s' % fmtDur( total_dur.duration ))
 		sys.exit( failed )
