blob: ff8ad0a2d2e1f86b7efd17e3e2bc107510761a7b [file] [log] [blame]
#!/usr/bin/python
"""subprocess replacement that implements specific programs in Python."""
import importlib
import logging
import os
import types
logger = logging.getLogger('subprocess')
logger.setLevel(logging.DEBUG)
CALL_HISTORY = []
# Values are only for when the module name does not match the command name.
_COMMAND_NAMES = {
'connection_check': None,
'cwmp': None,
'get_quantenna_interfaces': 'get-quantenna-interfaces',
'ifdown': None,
'ifplugd_action': '/etc/ifplugd/ifplugd.action',
'ifup': None,
'ip': None,
'register_experiment': None,
'run_dhclient': 'run-dhclient',
'qcsapi': None,
'upload_logs_and_wait': 'upload-logs-and-wait',
'wifi': None,
'wpa_cli': None,
}
_COMMANDS = {v or k: importlib.import_module('.' + k, __name__)
for k, v in _COMMAND_NAMES.iteritems()}
STDOUT = 1
STDERR = 2
class CalledProcessError(Exception):
def __init__(self, returncode, cmd, output):
super(CalledProcessError, self).__init__()
self.returncode = returncode
self.cmd = cmd
self.output = output
def __repr__(self):
return ('CalledProcessError: '
'Command "%r" returned non-zero exit status %d: %s'
% (self.cmd, self.returncode, self.output))
def _call(command, **kwargs):
"""Fake subprocess call."""
CALL_HISTORY.append((command, kwargs))
if type(command) not in (tuple, list):
raise Exception('Fake subprocess.call only supports list/tuple commands, '
'got: %s', command)
ignored_kwargs = ('stdout', 'stderr')
for ignored_kwarg in ignored_kwargs:
kwargs.pop(ignored_kwarg, None)
extra_env = kwargs.pop('env', {})
if kwargs:
raise Exception('Fake subprocess.call does not support these kwargs: %s'
% kwargs.keys())
logger.debug('%r%s', command, (', env %r' % extra_env) if extra_env else '')
command, args = command[0], command[1:]
if command not in _COMMANDS:
raise Exception('Fake subprocess.call does not support %r, supports %r' %
(command, _COMMANDS.keys()))
impl = _COMMANDS[command]
if isinstance(impl, types.ModuleType):
impl = impl.call
forwarded_kwargs = {}
if extra_env:
forwarded_kwargs['env'] = extra_env
return impl(*args, **forwarded_kwargs)
def call(command, **kwargs):
rc, _ = _call(command, **kwargs)
return rc
def check_call(command, **kwargs):
rc, output = _call(command, **kwargs)
if rc:
raise CalledProcessError(rc, command, output)
return True
def check_output(command, **kwargs):
rc, output = _call(command, **kwargs)
if rc != 0:
raise CalledProcessError(rc, command, output)
return output
def mock(command, *args, **kwargs):
_COMMANDS[command].mock(*args, **kwargs)
def reset():
"""Reset all state."""
global CALL_HISTORY
CALL_HISTORY = []
for command in _COMMANDS.itervalues():
if isinstance(command, types.ModuleType):
reload(command)
def set_conman_paths(tmp_path=None, config_path=None, cwmp_path=None):
for command in ('run-dhclient', '/etc/ifplugd/ifplugd.action'):
_COMMANDS[command].CONMAN_PATH = tmp_path
for command in ('cwmp',):
_COMMANDS[command].CONMAN_CONFIG_PATH = config_path
for command in ('cwmp',):
_COMMANDS[command].CWMP_PATH = cwmp_path
# Make sure <tmp_path>/interfaces exists.
tmp_interfaces_path = os.path.join(tmp_path, 'interfaces')
if not os.path.exists(tmp_interfaces_path):
os.mkdir(tmp_interfaces_path)
# Some tiny fake implementations don't need their own file.
def echo(*s):
return 0, ' '.join(s)
def env(extra_env, *command, **kwargs):
final_env = kwargs.get('env', {})
k, v = extra_env.split('=')
final_env[k] = v
kwargs['env'] = final_env
return _call(command, **kwargs)
def timeout(unused_t, *command, **kwargs):
"""Just a transparent pass-through."""
return _call(command, **kwargs)
_COMMANDS.update({'echo': echo, 'env': env, 'timeout': timeout,})