Index: tests/pybin/tools.py
===================================================================
--- tests/pybin/tools.py	(revision ea2074e147f08bec7cc5895e33bf66c399fd9d89)
+++ tests/pybin/tools.py	(revision 09f357ec00aefca93e05dd3cc0f865d2b3fe0881)
@@ -24,50 +24,62 @@
 # helper functions to run terminal commands
 def sh(*cmd, timeout = False, output_file = None, input_file = None, input_text = None, error = subprocess.STDOUT, ignore_dry_run = False):
-	cmd = list(cmd)
-
-	if input_file and input_text:
-		return 401, "Cannot use both text and file inputs"
-
-	# if this is a dry_run, only print the commands that would be ran
-	if settings.dry_run and not ignore_dry_run:
-		cmd = "{} cmd: {}".format(os.getcwd(), ' '.join(cmd))
-		if output_file and not isinstance(output_file, int):
-			cmd += " > "
-			cmd += output_file
-
-		if error and not isinstance(error, int):
-			cmd += " 2> "
-			cmd += error
-
-		if input_file and not isinstance(input_file, int) and os.path.isfile(input_file):
-			cmd += " < "
-			cmd += input_file
-
-		print(cmd)
-		return 0, None
-
-	with contextlib.ExitStack() as onexit:
-		# add input redirection if needed
-		input_file = openfd(input_file, 'r', onexit, True)
-
-		# add output redirection if needed
-		output_file = openfd(output_file, 'w', onexit, False)
-
-		# add error redirection if needed
-		error = openfd(error, 'w', onexit, False)
-
-		# run the desired command
-		try:
-			proc = subprocess.run(
+	try:
+		cmd = list(cmd)
+
+		if input_file and input_text:
+			return 401, "Cannot use both text and file inputs"
+
+		# if this is a dry_run, only print the commands that would be ran
+		if settings.dry_run and not ignore_dry_run:
+			cmd = "{} cmd: {}".format(os.getcwd(), ' '.join(cmd))
+			if output_file and not isinstance(output_file, int):
+				cmd += " > "
+				cmd += output_file
+
+			if error and not isinstance(error, int):
+				cmd += " 2> "
+				cmd += error
+
+			if input_file and not isinstance(input_file, int) and os.path.isfile(input_file):
+				cmd += " < "
+				cmd += input_file
+
+			print(cmd)
+			return 0, None
+
+		with contextlib.ExitStack() as onexit:
+			# add input redirection if needed
+			input_file = openfd(input_file, 'r', onexit, True)
+
+			# add output redirection if needed
+			output_file = openfd(output_file, 'w', onexit, False)
+
+			# add error redirection if needed
+			error = openfd(error, 'w', onexit, False)
+
+			# run the desired command
+			# use with statement to make sure proc is cleaned
+			# don't use subprocess.run because we want to send SIGABRT on exit
+			with subprocess.Popen(
 				cmd,
 				**({'input' : bytes(input_text, encoding='utf-8')} if input_text else {'stdin' : input_file}),
 				stdout  = output_file,
-				stderr  = error,
-				timeout = settings.timeout.single if timeout else None
-			)
-
-			return proc.returncode, proc.stdout.decode("utf-8") if proc.stdout else None
-		except subprocess.TimeoutExpired:
-			return 124, str(None)
+				stderr  = error
+			) as proc:
+
+				try:
+					out, _ = proc.communicate(
+						timeout = settings.timeout.single if timeout else None
+					)
+
+					return proc.returncode, out.decode("utf-8") if out else None
+				except subprocess.TimeoutExpired:
+					proc.send_signal(signal.SIGABRT)
+					proc.communicate()
+					return 124, str(None)
+
+	except Exception as ex:
+		print ("Unexpected error: %s" % ex)
+		raise
 
 def is_ascii(fname):
