Merge lp:~zyga/checkbox/fix-1401996 into lp:checkbox

Proposed by Zygmunt Krynicki
Status: Merged
Approved by: Daniel Manrique
Approved revision: 3502
Merged at revision: 3502
Proposed branch: lp:~zyga/checkbox/fix-1401996
Merge into: lp:checkbox
Diff against target: 68 lines (+39/-5)
1 file modified
plainbox/plainbox/vendor/extcmd/glibc.py (+39/-5)
To merge this branch: bzr merge lp:~zyga/checkbox/fix-1401996
Reviewer Review Type Date Requested Status
Daniel Manrique (community) Approve
Review via email: mp+244643@code.launchpad.net

Description of the change

75c3fff plainbox:vendor:extcmd: respect the on_line() protocol

To post a comment you must log in.
Revision history for this message
Daniel Manrique (roadmr) wrote :

Command to test:
plainbox -C -T plainbox.ctrl run -i 2013.com.canonical.certification::cdimage

Before the patch:
INFO plainbox.ctrl: Storing resource record '2013.com.canonical.certification::cdimage': Resource({'official': 'Alpha\narchitecture: amd64\ncodename: Trusty Tahr\nrelease: 14.04 LTS\ndate: 20140304\ndistributor: Ubuntu'})

After:
INFO plainbox.ctrl: Storing resource record '2013.com.canonical.certification::cdimage': Resource({'date': '20140304', 'codename': 'Trusty Tahr', 'architecture': 'amd64', 'distributor': 'Ubuntu', 'official': 'Alpha', 'release': '14.04 LTS'})

so +1 on functionality, and the code is quite clear and easy to follow (yay for a nice, loopy algorithm). My only request would be some sort of automated testing to ensure this doesn't bite us again, but I leave that up to you for reasons discussed on IRC (because... reasons).

review: Approve
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

There are no unit tests for extcmd that would cover this functionality (ideas on how to unit-test this are welcome though, I have none). We do have some tests that caught this, namely, the regression test provider.

Without this patch it breaks horribly, with this patch it works as expected.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plainbox/plainbox/vendor/extcmd/glibc.py'
2--- plainbox/plainbox/vendor/extcmd/glibc.py 2014-12-12 14:49:06 +0000
3+++ plainbox/plainbox/vendor/extcmd/glibc.py 2014-12-12 18:52:47 +0000
4@@ -215,17 +215,51 @@
5 _logger.debug("Sending SIGQUIT to process %d", pid)
6 os.kill(pid, signal.SIGQUIT)
7
8- def _read_pipe(self, fd, name):
9+ def _read_pipe(self, fd, name, buffer_map):
10 assert name in ('stdout', 'stderr')
11 data = os.read(fd, PIPE_BUF)
12+ done_reading = len(data) == 0
13 _logger.debug("Read %d bytes of data from %s", len(data), name)
14- if len(data) == 0:
15- return True
16- self._delegate.on_line(name, data)
17+ buf = buffer_map[name]
18+ if buf is not None:
19+ # Combine this buffer with the previous one before attempting
20+ # to detect newlines. This way, on the outside, we always
21+ # return complete lines and no something partial or in the
22+ # middle.
23+ buf.extend(data)
24+ else:
25+ buf = bytearray(data)
26+ assert isinstance(buf, bytearray)
27+ # Split the buffer into lines, the last line (aka line_list[-1])
28+ # may be partial and we have to keep it around for the next time
29+ # we're called.
30+ line_buffer_list = buf.splitlines(True)
31+ for line_buffer in line_buffer_list[0:-1]:
32+ self._delegate.on_line(name, bytes(line_buffer))
33+ if len(line_buffer_list) > 0:
34+ last_line_buffer = line_buffer_list[-1]
35+ if last_line_buffer.endswith(b'\n') or done_reading:
36+ # If the last line is complete (ends with the newline byte)
37+ # or this is the last chunk (we're done reading) then send it
38+ # out immedaitely.
39+ self._delegate.on_line(name, bytes(last_line_buffer))
40+ buffer_map[name] = None
41+ else:
42+ # Otherwise keep the last line around for the next time we're
43+ # called
44+ buffer_map[name] = last_line_buffer
45+ else:
46+ buffer_map[name] = None
47+ return done_reading
48
49 def _loop(self, selector, pid):
50 waiting_for = set(['stdout', 'stderr', 'proc'])
51 return_code = None
52+ # This buffer holds partial data that was not yet published
53+ buffer_map = {
54+ 'stdout': None,
55+ 'stderr': None,
56+ }
57 while waiting_for:
58 event_list = selector.select()
59 for key, events in event_list:
60@@ -253,7 +287,7 @@
61 "Unexpected event mask for signalfd: %d", events)
62 else:
63 if events & EVENT_READ:
64- if self._read_pipe(key.fd, key.data):
65+ if self._read_pipe(key.fd, key.data, buffer_map):
66 _logger.debug(
67 "pipe %s depleted, unregistering and closing",
68 key.data)

Subscribers

People subscribed via source and target branches