Index: src/tests/pybin/settings.py
===================================================================
--- src/tests/pybin/settings.py	(revision bacc36ce667997c9f0ed8001bf0911d0fe811ee1)
+++ src/tests/pybin/settings.py	(revision bacc36ce667997c9f0ed8001bf0911d0fe811ee1)
@@ -0,0 +1,28 @@
+
+class Architecture:
+	def __init__(self, cmd):
+		if cmd:
+			self.cross_compile = True
+			self.target = cmd
+		else:
+			self.cross_compile = False
+			self.target = 'x64'
+
+	def toString(self):
+		return self.target
+
+def init( options ):
+	global arch
+	global dry_run
+	global generating
+	global make
+
+	arch       = Architecture(options.arch)
+	dry_run    = options.dry_run
+	generating = options.regenerate_expected
+	make       = './make_command_not_initialized'
+
+def updateMakeCmd(force, jobs):
+	global make
+
+	make = "make" if force else ("make -j%i" % jobs)
Index: src/tests/pybin/test_run.py
===================================================================
--- src/tests/pybin/test_run.py	(revision 0ad0c55af834cd60b4644acba851192ddc6839bd)
+++ src/tests/pybin/test_run.py	(revision bacc36ce667997c9f0ed8001bf0911d0fe811ee1)
@@ -3,4 +3,5 @@
 from pybin.tools import *
 
+import pybin.settings
 
 # Test class that defines what a test is
@@ -14,21 +15,53 @@
 		return "{:25s} ({:5s} {:s})".format( self.name, self.arch if self.arch else "Any", self.target() )
 
-	def prepare(self, dry_run):
-		sh("mkdir -p %s" % os.path.join(self.path, '.err'), dry_run)
-		sh("mkdir -p %s" % os.path.join(self.path, '.out'), dry_run)
-		sh("mkdir -p %s" % os.path.join(self.path, '.in' ), dry_run)
+	def prepare(self):
+		sh("mkdir -p %s" % os.path.join(self.path, '.err'))
+		sh("mkdir -p %s" % os.path.join(self.path, '.out'))
+		sh("mkdir -p %s" % os.path.join(self.path, '.in' ))
 
-	def expect_file(self):
+	def expect(self):
 		return ("%s/.expect/%s.txt" % (self.path, self.name))
 
-	def error_file(self):
+	def error_log(self):
 		return ("%s/.err/%s.log"    % (self.path, self.name))
 
-	def output_file(self):
+	def output_log(self):
 		return ("%s/.out/%s.log"    % (self.path, self.name))
 
-	def input_file(self):
+	def input(self):
 		return ("%s/.in/%s.txt"     % (self.path, self.name))
+
+	def target_output(self):
+		return self.output_log() if not settings.generating else self.expect()
 
 	def target(self):
 		return os.path.join(self.path, self.name)
+
+	@classmethod
+	def valid_name(cls, name):
+		return not name.endswith( ('.c', '.cc', '.cpp', '.cfa') )
+
+	@classmethod
+	def from_target(cls, target):
+		test = Test()
+		test.name = os.path.basename(target)
+		test.path = os.path.dirname (target)
+		test.arch = settings.arch.toString() if settings.arch.cross_compile else ''
+		return test
+
+
+class TestResult:
+	SUCCESS = 0
+	FAILURE = 1
+	TIMEOUT = 124
+
+	@classmethod
+	def toString( cls, retcode ):
+		if settings.generating :
+			if   retcode == TestResult.SUCCESS: 	return "Done"
+			elif retcode == TestResult.TIMEOUT: 	return "TIMEOUT"
+			else :						return "ERROR code %d" % retcode
+		else :
+			if   retcode == TestResult.SUCCESS: 	return "PASSED"
+			elif retcode == TestResult.TIMEOUT: 	return "TIMEOUT"
+			else :						return "FAILED with code %d" % retcode
Index: src/tests/pybin/tools.py
===================================================================
--- src/tests/pybin/tools.py	(revision 0ad0c55af834cd60b4644acba851192ddc6839bd)
+++ src/tests/pybin/tools.py	(revision bacc36ce667997c9f0ed8001bf0911d0fe811ee1)
@@ -1,16 +1,33 @@
+from __future__ import print_function
+
 import __main__
 import argparse
+import multiprocessing
 import os
 import re
+import signal
 import stat
-
+import sys
+
+from pybin import settings
 from subprocess import Popen, PIPE, STDOUT
 
+################################################################################
+#               shell helpers
+################################################################################
+
 # helper functions to run terminal commands
-def sh(cmd, dry_run = False, print2stdout = True):
-	if dry_run : 	# if this is a dry_run, only print the commands that would be ran
+def sh(cmd, print2stdout = True, input = None):
+	# add input redirection if needed
+	if input and os.path.isfile(input):
+		cmd += " < %s" % input
+
+	# if this is a dry_run, only print the commands that would be ran
+	if settings.dry_run :
 		print("cmd: %s" % cmd)
 		return 0, None
-	else :			# otherwise create a pipe and run the desired command
+
+	# otherwise create a pipe and run the desired command
+	else :
 		proc = Popen(cmd, stdout=None if print2stdout else PIPE, stderr=STDOUT, shell=True)
 		out, err = proc.communicate()
@@ -18,10 +35,10 @@
 
 # Remove 1 or more files silently
-def rm( files, dry_run = False ):
+def rm( files ):
 	try:
 		for file in files:
-			sh("rm -f %s > /dev/null 2>&1" % file, dry_run)
+			sh("rm -f %s > /dev/null 2>&1" % file )
 	except TypeError:
-		sh("rm -f %s > /dev/null 2>&1" % files, dry_run)
+		sh("rm -f %s > /dev/null 2>&1" % files )
 
 def chdir( dest = __main__.__file__ ):
@@ -30,50 +47,6 @@
 	os.chdir(dname)
 
-# helper function to replace patterns in a file
-def file_replace(fname, pat, s_after):
-    # first, see if the pattern is even in the file.
-    with open(fname) as f:
-        if not any(re.search(pat, line) for line in f):
-            return # pattern does not occur in file so we are done.
-
-    # pattern is in the file, so perform replace operation.
-    with open(fname) as f:
-        out_fname = fname + ".tmp"
-        out = open(out_fname, "w")
-        for line in f:
-            out.write(re.sub(pat, s_after, line))
-        out.close()
-        os.rename(out_fname, fname)
-
-# helper function to check if a files contains only a specific string
-def fileContainsOnly(file, text) :
-	with open(file) as f:
-		ff = f.read().strip()
-		result = ff == text.strip()
-
-		return result;
-
-# check whether or not a file is executable
-def fileIsExecutable(file) :
-	try :
-		fileinfo = os.stat(file)
-		return bool(fileinfo.st_mode & stat.S_IXUSR)
-	except Exception as inst:
-		print(type(inst))    # the exception instance
-		print(inst.args)     # arguments stored in .args
-		print(inst)
-		return False
-
-# check if arguments is yes or no
-def yes_no(string):
-	if string == "yes" :
-		return True
-	if string == "no" :
-		return False
-	raise argparse.ArgumentTypeError(msg)
-	return False
-
 # diff two files
-def diff( lhs, rhs, dry_run ):
+def diff( lhs, rhs ):
 	# diff the output of the files
 	diff_cmd = ("diff --ignore-all-space "
@@ -94,5 +67,80 @@
 
 	# fetch return code and error from the diff command
-	return sh(diff_cmd % (lhs, rhs), dry_run, False)
+	return sh(diff_cmd % (lhs, rhs), False)
+
+# call make
+def make(target, flags = '', redirects = '', error_file = None, silent = False):
+	test_param = """test="%s" """ % (error_file) if error_file else ''
+	cmd = ' '.join([
+		settings.make,
+		'-s' if silent else '',
+		test_param,
+		flags,
+		target,
+		redirects
+	])
+	return sh(cmd)
+
+################################################################################
+#               file handling
+################################################################################
+
+# helper function to replace patterns in a file
+def file_replace(fname, pat, s_after):
+    # first, see if the pattern is even in the file.
+    with open(fname) as f:
+        if not any(re.search(pat, line) for line in f):
+            return # pattern does not occur in file so we are done.
+
+    # pattern is in the file, so perform replace operation.
+    with open(fname) as f:
+        out_fname = fname + ".tmp"
+        out = open(out_fname, "w")
+        for line in f:
+            out.write(re.sub(pat, s_after, line))
+        out.close()
+        os.rename(out_fname, fname)
+
+# helper function to check if a files contains only a specific string
+def fileContainsOnly(file, text) :
+	with open(file) as f:
+		ff = f.read().strip()
+		result = ff == text.strip()
+
+		return result;
+
+# check whether or not a file is executable
+def fileIsExecutable(file) :
+	try :
+		fileinfo = os.stat(file)
+		return bool(fileinfo.st_mode & stat.S_IXUSR)
+	except Exception as inst:
+		print(type(inst))    # the exception instance
+		print(inst.args)     # arguments stored in .args
+		print(inst)
+		return False
+
+# transform path to canonical form
+def canonicalPath(path):
+	return os.path.join('.', os.path.normpath(path) )
+
+# compare path even if form is different
+def pathCmp(lhs, rhs):
+	return canonicalPath( lhs ) == canonicalPath( rhs )
+
+# walk all files in a path
+def pathWalk( op ):
+	def step(_, dirname, names):
+		for name in names:
+			path = os.path.join(dirname, name)
+
+			op( path )
+
+	# Start the walk
+	os.path.walk('.', step, '')
+
+################################################################################
+#               system
+################################################################################
 
 # parses the Makefile to find the machine type (32-bit / 64-bit)
@@ -100,5 +148,5 @@
 	return 'x64'
 	sh('echo "void ?{}(int&a,int b){}int main(){return 0;}" > .dummy.c')
-	ret, out = sh("make .dummy -s", print2stdout=True)
+	ret, out = make('.dummy', silent = True)
 
 	if ret != 0:
@@ -114,2 +162,52 @@
 	return out
 	return re.search("ELF\s([0-9]+)-bit", out).group(1)
+
+# count number of jobs to create
+def jobCount( options, tests ):
+	# check if the user already passed in a number of jobs for multi-threading
+	make_flags = os.environ.get('MAKEFLAGS')
+	make_jobs_fds = re.search("--jobserver-(auth|fds)=\s*([0-9]+),([0-9]+)", make_flags) if make_flags else None
+	if make_jobs_fds :
+		tokens = os.read(int(make_jobs_fds.group(2)), 1024)
+		options.jobs = len(tokens)
+		os.write(int(make_jobs_fds.group(3)), tokens)
+	else :
+		options.jobs = multiprocessing.cpu_count()
+
+	# make sure we have a valid number of jobs that corresponds to user input
+	if options.jobs <= 0 :
+		print('ERROR: Invalid number of jobs', file=sys.stderr)
+		sys.exit(1)
+
+	return min( options.jobs, len(tests) ), True if make_flags else False
+
+# setup a proper processor pool with correct signal handling
+def setupPool(jobs):
+	original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+	pool = multiprocessing.Pool(jobs)
+	signal.signal(signal.SIGINT, original_sigint_handler)
+
+	return pool
+
+# handle signals in scope
+class SignalHandling():
+	def __enter__(self):
+		# enable signal handling
+	    	signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+	def __exit__(self, type, value, traceback):
+		# disable signal handling
+		signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+################################################################################
+#               misc
+################################################################################
+
+# check if arguments is yes or no
+def yes_no(string):
+	if string == "yes" :
+		return True
+	if string == "no" :
+		return False
+	raise argparse.ArgumentTypeError(msg)
+	return False
