def async_cmd_check(self): """ Check progress of installation command that was started asynchronously. :return: True if command completed, False otherwise """ if self.async_cmd_info is None: raise EasyBuildError("No installation command running asynchronously for %s", self.name) elif self.async_cmd_info is False: self.log.info("No asynchronous command was started for extension %s", self.name) return True else: self.log.debug("Checking on installation of extension %s...", self.name) # use small read size, to avoid waiting for a long time until sufficient output is produced res = check_async_cmd(*self.async_cmd_info, output_read_size=self.async_cmd_read_size) self.async_cmd_output += res['output'] if res['done']: self.log.info("Installation of extension %s completed!", self.name) self.async_cmd_info = None else: self.async_cmd_check_cnt += 1 self.log.debug("Installation of extension %s still running (checked %d times)", self.name, self.async_cmd_check_cnt) # increase read size after sufficient checks, # to avoid that installation hangs due to output buffer filling up... if self.async_cmd_check_cnt % 10 == 0 and self.async_cmd_read_size < (1024 ** 2): self.async_cmd_read_size *= 2 return res['done']
def test_run_cmd_async(self): """Test asynchronously running of a shell command via run_cmd + complete_cmd.""" os.environ['TEST'] = 'test123' test_cmd = "echo 'sleeping...'; sleep 2; echo $TEST" cmd_info = run_cmd(test_cmd, asynchronous=True) proc = cmd_info[0] # change value of $TEST to check that command is completed with correct environment os.environ['TEST'] = 'some_other_value' # initial poll should result in None, since it takes a while for the command to complete ec = proc.poll() self.assertEqual(ec, None) # wait until command is done while ec is None: time.sleep(1) ec = proc.poll() out, ec = complete_cmd(*cmd_info, simple=False) self.assertEqual(ec, 0) self.assertEqual(out, 'sleeping...\ntest123\n') # also test use of check_async_cmd function os.environ['TEST'] = 'test123' cmd_info = run_cmd(test_cmd, asynchronous=True) # first check, only read first 12 output characters # (otherwise we'll be waiting until command is completed) res = check_async_cmd(*cmd_info, output_read_size=12) self.assertEqual(res, { 'done': False, 'exit_code': None, 'output': 'sleeping...\n' }) # 2nd check with default output size (1024) gets full output # (keep checking until command is fully done) while not res['done']: res = check_async_cmd(*cmd_info, output=res['output']) self.assertEqual(res, { 'done': True, 'exit_code': 0, 'output': 'sleeping...\ntest123\n' }) # check asynchronous running of failing command error_test_cmd = "echo 'FAIL!' >&2; exit 123" cmd_info = run_cmd(error_test_cmd, asynchronous=True) time.sleep(1) error_pattern = 'cmd ".*" exited with exit code 123' self.assertErrorRegex(EasyBuildError, error_pattern, check_async_cmd, *cmd_info) cmd_info = run_cmd(error_test_cmd, asynchronous=True) res = check_async_cmd(*cmd_info, fail_on_error=False) # keep checking until command is fully done while not res['done']: res = check_async_cmd(*cmd_info, fail_on_error=False, output=res['output']) self.assertEqual(res, { 'done': True, 'exit_code': 123, 'output': "FAIL!\n" }) # also test with a command that produces a lot of output, # since that tends to lock up things unless we frequently grab some output... verbose_test_cmd = ';'.join([ "echo start", "for i in $(seq 1 50)", "do sleep 0.1", "for j in $(seq 1000)", "do echo foo", "done", "done", "echo done", ]) cmd_info = run_cmd(verbose_test_cmd, asynchronous=True) proc = cmd_info[0] output = '' ec = proc.poll() self.assertEqual(ec, None) while ec is None: time.sleep(1) output += get_output_from_process(proc) ec = proc.poll() out, ec = complete_cmd(*cmd_info, simple=False, output=output) self.assertEqual(ec, 0) self.assertTrue(out.startswith('start\n')) self.assertTrue(out.endswith('\ndone\n')) # also test use of check_async_cmd on verbose test command cmd_info = run_cmd(verbose_test_cmd, asynchronous=True) error_pattern = r"Number of output bytes to read should be a positive integer value \(or zero\)" self.assertErrorRegex(EasyBuildError, error_pattern, check_async_cmd, *cmd_info, output_read_size=-1) self.assertErrorRegex(EasyBuildError, error_pattern, check_async_cmd, *cmd_info, output_read_size='foo') # with output_read_size set to 0, no output is read yet, only status of command is checked res = check_async_cmd(*cmd_info, output_read_size=0) self.assertEqual(res['done'], False) self.assertEqual(res['exit_code'], None) self.assertEqual(res['output'], '') res = check_async_cmd(*cmd_info) self.assertEqual(res['done'], False) self.assertEqual(res['exit_code'], None) self.assertTrue(res['output'].startswith('start\n')) self.assertFalse(res['output'].endswith('\ndone\n')) # keep checking until command is complete while not res['done']: res = check_async_cmd(*cmd_info, output=res['output']) self.assertEqual(res['done'], True) self.assertEqual(res['exit_code'], 0) self.assertTrue(res['output'].startswith('start\n')) self.assertTrue(res['output'].endswith('\ndone\n'))