4141import tempfile
4242import textwrap
4343import time
44+ from concurrent .futures import ThreadPoolExecutor
4445from test .framework .utilities import EnhancedTestCase , TestLoaderFiltered , init_config
4546from unittest import TextTestRunner
4647from easybuild .base .fancylogger import setLogLevelDebug
@@ -248,7 +249,7 @@ def test_run_shell_cmd_log(self):
248249 fd , logfile = tempfile .mkstemp (suffix = '.log' , prefix = 'eb-test-' )
249250 os .close (fd )
250251
251- regex_start_cmd = re .compile ("Running command 'echo hello' in /" )
252+ regex_start_cmd = re .compile ("Running shell command 'echo hello' in /" )
252253 regex_cmd_exit = re .compile (r"Shell command completed successfully \(see output above\): echo hello" )
253254
254255 # command output is always logged
@@ -448,7 +449,7 @@ def test_run_cmd_work_dir(self):
448449
449450 def test_run_shell_cmd_work_dir (self ):
450451 """
451- Test running command in specific directory with run_shell_cmd function.
452+ Test running shell command in specific directory with run_shell_cmd function.
452453 """
453454 orig_wd = os .getcwd ()
454455 self .assertFalse (os .path .samefile (orig_wd , self .test_prefix ))
@@ -615,11 +616,11 @@ def test_run_shell_cmd_trace(self):
615616 """Test run_shell_cmd function in trace mode, and with tracing disabled."""
616617
617618 pattern = [
618- r"^ >> running command:" ,
619+ r"^ >> running shell command:" ,
620+ r"\techo hello" ,
619621 r"\t\[started at: .*\]" ,
620622 r"\t\[working dir: .*\]" ,
621623 r"\t\[output saved to .*\]" ,
622- r"\techo hello" ,
623624 r" >> command completed: exit 0, ran in .*" ,
624625 ]
625626
@@ -675,11 +676,11 @@ def test_run_shell_cmd_trace_stdin(self):
675676 init_config (build_options = {'trace' : True })
676677
677678 pattern = [
678- r"^ >> running command:" ,
679+ r"^ >> running shell command:" ,
680+ r"\techo hello" ,
679681 r"\t\[started at: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\]" ,
680682 r"\t\[working dir: .*\]" ,
681683 r"\t\[output saved to .*\]" ,
682- r"\techo hello" ,
683684 r" >> command completed: exit 0, ran in .*" ,
684685 ]
685686
@@ -707,8 +708,8 @@ def test_run_shell_cmd_trace_stdin(self):
707708 self .assertEqual (res .output , 'hello' )
708709 self .assertEqual (res .exit_code , 0 )
709710 self .assertEqual (stderr , '' )
710- pattern .insert (3 , r"\t\[input: hello\]" )
711- pattern [- 2 ] = "\t cat"
711+ pattern .insert (4 , r"\t\[input: hello\]" )
712+ pattern [1 ] = "\t cat"
712713 regex = re .compile ('\n ' .join (pattern ))
713714 self .assertTrue (regex .search (stdout ), "Pattern '%s' found in: %s" % (regex .pattern , stdout ))
714715
@@ -909,7 +910,8 @@ def test_run_shell_cmd_cache(self):
909910 # inject value into cache to check whether executing command again really returns cached value
910911 with self .mocked_stdout_stderr ():
911912 cached_res = RunShellCmdResult (cmd = cmd , output = "123456" , exit_code = 123 , stderr = None ,
912- work_dir = '/test_ulimit' , out_file = '/tmp/foo.out' , err_file = None )
913+ work_dir = '/test_ulimit' , out_file = '/tmp/foo.out' , err_file = None ,
914+ thread_id = None )
913915 run_shell_cmd .update_cache ({(cmd , None ): cached_res })
914916 res = run_shell_cmd (cmd )
915917 self .assertEqual (res .cmd , cmd )
@@ -928,7 +930,8 @@ def test_run_shell_cmd_cache(self):
928930 # inject different output for cat with 'foo' as stdin to check whether cached value is used
929931 with self .mocked_stdout_stderr ():
930932 cached_res = RunShellCmdResult (cmd = cmd , output = "bar" , exit_code = 123 , stderr = None ,
931- work_dir = '/test_cat' , out_file = '/tmp/cat.out' , err_file = None )
933+ work_dir = '/test_cat' , out_file = '/tmp/cat.out' , err_file = None ,
934+ thread_id = None )
932935 run_shell_cmd .update_cache ({(cmd , 'foo' ): cached_res })
933936 res = run_shell_cmd (cmd , stdin = 'foo' )
934937 self .assertEqual (res .cmd , cmd )
@@ -1006,7 +1009,7 @@ def test_run_shell_cmd_dry_run(self):
10061009 self .assertEqual (res .output , '' )
10071010 self .assertEqual (res .stderr , None )
10081011 # check dry run output
1009- expected = """ running command "somecommand foo 123 bar"\n """
1012+ expected = """ running shell command "somecommand foo 123 bar"\n """
10101013 self .assertIn (expected , stdout )
10111014
10121015 # check enabling 'hidden'
@@ -1029,7 +1032,7 @@ def test_run_shell_cmd_dry_run(self):
10291032 fail_on_error = False , in_dry_run = True )
10301033 stdout = self .get_stdout ()
10311034 self .mock_stdout (False )
1032- self .assertNotIn ('running command "' , stdout )
1035+ self .assertNotIn ('running shell command "' , stdout )
10331036 self .assertNotEqual (res .exit_code , 0 )
10341037 self .assertEqual (res .output , 'done\n ' )
10351038 self .assertEqual (res .stderr , None )
@@ -1207,7 +1210,7 @@ def test_run_cmd_async(self):
12071210 "for i in $(seq 1 50)" ,
12081211 "do sleep 0.1" ,
12091212 "for j in $(seq 1000)" ,
1210- "do echo foo" ,
1213+ "do echo foo${i}${j} " ,
12111214 "done" ,
12121215 "done" ,
12131216 "echo done" ,
@@ -1257,8 +1260,68 @@ def test_run_cmd_async(self):
12571260 res = check_async_cmd (* cmd_info , output = res ['output' ])
12581261 self .assertEqual (res ['done' ], True )
12591262 self .assertEqual (res ['exit_code' ], 0 )
1260- self .assertTrue (res ['output' ].startswith ('start\n ' ))
1261- self .assertTrue (res ['output' ].endswith ('\n done\n ' ))
1263+ self .assertEqual (len (res ['output' ]), 435661 )
1264+ self .assertTrue (res ['output' ].startswith ('start\n foo11\n foo12\n ' ))
1265+ self .assertTrue ('\n foo49999\n foo491000\n foo501\n ' in res ['output' ])
1266+ self .assertTrue (res ['output' ].endswith ('\n foo501000\n done\n ' ))
1267+
1268+ def test_run_shell_cmd_async (self ):
1269+ """Test asynchronously running of a shell command via run_shell_cmd """
1270+
1271+ thread_pool = ThreadPoolExecutor ()
1272+
1273+ os .environ ['TEST' ] = 'test123'
1274+ env = os .environ .copy ()
1275+
1276+ test_cmd = "echo 'sleeping...'; sleep 2; echo $TEST"
1277+ task = thread_pool .submit (run_shell_cmd , test_cmd , hidden = True , asynchronous = True , env = env )
1278+
1279+ # change value of $TEST to check that command is completed with correct environment
1280+ os .environ ['TEST' ] = 'some_other_value'
1281+
1282+ # initial poll should result in None, since it takes a while for the command to complete
1283+ self .assertEqual (task .done (), False )
1284+
1285+ # wait until command is done
1286+ while not task .done ():
1287+ time .sleep (1 )
1288+ res = task .result ()
1289+
1290+ self .assertEqual (res .exit_code , 0 )
1291+ self .assertEqual (res .output , 'sleeping...\n test123\n ' )
1292+
1293+ # check asynchronous running of failing command
1294+ error_test_cmd = "echo 'FAIL!' >&2; exit 123"
1295+ task = thread_pool .submit (run_shell_cmd , error_test_cmd , hidden = True , fail_on_error = False , asynchronous = True )
1296+ time .sleep (1 )
1297+ res = task .result ()
1298+ self .assertEqual (res .exit_code , 123 )
1299+ self .assertEqual (res .output , "FAIL!\n " )
1300+ self .assertTrue (res .thread_id )
1301+
1302+ # also test with a command that produces a lot of output,
1303+ # since that tends to lock up things unless we frequently grab some output...
1304+ verbose_test_cmd = ';' .join ([
1305+ "echo start" ,
1306+ "for i in $(seq 1 50)" ,
1307+ "do sleep 0.1" ,
1308+ "for j in $(seq 1000)" ,
1309+ "do echo foo${i}${j}" ,
1310+ "done" ,
1311+ "done" ,
1312+ "echo done" ,
1313+ ])
1314+ task = thread_pool .submit (run_shell_cmd , verbose_test_cmd , hidden = True , asynchronous = True )
1315+
1316+ while not task .done ():
1317+ time .sleep (1 )
1318+ res = task .result ()
1319+
1320+ self .assertEqual (res .exit_code , 0 )
1321+ self .assertEqual (len (res .output ), 435661 )
1322+ self .assertTrue (res .output .startswith ('start\n foo11\n foo12\n ' ))
1323+ self .assertTrue ('\n foo49999\n foo491000\n foo501\n ' in res .output )
1324+ self .assertTrue (res .output .endswith ('\n foo501000\n done\n ' ))
12621325
12631326 def test_check_log_for_errors (self ):
12641327 fd , logfile = tempfile .mkstemp (suffix = '.log' , prefix = 'eb-test-' )
@@ -1373,7 +1436,7 @@ def post_run_shell_cmd_hook(cmd, *args, **kwargs):
13731436
13741437 def test_run_shell_cmd_with_hooks (self ):
13751438 """
1376- Test running command with run_shell_cmd function with pre/post run_shell_cmd hooks in place.
1439+ Test running shell command with run_shell_cmd function with pre/post run_shell_cmd hooks in place.
13771440 """
13781441 cwd = os .getcwd ()
13791442
0 commit comments