blob: c44922c543dff9bf3c2d529f69598a6e742505cd [file] [log] [blame]
#!/usr/bin/env python2.7
# Copyright 2019 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import errno
import glob
import os
import shutil
import sys
import tempfile
import time
from process import Process
class Host(object):
"""Represent the platform-specific interface to the host system.
Attributes:
platform: The name of the current operating system.
tracing: Indicates if additional output is enabled.
"""
# Convenience file descriptor for silencing subprocess output
DEVNULL = open(os.devnull, 'w')
# Used to pass tracing flag to tests in subprocesses.
TRACE_ENVVAR = 'FX_FUZZ_TRACE'
def __init__(self):
self._platform = 'mac-x64' if os.uname()[0] == 'Darwin' else 'linux-x64'
self._fd_out = sys.stdout
self._fd_err = sys.stderr
self._tracing = os.getenv(Host.TRACE_ENVVAR) == '1'
@property
def platform(self):
return self._platform
@property
def fd_out(self):
return self._fd_out
@fd_out.setter
def fd_out(self, fd_out):
self._fd_out = fd_out
@property
def fd_err(self):
return self._fd_err
@fd_out.setter
def fd_err(self, fd_err):
self._fd_err = fd_err
@property
def tracing(self):
"""Enables detailed output about action the script is taking."""
return self._tracing
@tracing.setter
def tracing(self, tracing):
self._tracing = tracing
# I/O routines
def trace(self, message):
"""Prints execution details to stdout."""
if self._tracing:
print('+ {}'.format(message))
def echo(self, *args, **kwargs):
"""Print an informational message from a list of strings.
Arguments:
fd File descriptor to print to.
end Terminating character to append to message.
"""
if not args:
args = ['']
fd = kwargs.pop('fd', self._fd_out)
end = kwargs.pop('end', '\n')
assert not kwargs, 'Unexpected keyword arguments: {}'.format(kwargs)
for line in args:
fd.write(line)
fd.write(end)
fd.flush()
def error(self, *lines, **kwargs):
"""Print an error message and exit."""
assert lines, 'Fatal error without error message.'
fd = kwargs.pop('fd', self._fd_err)
status = kwargs.pop('status', 1)
assert not kwargs, 'Unexpected keyword arguments: {}'.format(kwargs)
self.echo('ERROR: {}'.format(lines[0]), fd=fd)
for line in lines[1:]:
self.echo(' {}'.format(line), fd=fd)
sys.exit(status)
def choose(self, prompt, choices):
"""Displays a simple interactive menu."""
self.echo(prompt + ' (or enter 0 to cancel):')
for i, choice in enumerate(choices, 1):
self.echo(" {}) {}".format(i, choice))
choice = None
while not choice:
self.echo("Choose 1-{}: ".format(len(choices)), end='')
try:
raw_choice = int(raw_input())
if raw_choice == 0:
self.echo("User canceled.")
sys.exit(0)
choice = choices[raw_choice - 1]
except ValueError, IndexError:
self.echo("Invalid selection.")
return choice
# Filesystem routines
def getcwd(self):
"""Returns the current working directory."""
return os.getcwd()
def isfile(self, pathname):
"""Returns true for files that exist on the host."""
return os.path.isfile(pathname)
def isdir(self, pathname):
"""Returns true for directories that exist on the host."""
return os.path.isdir(pathname)
def glob(self, pattern):
"""Returns a list of pathnames from shell-expanding the pattern."""
return sorted(glob.glob(pattern))
def open(self, pathname, mode='r', on_error=None, missing_ok=False):
"""Opens and returns a file-like object.
It is the callers responsibility to clean up, preferably by using a
"with" statement.
Arguments:
mode: Same as for built-in open()
on_error: Message to display if file fails to open.
missing_ok: If true, return None if file not found.
"""
try:
self.trace(' opening: {}'.format(pathname))
return open(pathname, mode)
except IOError as e:
if e.errno != errno.ENOENT:
raise
elif missing_ok:
return None
elif on_error:
self.error(*on_error)
else:
self.error('Failed to open {}.'.format(pathname))
def readfile(self, pathname, **kwargs):
"""Returns the contents of a file."""
opened = self.open(pathname, **kwargs)
if not opened:
return None
try:
return opened.read().strip()
finally:
opened.close()
def touch(self, pathname):
with self.open(pathname, 'a') as opened:
pass
def mkdir(self, pathname):
"""Creates a directory and its parents if any of them are missing."""
self.trace('creating: {}'.format(pathname))
try:
os.makedirs(pathname)
except OSError as e:
if e.errno != errno.EEXIST:
raise
def link(self, pathname, linkname):
"""Creates or replaces a symbolic link from linkname to pathname."""
self.trace(' linking: {}'.format(linkname))
self.trace(' to: {}'.format(pathname))
try:
os.unlink(linkname)
except OSError as e:
if e.errno != errno.ENOENT:
raise
os.symlink(pathname, linkname)
def remove(self, pathname):
self.trace('removing: {}'.format(pathname))
if self.isdir(pathname):
shutil.rmtree(pathname)
else:
os.remove(pathname)
def _mkdtemp(self):
return tempfile.mkdtemp()
def temp_dir(self):
return _TemporaryDirectory(self)
# Other routines.
def getenv(self, name):
return os.getenv(name)
def create_process(self, args, **kwargs):
self.trace(' running: {}'.format(' '.join(args)))
return Process(args, **kwargs)
def sleep(self, duration):
if duration > 0:
self.trace('sleeping: {}'.format(duration))
time.sleep(duration)
class _TemporaryDirectory(object):
"""A temporary directory that can be used with "with"."""
def __init__(self, host):
self._host = host
self._pathname = None
@property
def pathname(self):
return self._pathname
def __enter__(self):
self._pathname = self._host._mkdtemp()
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self._host.remove(self._pathname)