4747import easybuild .tools .asyncprocess as asyncprocess
4848import easybuild .tools .utilities
4949from easybuild .tools .build_log import EasyBuildError , init_logging , stop_logging
50- from easybuild .tools .filetools import adjust_permissions , read_file , write_file
50+ from easybuild .tools .filetools import adjust_permissions , mkdir , read_file , write_file
5151from easybuild .tools .run import check_async_cmd , check_log_for_errors , complete_cmd , get_output_from_process
52- from easybuild .tools .run import parse_log_for_error , run_cmd , run_cmd_qa , subprocess_terminate
52+ from easybuild .tools .run import parse_log_for_error , run , run_cmd , run_cmd_qa , subprocess_terminate
5353from easybuild .tools .config import ERROR , IGNORE , WARN
5454
5555
@@ -159,6 +159,32 @@ def test_run_cmd(self):
159159 self .assertTrue (out .startswith ('foo ' ) and out .endswith (' bar' ))
160160 self .assertEqual (type (out ), str )
161161
162+ def test_run_basic (self ):
163+ """Basic test for run function."""
164+
165+ with self .mocked_stdout_stderr ():
166+ res = run ("echo hello" )
167+ self .assertEqual (res .output , "hello\n " )
168+ # no reason echo hello could fail
169+ self .assertEqual (res .exit_code , 0 )
170+ self .assertEqual (type (res .output ), str )
171+
172+ # test running command that emits non-UTF-8 characters
173+ # this is constructed to reproduce errors like:
174+ # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe2
175+ # UnicodeEncodeError: 'ascii' codec can't encode character u'\u2018'
176+ # (such errors are ignored by the 'run' implementation)
177+ for text in [b"foo \xe2 bar" , b"foo \u2018 bar" ]:
178+ test_file = os .path .join (self .test_prefix , 'foo.txt' )
179+ write_file (test_file , text )
180+ cmd = "cat %s" % test_file
181+
182+ with self .mocked_stdout_stderr ():
183+ res = run (cmd )
184+ self .assertEqual (res .exit_code , 0 )
185+ self .assertTrue (res .output .startswith ('foo ' ) and res .output .endswith (' bar' ))
186+ self .assertEqual (type (res .output ), str )
187+
162188 def test_run_cmd_log (self ):
163189 """Test logging of executed commands."""
164190 fd , logfile = tempfile .mkstemp (suffix = '.log' , prefix = 'eb-test-' )
@@ -200,14 +226,47 @@ def test_run_cmd_log(self):
200226
201227 # Test that we can set the directory for the logfile
202228 log_path = os .path .join (self .test_prefix , 'chicken' )
203- os . mkdir (log_path )
229+ mkdir (log_path )
204230 logfile = None
205231 init_logging (logfile , silent = True , tmp_logdir = log_path )
206232 logfiles = os .listdir (log_path )
207233 self .assertEqual (len (logfiles ), 1 )
208234 self .assertTrue (logfiles [0 ].startswith ("easybuild" ))
209235 self .assertTrue (logfiles [0 ].endswith ("log" ))
210236
237+ def test_run_log (self ):
238+ """Test logging of executed commands with run function."""
239+
240+ fd , logfile = tempfile .mkstemp (suffix = '.log' , prefix = 'eb-test-' )
241+ os .close (fd )
242+
243+ regex_start_cmd = re .compile ("Running command 'echo hello' in /" )
244+ regex_cmd_exit = re .compile ("Command 'echo hello' exited with exit code [0-9]* and output:" )
245+
246+ # command output is always logged
247+ init_logging (logfile , silent = True )
248+ with self .mocked_stdout_stderr ():
249+ res = run ("echo hello" )
250+ stop_logging (logfile )
251+ self .assertEqual (res .exit_code , 0 )
252+ self .assertEqual (res .output , 'hello\n ' )
253+ self .assertEqual (len (regex_start_cmd .findall (read_file (logfile ))), 1 )
254+ self .assertEqual (len (regex_cmd_exit .findall (read_file (logfile ))), 1 )
255+ write_file (logfile , '' )
256+
257+ # with debugging enabled, exit code and output of command should only get logged once
258+ setLogLevelDebug ()
259+
260+ init_logging (logfile , silent = True )
261+ with self .mocked_stdout_stderr ():
262+ res = run ("echo hello" )
263+ stop_logging (logfile )
264+ self .assertEqual (res .exit_code , 0 )
265+ self .assertEqual (res .output , 'hello\n ' )
266+ self .assertEqual (len (regex_start_cmd .findall (read_file (logfile ))), 1 )
267+ self .assertEqual (len (regex_cmd_exit .findall (read_file (logfile ))), 1 )
268+ write_file (logfile , '' )
269+
211270 def test_run_cmd_negative_exit_code (self ):
212271 """Test run_cmd function with command that has negative exit code."""
213272 # define signal handler to call in case run_cmd takes too long
@@ -281,8 +340,6 @@ def test_run_cmd_log_output(self):
281340
282341 def test_run_cmd_trace (self ):
283342 """Test run_cmd under --trace"""
284- # replace log.experimental with log.warning to allow experimental code
285- easybuild .tools .utilities ._log .experimental = easybuild .tools .utilities ._log .warning
286343
287344 init_config (build_options = {'trace' : True })
288345
@@ -302,6 +359,7 @@ def test_run_cmd_trace(self):
302359 stderr = self .get_stderr ()
303360 self .mock_stdout (False )
304361 self .mock_stderr (False )
362+ self .assertEqual (out , 'hello\n ' )
305363 self .assertEqual (ec , 0 )
306364 self .assertEqual (stderr , '' )
307365 regex = re .compile ('\n ' .join (pattern ))
@@ -315,6 +373,7 @@ def test_run_cmd_trace(self):
315373 stderr = self .get_stderr ()
316374 self .mock_stdout (False )
317375 self .mock_stderr (False )
376+ self .assertEqual (out , 'hello' )
318377 self .assertEqual (ec , 0 )
319378 self .assertEqual (stderr , '' )
320379 pattern .insert (3 , r"\t\[input: hello\]" )
@@ -330,6 +389,63 @@ def test_run_cmd_trace(self):
330389 stderr = self .get_stderr ()
331390 self .mock_stdout (False )
332391 self .mock_stderr (False )
392+ self .assertEqual (out , 'hello\n ' )
393+ self .assertEqual (ec , 0 )
394+ self .assertEqual (stdout , '' )
395+ self .assertEqual (stderr , '' )
396+
397+ def test_run_trace_stdin (self ):
398+ """Test run under --trace + passing stdin input."""
399+
400+ init_config (build_options = {'trace' : True })
401+
402+ pattern = [
403+ r"^ >> running command:" ,
404+ 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]\]" ,
405+ r"\t\[working dir: .*\]" ,
406+ r"\techo hello" ,
407+ r" >> command completed: exit 0, ran in .*" ,
408+ ]
409+
410+ self .mock_stdout (True )
411+ self .mock_stderr (True )
412+ res = run ("echo hello" )
413+ stdout = self .get_stdout ()
414+ stderr = self .get_stderr ()
415+ self .mock_stdout (False )
416+ self .mock_stderr (False )
417+ self .assertEqual (res .output , 'hello\n ' )
418+ self .assertEqual (res .exit_code , 0 )
419+ self .assertEqual (stderr , '' )
420+ regex = re .compile ('\n ' .join (pattern ))
421+ self .assertTrue (regex .search (stdout ), "Pattern '%s' found in: %s" % (regex .pattern , stdout ))
422+
423+ # also test with command that is fed input via stdin
424+ self .mock_stdout (True )
425+ self .mock_stderr (True )
426+ res = run ('cat' , stdin = 'hello' )
427+ stdout = self .get_stdout ()
428+ stderr = self .get_stderr ()
429+ self .mock_stdout (False )
430+ self .mock_stderr (False )
431+ self .assertEqual (res .output , 'hello' )
432+ self .assertEqual (res .exit_code , 0 )
433+ self .assertEqual (stderr , '' )
434+ pattern .insert (3 , r"\t\[input: hello\]" )
435+ pattern [- 2 ] = "\t cat"
436+ regex = re .compile ('\n ' .join (pattern ))
437+ self .assertTrue (regex .search (stdout ), "Pattern '%s' found in: %s" % (regex .pattern , stdout ))
438+
439+ # trace output can be disabled on a per-command basis by enabling 'hidden'
440+ self .mock_stdout (True )
441+ self .mock_stderr (True )
442+ res = run ("echo hello" , hidden = True )
443+ stdout = self .get_stdout ()
444+ stderr = self .get_stderr ()
445+ self .mock_stdout (False )
446+ self .mock_stderr (False )
447+ self .assertEqual (res .output , 'hello\n ' )
448+ self .assertEqual (res .exit_code , 0 )
333449 self .assertEqual (stdout , '' )
334450 self .assertEqual (stderr , '' )
335451
0 commit comments