Merge lp:~gocept/landscape-client/raise-error-on-signal into lp:~landscape/landscape-client/trunk

Proposed by Steffen Allner
Status: Merged
Approved by: Eric Snow
Approved revision: 999
Merged at revision: 1001
Proposed branch: lp:~gocept/landscape-client/raise-error-on-signal
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 84 lines (+33/-3)
3 files modified
landscape/lib/tests/test_twisted_util.py (+24/-0)
landscape/lib/twisted_util.py (+7/-1)
landscape/manager/tests/test_aptsources.py (+2/-2)
To merge this branch: bzr merge lp:~gocept/landscape-client/raise-error-on-signal
Reviewer Review Type Date Requested Status
Eric Snow (community) Approve
🤖 Landscape Builder test results Approve
Daniel Havlik (community) Approve
Review via email: mp+321707@code.launchpad.net

Commit message

"Raise" a new SignalError from AllOutputProcessProtocol.processEnded().

The exception is propagated through reactor.errback() rather than actually raised. SignalError happens when a twisted-spawned process exits due to a signal. This is a necessary change because of how twisted errbacks are handled in Python 3. However, it is also a valid change for Python 2 since errbacks align with exceptions, not tuples.

Description of the change

As was mentioned in the comment here by Adam [0], it is necessary, that we issue a proper Exception instead of a tuple if we want to be catched by an errback.

As this code path was not tested before, I added a unittest to cover this.

[0] https://code.launchpad.net/~gocept/landscape-client/py3-manager-shutdownmanager-aptsources/+merge/321253/comments/841470

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: TRIAL_ARGS=-j4 make ci-check
Result: Success
Revno: 997
Branch: lp:~gocept/landscape-client/raise-error-on-signal
Jenkins: https://ci.lscape.net/job/latch-test-xenial/3827/

review: Approve (test results)
998. By Steffen Allner

Backmerge from trunk.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: TRIAL_ARGS=-j4 make ci-check
Result: Success
Revno: 998
Branch: lp:~gocept/landscape-client/raise-error-on-signal
Jenkins: https://ci.lscape.net/job/latch-test-xenial/3828/

review: Approve (test results)
Revision history for this message
Eric Snow (ericsnowcurrently) wrote :

This patch looks good, but should include changes for the test that motivated this change:

  landscape.manager.tests.test_aptsources.AptSourcesTests.test_signaled_import_reported

review: Needs Fixing
Revision history for this message
Steffen Allner (sallner) wrote :

> This patch looks good, but should include changes for the test that motivated
> this change:
>
> landscape.manager.tests.test_aptsources.AptSourcesTests.test_signaled_import
> _reported

Hm, I tried different things to do something like an integration test, but failed to achieve this. I have probably not fully understood how the test case operates. The closest I got was [0]. If I just return the spawn_process() call in _run_process() as in the actual implement the test fails, probably because it does not support async in this way.

Maybe I also misunderstood you and you just requested a renaming of the RuntimeError to a SignalError.

Nevertheless, I would be happy to understand the way the tests interact here.

[0] http://paste.ubuntu.com/24313000/

Revision history for this message
Eric Snow (ericsnowcurrently) wrote :

Right, I only meant changing RuntimeError to SignalError. :)

Revision history for this message
Daniel Havlik (nilo) wrote :

+1

review: Approve
999. By Steffen Allner

Change the error to SignalError, which is actually raised in case of a process terminated by a signal in l.l.twisted_util.

Revision history for this message
Steffen Allner (sallner) wrote :

> Right, I only meant changing RuntimeError to SignalError. :)

That simplifies things ;) Changed and pushed.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-check
Result: Success
Revno: 999
Branch: lp:~gocept/landscape-client/raise-error-on-signal
Jenkins: https://ci.lscape.net/job/latch-test-xenial/3835/

review: Approve (test results)
Revision history for this message
Eric Snow (ericsnowcurrently) wrote :

LGTM :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/lib/tests/test_twisted_util.py'
2--- landscape/lib/tests/test_twisted_util.py 2017-03-14 09:53:55 +0000
3+++ landscape/lib/tests/test_twisted_util.py 2017-04-05 07:25:59 +0000
4@@ -136,3 +136,27 @@
5 result = spawn_process(self.command, stdin="hello")
6 result.addCallback(callback)
7 return result
8+
9+ def test_spawn_process_with_signal(self):
10+ """
11+ In case the process gets terminated by a signal, it raises a
12+ C{SignalError}.
13+ """
14+ # This script will kill itself.
15+ create_text_file(self.command, """\
16+#!/bin/sh
17+kill $$
18+""")
19+
20+ def callback(args):
21+ raise RuntimeError("Errback was not called.")
22+
23+ def errback(failure):
24+ out, err, code = failure.value.args
25+ self.assertEqual(out, b"")
26+ self.assertEqual(err, b"")
27+ self.assertEqual(code, 15) # 15 for SIGTERM
28+
29+ result = spawn_process(self.command)
30+ result.addCallbacks(callback, errback)
31+ return result
32
33=== modified file 'landscape/lib/twisted_util.py'
34--- landscape/lib/twisted_util.py 2017-04-04 08:50:05 +0000
35+++ landscape/lib/twisted_util.py 2017-04-05 07:25:59 +0000
36@@ -4,11 +4,16 @@
37 from twisted.internet.protocol import ProcessProtocol
38 from twisted.internet.process import Process, ProcessReader
39 from twisted.internet import reactor
40+from twisted.python.failure import Failure
41 from twisted.python.compat import itervalues, networkString
42
43 from landscape.lib.encoding import encode_values
44
45
46+class SignalError(Exception):
47+ """An error if the process was terminated by a signal."""
48+
49+
50 def gather_results(deferreds, consume_errors=False):
51 d = DeferredList(deferreds, fireOnOneErrback=1,
52 consumeErrors=consume_errors)
53@@ -59,7 +64,8 @@
54 e = reason.value
55 code = e.exitCode
56 if e.signal:
57- self.deferred.errback((out, err, e.signal))
58+ failure = Failure(SignalError(out, err, e.signal))
59+ self.deferred.errback(failure)
60 else:
61 self.deferred.callback((out, err, code))
62
63
64=== modified file 'landscape/manager/tests/test_aptsources.py'
65--- landscape/manager/tests/test_aptsources.py 2017-03-29 11:38:02 +0000
66+++ landscape/manager/tests/test_aptsources.py 2017-04-05 07:25:59 +0000
67@@ -8,7 +8,7 @@
68 from landscape.manager.aptsources import AptSources
69 from landscape.manager.plugin import SUCCEEDED, FAILED
70
71-from landscape.lib.twisted_util import gather_results
72+from landscape.lib.twisted_util import gather_results, SignalError
73 from landscape.tests.helpers import LandscapeTest, ManagerHelper
74 from landscape.package.reporter import find_reporter_command
75
76@@ -356,7 +356,7 @@
77 deferred = Deferred()
78
79 def _run_process(command, args, env={}, path=None, uid=None, gid=None):
80- deferred.errback(RuntimeError("nok", "some error", 1))
81+ deferred.errback(SignalError("nok", "some error", 1))
82 return deferred
83
84 self.sourceslist._run_process = _run_process

Subscribers

People subscribed via source and target branches

to all changes: