Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/.tox
/build
/dist
.venv
29 changes: 17 additions & 12 deletions honcho/manager.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import datetime
import multiprocessing
import signal
import sys

from .colour import get_colours
from .compat import Empty
from .compat import iteritems
from .environ import Env
from .process import Process
from .printer import Printer, Message
from .util.type import sequencify

KILL_WAIT = 5
SIGNALS = {
Expand All @@ -28,7 +28,7 @@ class Manager(object):
"""
Manager is responsible for running multiple external processes in parallel
managing the events that result (starting, stopping, printing). By default
it relays printed lines to a printer that prints to STDOUT.
it relays printed lines to a printers that prints to STDOUT.

Example::

Expand All @@ -54,8 +54,9 @@ def __init__(self, printer=None):
self._colours = get_colours()
self._env = Env()

self._printer = printer if printer is not None else Printer(sys.stdout)
self._printer.width = len(SYSTEM_PRINTER_NAME)
self._printers = sequencify(printer or Printer())
for i, _printer in enumerate(self._printers):
self._printers[i].width = len(SYSTEM_PRINTER_NAME)

self._process_ctor = Process
self._processes = {}
Expand All @@ -78,7 +79,8 @@ def add_process(self, name, cmd, quiet=False, env=None, cwd=None):
self._processes[name]['obj'] = proc

# Update printer width to accommodate this process name
self._printer.width = max(self._printer.width, len(name))
for i, _printer in enumerate(self._printers):
self._printers[i].width = max(self._printers[i].width, len(name))

return proc

Expand All @@ -105,15 +107,16 @@ def _terminate(signum, frame):
exit = False
exit_start = None

while 1:
while True:
try:
msg = self.events.get(timeout=0.1)
except Empty:
if exit:
break
else:
if msg.type == 'line':
self._printer.write(msg)
for _printer in self._printers:
_printer.write(msg)
elif msg.type == 'start':
self._processes[msg.name]['pid'] = msg.data['pid']
self._system_print("%s started (pid=%s)\n"
Expand Down Expand Up @@ -189,8 +192,10 @@ def _any_stopped(self):
return any(p.get('returncode') is not None for _, p in iteritems(self._processes))

def _system_print(self, data):
self._printer.write(Message(type='line',
data=data,
time=self._env.now(),
name=SYSTEM_PRINTER_NAME,
colour=None))
for _printer in self._printers:
now = self._env.now()
_printer.write(Message(type='line',
data=data,
time=now,
name=SYSTEM_PRINTER_NAME,
colour=None))
Empty file added honcho/util/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions honcho/util/type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def sequencify(value, type_=list):
if not isinstance(value, (list, tuple)):
value = [value]

value = type_(value)

return value
41 changes: 29 additions & 12 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,20 +189,23 @@ def find_line(self, data):
class TestManager(object):
@pytest.fixture(autouse=True)
def printer(self): # noqa
self.p = FakePrinter()
self.m = Manager(printer=self.p)
self.p1 = FakePrinter()
self.p2 = FakePrinter()
self.m = Manager(printer=[self.p1,self.p2])
self.m._env = FakeEnv()

def run_history(self, name, wait=True):
self.h = Harness(HISTORIES[name], self.m)
self.h.run(wait=wait)

def test_init_sets_default_printer_width(self):
assert self.p.width == len(SYSTEM_PRINTER_NAME)
assert self.p1.width == len(SYSTEM_PRINTER_NAME)
assert self.p2.width == len(SYSTEM_PRINTER_NAME)

def test_add_process_updates_printer_width(self):
self.m.add_process('interesting', 'ruby server.rb')
assert self.p.width == len('interesting')
assert self.p1.width == len('interesting')
assert self.p2.width == len('interesting')

def test_add_process_sets_name(self):
proc = self.m.add_process('foo', 'ruby server.rb')
Expand Down Expand Up @@ -246,15 +249,26 @@ def test_loop_calls_process_run(self):

def test_printer_receives_messages_in_correct_order(self):
self.run_history('one')
self.p.fetch_lines()
assert self.p.lines_local[0].data == 'foo started (pid=123)\n'
assert self.p.lines_local[1].data == b'hello, world!\n'
assert self.p.lines_local[2].data == 'foo stopped (rc=0)\n'
self.p1.fetch_lines()
self.p2.fetch_lines()

assert self.p1.lines_local[0].data == 'foo started (pid=123)\n'
assert self.p1.lines_local[1].data == b'hello, world!\n'
assert self.p1.lines_local[2].data == 'foo stopped (rc=0)\n'

assert self.p2.lines_local[0].data == 'foo started (pid=123)\n'
assert self.p2.lines_local[1].data == b'hello, world!\n'
assert self.p2.lines_local[2].data == 'foo stopped (rc=0)\n'

def test_printer_receives_lines_multi_process(self):
self.run_history('two')
l1 = self.p.find_line(b'process one\n')
l2 = self.p.find_line(b'process two\n')
l1 = self.p1.find_line(b'process one\n')
l2 = self.p1.find_line(b'process two\n')
assert l1.name == 'foo'
assert l2.name == 'bar'

l1 = self.p2.find_line(b'process one\n')
l2 = self.p2.find_line(b'process two\n')
assert l1.name == 'foo'
assert l2.name == 'bar'

Expand All @@ -264,5 +278,8 @@ def test_returncode_set_by_first_exiting_process(self):

def test_printer_receives_lines_after_stop(self):
self.run_history('output_after_stop')
assert self.p.got_line(b'fishmongers\n')
assert self.p.got_line(b'butchers\n')
assert self.p1.got_line(b'fishmongers\n')
assert self.p1.got_line(b'butchers\n')

assert self.p2.got_line(b'fishmongers\n')
assert self.p2.got_line(b'butchers\n')
14 changes: 14 additions & 0 deletions tests/test_util_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from honcho.util.type import sequencify

def test_sequencify():
assert sequencify("foobar") == ["foobar"]
assert sequencify([1,2,3]) == [1,2,3]
assert sequencify([1,2,3]) != [3,2,1]
assert sequencify([]) == []
assert sequencify(None) == [None]

assert sequencify("foobar", type_ = tuple) == ("foobar",)
assert sequencify([1,2,3], type_ = tuple) == (1,2,3)
assert sequencify([1,2,3], type_ = tuple) != (3,2,1)
assert sequencify([], type_ = tuple) == tuple()
assert sequencify(None, type_ = tuple) == (None,)