Merge lp:~libertine-team/libertine/refactor-app-wrapper into lp:libertine

Proposed by Stephen M. Webb
Status: Merged
Approved by: Christopher Townsend
Approved revision: 346
Merged at revision: 320
Proposed branch: lp:~libertine-team/libertine/refactor-app-wrapper
Merge into: lp:libertine
Diff against target: 2156 lines (+1575/-308)
19 files modified
.bzrignore (+2/-0)
debian/control (+2/-0)
debian/python3-libertine.install (+1/-0)
python/libertine/ChrootContainer.py (+14/-7)
python/libertine/Libertine.py (+37/-4)
python/libertine/LxcContainer.py (+15/-7)
python/libertine/launcher/__init__.py (+28/-0)
python/libertine/launcher/config.py (+290/-0)
python/libertine/launcher/session.py (+340/-0)
python/libertine/launcher/task.py (+72/-0)
python/libertine/utils.py (+4/-9)
tests/unit/CMakeLists.txt (+10/-55)
tests/unit/libertine_public_gir_tests.py (+0/-63)
tests/unit/test_launcher.py (+557/-4)
tests/unit/test_launcher_with_dbus.py (+103/-0)
tests/unit/test_libertine_gir.py (+67/-0)
tests/unit/test_logger.py (+16/-20)
tests/unit/test_session_bridge.py (+2/-0)
tools/libertine-launch (+15/-139)
To merge this branch: bzr merge lp:~libertine-team/libertine/refactor-app-wrapper
Reviewer Review Type Date Requested Status
Christopher Townsend Approve
Stephen M. Webb (community) Approve
Larry Price Approve
Libertine CI Bot continuous-integration Approve
Brandon Schaefer (community) Approve
Review via email: mp+305544@code.launchpad.net

Commit message

libertine-launch: refactored core components of application session management

Description of the change

Refactor libertine-launch session into various classes to better enable unit testing, and added unit testing.

Adding unit tests required refactoring some unrelated chunkies of code.

A follow-up merge will remove the replaced code.

To post a comment you must log in.
Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:320
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/135/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/362/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/276/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/364
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/347
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/347
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/347
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/346/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/346/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/346/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/346/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/346/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/346/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/135/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

+1 for skipping that test for now. Its really a huge intergeneration test that should be looked at after we finish refactoring.

1 inline comment

Revision history for this message
Brandon Schaefer (brandontschaefer) :
review: Approve
321. By Stephen M. Webb

debian/control: added python3-dbusmock build dep

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:321
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/139/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/366/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/280/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/368
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/351
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/351
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/351
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/350/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/350
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/350/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/350
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/350/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/350/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/350
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/350/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/350
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/350/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/139/rebuild

review: Needs Fixing (continuous-integration)
322. By Stephen M. Webb

python/libertine/launcher/session.py:_create_bridge_listener() set listen() buffer to 5

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:322
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/140/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/367
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/290
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/290
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/290
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/290
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/290
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/290
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/281/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/369
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/352
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/352
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/352
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/351
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/351/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/351
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/351/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/351
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/351/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/351
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/351/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/351
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/351/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/351
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/351/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/140/rebuild

review: Approve (continuous-integration)
Revision history for this message
Christopher Townsend (townsend) wrote :

Couple of issues I'm seeing thus far.

1. pasted is not running. This is the output from the upstart log for the app:

pasted: UbuntuClientIntegration: connection to Mir server failed. Check that a Mir server is
running, and the correct socket is being used and is accessible. The shell may have
rejected the incoming connection, so check its log file

2. Whenever I close an app via the File->Quit operation, I get this crash:

Traceback (most recent call last):
  File "/usr/bin/libertine-launch", line 29, in <module>
    main()
  File "/usr/bin/libertine-launch", line 26, in main
    session.run()
  File "/usr/lib/python3/dist-packages/libertine/launcher/session.py", line 219, in run
    handler(key.fd, datum)
  File "/usr/lib/python3/dist-packages/libertine/launcher/session.py", line 285, in _handle_sig_fd
    if not self._handle_child_died():
  File "/usr/lib/python3/dist-packages/libertine/launcher/session.py", line 270, in _handle_child_died
    if child.wait():
  File "/usr/lib/python3/dist-packages/libertine/launcher/task.py", line 72, in wait
    (pid, status) = waitpid(self._process.pid, WNOHANG)
ChildProcessError: [Errno 10] No child processes

review: Needs Fixing
323. By Stephen M. Webb

libertine/launcher/config: separated host_environ from session_environ

324. By Stephen M. Webb

libertine_launch: passed cleansed host environ to launch tasks

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:324
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/144/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/372/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/285/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/374
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/356
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/356
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/356
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/355
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/355/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/355
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/355/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/355/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/355
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/355/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/355
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/355/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/355
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/355/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/144/rebuild

review: Needs Fixing (continuous-integration)
325. By Stephen M. Webb

test/unit/test_launcher(SessionRunning): add multiple join attempts for race conditions

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:325
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/146/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/374
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/295
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/295
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/295
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/295
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/295
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/295
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/287/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/376
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/358
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/358
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/358
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/357
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/357
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/357
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/357
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/357
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/357
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/357/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/146/rebuild

review: Approve (continuous-integration)
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

Cool i can confirm as well that i can no longer get this race. We were able to reproduce it with:

stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 10s & make test

review: Approve
Revision history for this message
Christopher Townsend (townsend) wrote :

Still seeing issues.

Latest traceback(s) when staring up an app:

(terminator:166): GConf-WARNING **: Client failed to connect to the D-BUS daemon:
Failed to connect to socket /tmp/dbus-nzBTSULJ3L: Connection refused
Traceback (most recent call last):
  File "/usr/bin/terminator", line 118, in <module>
    TERMINATOR.create_layout(OPTIONS.layout)
  File "/usr/share/terminator/terminatorlib/terminator.py", line 265, in create_layout
    window, terminal = self.new_window()
  File "/usr/share/terminator/terminatorlib/terminator.py", line 192, in new_window
    terminal = maker.make('Terminal')
  File "/usr/share/terminator/terminatorlib/factory.py", line 94, in make
    output = func(**kwargs)
  File "/usr/share/terminator/terminatorlib/factory.py", line 106, in make_terminal
    return(terminal.Terminal())
  File "/usr/share/terminator/terminatorlib/terminal.py", line 178, in __init__
    self.reconfigure()
  File "/usr/share/terminator/terminatorlib/terminal.py", line 782, in reconfigure
    self.titlebar.update()
  File "/usr/share/terminator/terminatorlib/titlebar.py", line 113, in update
    title_font = pango.FontDescription(self.config.get_system_prop_font())
  File "/usr/share/terminator/terminatorlib/config.py", line 371, in get_system_prop_font
    '/desktop/gnome/interface/font_name')
glib.GError: No D-BUS daemon running

Traceback (most recent call last):
  File "/usr/bin/libertine-launch", line 29, in <module>
    main()
  File "/usr/bin/libertine-launch", line 26, in main
    session.run()
  File "/usr/lib/python3/dist-packages/libertine/launcher/session.py", line 220, in run
    self._container.finish_application(self._app)
  File "/usr/lib/python3/dist-packages/libertine/Libertine.py", line 415, in finish_application
    self.container.finish_application(app)
  File "/usr/lib/python3/dist-packages/libertine/LxcContainer.py", line 319, in finish_application
    utils.terminate_window_manager(psutil.Process(window_manager))
NameError: name 'window_manager' is not defined

So it seems the DBUS_SESSION_BUS_ADDRESS env var is not being passed in correctly.

And obviously the other error.

review: Needs Fixing
Revision history for this message
Larry Price (larryprice) wrote :

tiny diff comments commonly about the year 2106 CE, will attempt to build/test on u8...

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:327
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/164/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/400
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/318
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/318
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/318
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/318
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/318
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/318
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/305/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/402
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/384
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/384
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/384
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/383
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/383/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/383
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/383/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/383
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/383/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/383
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/383/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/383
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/383/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/383
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/383/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/164/rebuild

review: Approve (continuous-integration)
326. By Christopher Townsend

Merge lp:libertine.

327. By Christopher Townsend

Fix unknown 'window_manager' variable.

328. By Christopher Townsend

Fix typos & unused code per review comments.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
329. By Christopher Townsend

Prepend "unix:path=" to env vars.

330. By Christopher Townsend

Merge lp:libertine.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:330
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/166/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/403/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/307/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/405
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/386
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/386
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/386
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/385/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/385/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/385/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/385/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/385/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/385/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/166/rebuild

review: Needs Fixing (continuous-integration)
331. By Christopher Townsend

Fix unit tests due to last merge.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:331
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/167/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/404
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/320
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/320
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/320
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/320
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/320
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/320
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/308/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/406
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/387
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/387
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/387
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/386
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/386/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/386
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/386/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/386
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/386/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/386
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/386/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/386
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/386/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/386
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/386/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/167/rebuild

review: Approve (continuous-integration)
332. By Christopher Townsend

Fix environment update and add missing os import.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:332
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/168/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/405
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/321
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/321
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/321
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/321
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/321
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/321
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/309/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/407
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/388
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/388
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/388
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/387
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/387/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/387
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/387/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/387
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/387/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/387
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/387/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/387
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/387/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/387
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/387/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/168/rebuild

review: Approve (continuous-integration)
333. By Christopher Townsend

Properly add the prefixed '\0' character to abstract socket paths.

334. By Christopher Townsend

Catch exceptions that can and will occur when using the session DBus bridge and ignore them.
Also unregister fd selectors when we are done with them.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:334
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/171/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/409/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/312/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/411
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/392
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/392
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/392
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/391/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/391/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/391/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/391/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/391/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/391/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/171/rebuild

review: Needs Fixing (continuous-integration)
335. By Christopher Townsend

When receiving a SIGCHLD, actually check for the launched app instead of the helper services.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:335
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/172/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/410/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/313/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/412
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/393
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/393
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/393
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/392/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/392/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/392/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/392/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/392/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/392/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/172/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:336
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/173/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/411/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/314/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/413
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/394
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/394
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/394
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/393/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/393/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/393/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/393/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/393/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/393/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/173/rebuild

review: Needs Fixing (continuous-integration)
336. By Christopher Townsend

Use 'unix:path' instead of 'unix:abstract' in unit tests to make cleaning up mock sockets easier.
Add a unit test for checking correct unix abstract socket path address (with '\0' prefix).

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:336
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/174/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/412
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/325
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/325
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/325
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/325
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/325
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/325
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/315/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/414
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/395
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/395
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/395
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/394
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/394/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/394
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/394/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/394
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/394/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/394
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/394/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/394
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/394/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/394
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/394/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/174/rebuild

review: Approve (continuous-integration)
337. By Christopher Townsend

Remove the session socket path upon deletion of the Session object.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:337
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/175/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/413
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/326
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/326
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/326
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/326
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/326
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/326
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/316/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/415
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/396
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/396
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/396
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/395
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/395/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/395
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/395/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/395
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/395/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/395
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/395/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/395
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/395/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/395
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/395/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/175/rebuild

review: Approve (continuous-integration)
338. By Christopher Townsend

Move the session socket address removal to the shutdown method.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:338
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/176/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/414/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/317/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/416
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/397
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/397
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/397
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/396/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/396/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/396/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/396/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/396/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/396/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/176/rebuild

review: Needs Fixing (continuous-integration)
339. By Christopher Townsend

Fix typo from last commit.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

FAILED: Continuous integration, rev:339
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/177/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/libertine/job/build/415/console
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/318/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/417
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/398
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/398
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/398
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/397/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/397/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/397/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/397/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/397/console
    FAILURE: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/397/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/177/rebuild

review: Needs Fixing (continuous-integration)
340. By Christopher Townsend

Fix unit tests to account for socket removal in the Session class's _shutdown method.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:340
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/178/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/416
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/327
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/327
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/327
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/327
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/327
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/327
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/319/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/418
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/399
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/399
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/399
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/398
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/398/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/398
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/398/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/398
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/398/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/398
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/398/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/398
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/398/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/398
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/398/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/178/rebuild

review: Approve (continuous-integration)
341. By Christopher Townsend

Merge lp:libertine.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:341
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/181/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/420
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/331
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/331
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/331
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/331
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/331
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/331
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/322/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/422
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/403
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/403
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/403
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/402
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/402/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/402
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/402/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/402
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/402/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/402
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/402/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/402
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/402/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/402
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/402/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/181/rebuild

review: Approve (continuous-integration)
342. By Christopher Townsend

Use 'with' to open the Session class ContextManager in order to force the _shutdown callback to be called on Vivid.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:342
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/182/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/422
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/333
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/333
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/333
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/333
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/333
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/333
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/323/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/424
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/405
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/405
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/405
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/404
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/404/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/404
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/404/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/404
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/404/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/404
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/404/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/404
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/404/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/404
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/404/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/182/rebuild

review: Approve (continuous-integration)
343. By Christopher Townsend

Need to use 'argparse.REMAINDER' to capture the whole passed in exec line, otherwise, options passed in with the exec line will be treated like options on the libertine-laucnh exec line and cause failures.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:343
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/184/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/424
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/334
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/334
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/334
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/334
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/334
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/334
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/325/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/426
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/407
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/407
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/407
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/406
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/406/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/406
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/406/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/406
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/406/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/406
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/406/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/406
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/406/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/406
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/406/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/184/rebuild

review: Approve (continuous-integration)
344. By Christopher Townsend

Split out some test arguments to make testing a bit easier.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:344
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/186/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/426
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/336
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/336
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/336
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/336
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/336
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/336
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/327/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/428
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/409
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/409
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/409
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/408
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/408/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/408
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/408/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/408
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/408/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/408
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/408/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/408
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/408/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/408
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/408/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/186/rebuild

review: Approve (continuous-integration)
345. By Christopher Townsend

Add test/unit/.cache to .bzrignore.

346. By Christopher Townsend

Remove unnecessary import of ContextDecorator.

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:345
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/187/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/428
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/338
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/338
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/338
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/338
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/338
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/338
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/328/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/430
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/411
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/411
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/411
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/410
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/410/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/410
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/410/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/410
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/410/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/410
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/410/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/410
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/410/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/410
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/410/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/187/rebuild

review: Approve (continuous-integration)
Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :

PASSED: Continuous integration, rev:346
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/188/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/libertine/job/build/429
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=default/339
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=default/339
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=amd64,release=yakkety,testname=default/339
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=vivid+overlay,testname=default/339
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=xenial+overlay,testname=default/339
    SUCCESS: https://jenkins.canonical.com/libertine/job/test-0-autopkgtest/label=i386,release=yakkety,testname=default/339
    None: https://jenkins.canonical.com/libertine/job/lp-generic-update-mp/329/console
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-0-fetch/431
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=vivid+overlay/412
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=xenial+overlay/412
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-1-sourcepkg/release=yakkety/412
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/411
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=vivid+overlay/411/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/411
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=xenial+overlay/411/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/411
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=amd64,release=yakkety/411/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/411
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=vivid+overlay/411/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/411
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=xenial+overlay/411/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/411
        deb: https://jenkins.canonical.com/libertine/job/build-2-binpkg/arch=i386,release=yakkety/411/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/libertine/job/lp-libertine-ci/188/rebuild

review: Approve (continuous-integration)
Revision history for this message
Larry Price (larryprice) wrote :

Seems good to me, a little slow on starting the first app but I think that's how it's always been. I tested opening a few apps at a time from different containers, copy/paste, and opening apps again after their container should have been closed.

review: Approve
Revision history for this message
Stephen M. Webb (bregma) :
review: Approve
Revision history for this message
Christopher Townsend (townsend) wrote :

Ok, approving this as well. Let's get it merged!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2016-02-29 18:07:02 +0000
3+++ .bzrignore 2016-10-25 17:37:59 +0000
4@@ -1,2 +1,4 @@
5 po/POTFILES.in
6 po/Makefile.in.in
7+__pycache__
8+tests/unit/.cache
9
10=== modified file 'debian/control'
11--- debian/control 2016-10-07 21:09:41 +0000
12+++ debian/control 2016-10-25 17:37:59 +0000
13@@ -19,11 +19,13 @@
14 pkg-config,
15 python3-apt,
16 python3-dbus,
17+ python3-dbusmock,
18 python3-dev,
19 python3-distro-info,
20 python3-gi,
21 python3-lxc,
22 python3-psutil,
23+ python3-pytest,
24 python3-testtools,
25 python3-xdg,
26 qtdeclarative5-dev
27
28=== modified file 'debian/python3-libertine.install'
29--- debian/python3-libertine.install 2016-10-07 21:09:41 +0000
30+++ debian/python3-libertine.install 2016-10-25 17:37:59 +0000
31@@ -3,4 +3,5 @@
32 usr/lib/python*/*/libertine/HostInfo.py
33 usr/lib/python*/*/libertine/Libertine.py
34 usr/lib/python*/*/libertine/__init__.py
35+usr/lib/python*/*/libertine/launcher
36 usr/lib/python*/*/libertine/utils.py
37
38=== modified file 'python/libertine/ChrootContainer.py'
39--- python/libertine/ChrootContainer.py 2016-09-30 18:55:22 +0000
40+++ python/libertine/ChrootContainer.py 2016-10-25 17:37:59 +0000
41@@ -199,26 +199,29 @@
42
43 return proot_cmd
44
45- def launch_application(self, app_exec_line):
46+ def start_application(self, app_exec_line, environ):
47 # FIXME: Disabling seccomp is a temporary measure until we fully understand why we need
48 # it or figure out when we need it.
49- os.environ['PROOT_NO_SECCOMP'] = '1'
50+ environ['PROOT_NO_SECCOMP'] = '1'
51
52 # Workaround issue where a custom dconf profile is on the machine
53- if 'DCONF_PROFILE' in os.environ:
54- del os.environ['DCONF_PROFILE']
55+ if 'DCONF_PROFILE' in environ:
56+ del environ['DCONF_PROFILE']
57
58 proot_cmd = self._build_proot_command()
59
60 args = shlex.split(proot_cmd)
61 args.extend(utils.setup_window_manager(self.container_id, enable_toolbars=True))
62- window_manager = psutil.Popen(args)
63+ window_manager = psutil.Popen(args, env=environ)
64
65 args = shlex.split(proot_cmd)
66 args.extend(app_exec_line)
67- psutil.Popen(args).wait()
68+ app = psutil.Popen(args, env=environ)
69+ return app
70
71+ def finish_application(self, app):
72 utils.terminate_window_manager(window_manager)
73+ app.wait()
74
75 def _run_ldconfig(self, verbosity=1):
76 if verbosity == 1:
77@@ -227,4 +230,8 @@
78 command_line = self._build_privileged_proot_cmd() + " ldconfig.REAL"
79
80 args = shlex.split(command_line)
81- subprocess.Popen(args).wait()
82+ app = subprocess.Popen(args)
83+ return app
84+
85+ def finish_application(self, app):
86+ app.wait()
87
88=== modified file 'python/libertine/Libertine.py'
89--- python/libertine/Libertine.py 2016-10-13 14:57:33 +0000
90+++ python/libertine/Libertine.py 2016-10-25 17:37:59 +0000
91@@ -249,11 +249,14 @@
92 def run_in_container(self, command_string):
93 return 0
94
95- def launch_application(self, app_exec_line):
96+ def start_application(self, app_exec_line, environ):
97 import subprocess
98
99- cmd = subprocess.Popen(app_exec_line)
100- cmd.wait()
101+ app = subprocess.Popen(app_exec_line, env=environ)
102+ return app
103+
104+ def finish_application(self, app):
105+ app.wait()
106
107
108 class ContainerRunning(contextlib.ExitStack):
109@@ -381,6 +384,35 @@
110 except RuntimeError as e:
111 return handle_runtime_error(e)
112
113+ def connect(self):
114+ """
115+ Connects to the container in preparation to launch an application. May
116+ do something like start up daemons or bind-mount directories, I dunno,
117+ it's up to the concrete container class. Maybe it does nothing.
118+ """
119+ pass
120+
121+ def disconnect(self):
122+ """
123+ The inverse of connect() above.
124+ """
125+ pass
126+
127+ def start_application(self, app_exec_line, environ):
128+ """
129+ Launches an application in the container.
130+
131+ :param app_exec_line: the application exec line as passed in by
132+ ubuntu-app-launch
133+ """
134+ return self.container.start_application(app_exec_line, environ)
135+
136+ def finish_application(self, app):
137+ """
138+ Finishes the currently running application in the container.
139+ """
140+ self.container.finish_application(app)
141+
142 def launch_application(self, app_exec_line):
143 """
144 Launches an application in the container.
145@@ -393,7 +425,8 @@
146 if '/usr/games' not in os.environ['PATH']:
147 os.environ['PATH'] = os.environ['PATH'] + ":/usr/games"
148
149- self.container.launch_application(app_exec_line)
150+ app = self.start_application(app_exec_line, environ=os.environ)
151+ self.finish_application(app)
152 else:
153 raise RuntimeError("Container with id %s does not exist." % self.container.container_id)
154
155
156=== modified file 'python/libertine/LxcContainer.py'
157--- python/libertine/LxcContainer.py 2016-10-07 15:42:59 +0000
158+++ python/libertine/LxcContainer.py 2016-10-25 17:37:59 +0000
159@@ -139,6 +139,7 @@
160 self.container = lxc_container(container_id)
161 self._set_lxc_log()
162 self.lxc_manager_interface = None
163+ self.window_manager = None
164
165 utils.set_session_dbus_env_var()
166
167@@ -309,11 +310,14 @@
168 # Dump it all to disk
169 self.container.save_config()
170
171- def launch_application(self, app_exec_line):
172+ def start_application(self, app_exec_line, environ):
173 if self.lxc_manager_interface == None:
174 print("No interface to libertine-lxc-manager. Failing application launch.")
175 return
176
177+ os.environ.clear()
178+ os.environ.update(environ)
179+
180 (result, error) = self.lxc_manager_interface.app_start(self.container_id, self.lxc_log_file)
181
182 if not result:
183@@ -321,18 +325,22 @@
184 print("%s" % error)
185 return
186
187- window_manager = self.container.attach(lxc.attach_run_command,
188- utils.setup_window_manager(self.container_id))
189+ self.window_manager = self.container.attach(lxc.attach_run_command,
190+ utils.setup_window_manager(self.container_id))
191
192 # Setup pulse to work inside the container
193 os.environ['PULSE_SERVER'] = utils.get_libertine_lxc_pulse_socket_path()
194
195 app_launch_cmd = "sudo -E -u " + os.environ['USER'] + " env PATH=" + os.environ['PATH']
196 cmd = shlex.split(app_launch_cmd)
197- self.container.attach_wait(lxc.attach_run_command,
198- cmd + app_exec_line)
199-
200- utils.terminate_window_manager(psutil.Process(window_manager))
201+ app = self.container.attach(lxc.attach_run_command,
202+ cmd + app_exec_line)
203+ return psutil.Process(app)
204+
205+ def finish_application(self, app):
206+ os.waitpid(app.pid, 0)
207+
208+ utils.terminate_window_manager(psutil.Process(self.window_manager))
209
210 # Tell libertine-lxc-manager that the app has stopped.
211 self.lxc_manager_interface.app_stop(self.container_id)
212
213=== added directory 'python/libertine/launcher'
214=== added file 'python/libertine/launcher/__init__.py'
215--- python/libertine/launcher/__init__.py 1970-01-01 00:00:00 +0000
216+++ python/libertine/launcher/__init__.py 2016-10-25 17:37:59 +0000
217@@ -0,0 +1,28 @@
218+# Copyright 2016 Canonical Ltd.
219+#
220+# This program is free software: you can redistribute it and/or modify it
221+# under the terms of the GNU General Public License version 3, as published
222+# by the Free Software Foundation.
223+#
224+# This program is distributed in the hope that it will be useful, but
225+# WITHOUT ANY WARRANTY; without even the implied warranties of
226+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
227+# PURPOSE. See the GNU General Public License for more details.
228+#
229+# You should have received a copy of the GNU General Public License along
230+# with this program. If not, see <http://www.gnu.org/licenses/>.
231+
232+"""Provides the Libertine launcher functionality.
233+
234+All the things used specifically for launching and running an application under
235+a Libertine aegis are in this subpackage. It is the principal guts of the
236+libertine-launch tool and associated test suites.
237+
238+This is the public interface of the Libertine launcher package.
239+"""
240+
241+from .config import Config, SocketBridge
242+from .session import Session, translate_to_real_address
243+from .task import LaunchServiceTask, TaskConfig, TaskType
244+
245+__all__ = ('Config', 'Session')
246
247=== added file 'python/libertine/launcher/config.py'
248--- python/libertine/launcher/config.py 1970-01-01 00:00:00 +0000
249+++ python/libertine/launcher/config.py 2016-10-25 17:37:59 +0000
250@@ -0,0 +1,290 @@
251+# Copyright 2016 Canonical Ltd.
252+#
253+# This program is free software: you can redistribute it and/or modify it
254+# under the terms of the GNU General Public License version 3, as published
255+# by the Free Software Foundation.
256+#
257+# This program is distributed in the hope that it will be useful, but
258+# WITHOUT ANY WARRANTY; without even the implied warranties of
259+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
260+# PURPOSE. See the GNU General Public License for more details.
261+#
262+# You should have received a copy of the GNU General Public License along
263+# with this program. If not, see <http://www.gnu.org/licenses/>.
264+
265+"""Configure the libertine launcher."""
266+
267+import argparse
268+import dbus
269+import os
270+import random
271+import string
272+import sys
273+from .task import TaskType, TaskConfig
274+from .. import utils
275+
276+import gettext
277+gettext.textdomain('libertine')
278+_ = gettext.gettext
279+
280+log = utils.get_logger()
281+
282+
283+def _generate_unique_id():
284+ """Generate a (hopefully) unique identifier string."""
285+ return ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(8))
286+
287+
288+class SocketBridge(object):
289+ """Configuration for a single socket bridge entry.
290+
291+ This is a 3-tuple of a label, the address of a Unix-domain socket in the
292+ host environment, and the address of a Unix-domain socket in the session
293+ environment.
294+
295+ .. data:: env_var
296+
297+ The label of the socket bridge. This is used as the environment variable
298+ in the session environment. It should follow the naming rules for
299+ environment variables.
300+
301+ .. data:: host_address
302+
303+ The address of a Unix-domain socket in the host environment.
304+
305+ .. data:: session_address
306+
307+ The address of a Unix-domain socket in the session environment.
308+
309+ """
310+
311+ def __init__(self, env_var, host_address, session_address):
312+ """Initialize the socket bridge address pair.
313+
314+ :param env_var: The socket bridge label.
315+ :param host_address: The host socket address.
316+ :param session_address: The session socket address.
317+
318+ """
319+ self.env_var = env_var
320+ self.host_address = host_address
321+ self.session_address = session_address
322+
323+ def __repr__(self):
324+ """Get a human-readable string representation."""
325+ return '{{{}:{}->{}}}'.format(self.env_var, self.host_address, self.session_address)
326+
327+
328+def _get_maliit_address_from_dbus():
329+ """Query the session D-Bus for the address of the Maliit server.
330+
331+ If there is no response from the D-Bus (let's just say there is no maliit
332+ server running) None is returned.
333+ """
334+ try:
335+ address_bus_name = 'org.maliit.server'
336+ address_object_path = '/org/maliit/server/address'
337+ address_interface = 'org.maliit.Server.Address'
338+ address_property = 'address'
339+
340+ session_bus = dbus.SessionBus()
341+ maliit_object = session_bus.get_object('org.maliit.server', '/org/maliit/server/address')
342+ interface = dbus.Interface(maliit_object, dbus.PROPERTIES_IFACE)
343+ return interface.Get('org.maliit.Server.Address', 'address')
344+ except Exception as ex:
345+ log.warning(ex)
346+ return None
347+
348+
349+class Config(object):
350+ """Configuration for the libertine application launcher.
351+
352+ The main configuration items are the container ID and the application
353+ command line. Additional configuration items include and environment
354+ variables to be passed in to the application session and any sockets to be
355+ bridged between the session and the host.
356+
357+ The following members can be read from objects of this class.
358+
359+ ================== ==================
360+ Attribute Description
361+ ------------------ ------------------
362+ **id** A unique session identifier. A random string of letters and numbers.
363+ **container_id** The ID of the container in which the session will run.
364+ **exec_line** The program and arguments to execute.
365+ **host_environ** A sanitized dictionary of environment variables to
366+ export in host operations.
367+ **session_environ** A sanitized, remapped, and extended dictionary of environment variables to
368+ export in the session environment. Effectively a copy of the host
369+ environment but with some changes.
370+ **socket_bridges** A collection of SocketBridge objects to implement.
371+ **prelaunch_tasks** A collection of tasks to perform before launching the application.
372+ ================== ==================
373+
374+ """
375+
376+ def __init__(self, argv=sys.argv[1:]):
377+ """
378+ :param argv: Command-line arguments used to configure the launcher. Default is ``sys.argv``.
379+
380+ """
381+ log.debug('Config.__init__() begins')
382+ arg_parser = argparse.ArgumentParser(description=_('Launch an application natively or in a Libertine container'))
383+ arg_parser.add_argument('-i', '--id',
384+ help=_('Container identifier when launching containerized apps'))
385+ arg_parser.add_argument('-E', '--env',
386+ default=[],
387+ dest='environ',
388+ action='append',
389+ help=_('Set an environment variable'))
390+ arg_parser.add_argument('app_exec_line',
391+ nargs=argparse.REMAINDER,
392+ help=_('exec line'))
393+ options = arg_parser.parse_args(args=argv)
394+
395+ if not options.app_exec_line:
396+ arg_parser.error('Must specify an exec line')
397+
398+ if options.id:
399+ self.container_id = options.id
400+ else:
401+ self.container_id = None
402+
403+ self.id = _generate_unique_id()
404+ self.exec_line = options.app_exec_line
405+ self.host_environ = self._sanitize_host_environment(options)
406+ self.socket_bridges = self._create_socket_bridges()
407+ self.prelaunch_tasks = self._add_prelaunch_tasks()
408+ self.session_environ = self._generate_session_environment()
409+
410+ log.debug('id = "{}"'.format(self.id))
411+ log.debug('container_id = "{}"'.format(self.container_id))
412+ log.debug('exec_line = "{}"'.format(self.exec_line))
413+ log.debug('session_environ = {}'.format(self.session_environ))
414+ for bridge in self.socket_bridges:
415+ log.debug('socket bridge: {}'.format(bridge))
416+ log.debug('Config.__init__() ends')
417+
418+ def _sanitize_host_environment(self, options):
419+ """Create a dictionary of sanitized environment variables.
420+
421+ The environment available in the Libertine aegis are effectively those
422+ of the launcher, but santized to remove any unwanted or dangerous stuff
423+ and with and changes requested on the command line.
424+
425+ :param options: A collection of option values including a dictionary
426+ named 'environ'.
427+ :param type: An argparse namespace.
428+
429+ :returns: A sanitized host environment dictionary.
430+ """
431+ # inherit from the host
432+ environ = os.environ.copy()
433+
434+ # remove problematic environment variables
435+ for e in ['QT_QPA_PLATFORM', 'LD_LIBRARY_PATH', 'FAKECHROOT_BASE', 'FAKECHROOT_CMD_SUBST']:
436+ if e in environ:
437+ del environ[e]
438+
439+ # add or replace CLI-speicifed variables
440+ for e in options.environ:
441+ k, v = e.split(sep='=', maxsplit=2)
442+ environ[k] = v
443+
444+ return environ
445+
446+ def _generate_session_environment(self):
447+ """Generate the session environment."""
448+
449+ # Start with the host environment.
450+ environ = self.host_environ.copy()
451+
452+ # Fudge some values.
453+ path = environ.get('PATH', '/usr/bin')
454+ if '/usr/games' not in path.split(sep=':'):
455+ environ['PATH'] = path + os.pathsep + '/usr/games'
456+
457+ # Add the remapped socket bridge variables.
458+ for bridge in self.socket_bridges:
459+ environ[bridge.env_var] = 'unix:path=' + bridge.session_address
460+
461+ return environ
462+
463+ def _add_prelaunch_tasks(self):
464+ """Create a collection of pre-launch tasks."""
465+ tasks = []
466+ tasks.append(TaskConfig(TaskType.LAUNCH_SERVICE, ["pasted"]))
467+ return tasks
468+
469+ def _create_socket_bridges(self):
470+ """Create the required socket bridge configs."""
471+ bridges = []
472+ maliit_host_bridge = self._create_maliit_host_bridge()
473+ if maliit_host_bridge:
474+ bridges.append(maliit_host_bridge)
475+ dbus_host_bridge = self._create_dbus_host_bridge()
476+ if dbus_host_bridge:
477+ bridges.append(dbus_host_bridge)
478+ return bridges
479+
480+ def _generate_session_socket_name(self, socket_target):
481+ """Generate a socket name for the target.
482+
483+ :param target: A string used as a base for the socket address.
484+
485+ If there is a seession ID, the session ID is appended to the socket name
486+ to make it (hopefully) unique.
487+ """
488+ socket_name = os.path.join(utils.get_libertine_runtime_dir(), socket_target)
489+ if self.id:
490+ socket_name += ('-' + self.id)
491+ return socket_name
492+
493+ def _get_dbus_host_address(self):
494+ """Get (or try to get) the D-Bus server socket address.
495+
496+ If the D-Bus server socket address environment variable is set, the
497+ value of that environment variable is used, otherwise None is returned.
498+ """
499+ return self.host_environ.get('DBUS_SESSION_BUS_ADDRESS', None)
500+
501+ def _create_dbus_host_bridge(self):
502+ """Create a socket bridge for the D-Bus session.
503+
504+ Creates a socket bridge configuration for the D-Bus session, if a socket
505+ can be found for the host, otherwise returns None.
506+ """
507+ socket_bridge = None
508+ server_address = self._get_dbus_host_address()
509+ if server_address:
510+ socket_bridge = SocketBridge('DBUS_SESSION_BUS_ADDRESS',
511+ server_address,
512+ self._generate_session_socket_name('dbus'))
513+ return socket_bridge
514+
515+ def _get_maliit_host_address(self):
516+ """Get (or try to get) the Maliit server socket address.
517+
518+ If the Maliit server socket address environment variable is set, the
519+ value of that environment variable is used, otherwise an attempt is made
520+ to query the session D-Bus. If neither works, Maliit is probably not
521+ there.
522+ """
523+ env_var = self.host_environ.get('MALIIT_SERVER_ADDRESS', None)
524+ if env_var:
525+ return env_var
526+ return _get_maliit_address_from_dbus()
527+
528+ def _create_maliit_host_bridge(self):
529+ """Create a socket bridge for the Maliit server.
530+
531+ Creates a socket bridge configuration for the Maliit server, if a socket
532+ can be found for the host, otherwise returns None.
533+ """
534+ socket_bridge = None
535+ server_address = self._get_maliit_host_address()
536+ if server_address:
537+ socket_bridge = SocketBridge('MALIIT_SERVER_ADDRESS',
538+ server_address,
539+ self._generate_session_socket_name('maliit'))
540+ return socket_bridge
541
542=== added file 'python/libertine/launcher/session.py'
543--- python/libertine/launcher/session.py 1970-01-01 00:00:00 +0000
544+++ python/libertine/launcher/session.py 2016-10-25 17:37:59 +0000
545@@ -0,0 +1,340 @@
546+# Copyright 2016 Canonical Ltd.
547+#
548+# This program is free software: you can redistribute it and/or modify it
549+# under the terms of the GNU General Public License version 3, as published
550+# by the Free Software Foundation.
551+#
552+# This program is distributed in the hope that it will be useful, but
553+# WITHOUT ANY WARRANTY; without even the implied warranties of
554+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
555+# PURPOSE. See the GNU General Public License for more details.
556+#
557+# You should have received a copy of the GNU General Public License along
558+# with this program. If not, see <http://www.gnu.org/licenses/>.
559+
560+"""High-level interface for starting and running an application in Libertine."""
561+
562+import os
563+import selectors
564+import signal
565+import struct
566+import sys
567+
568+from .config import Config
569+from .. import utils
570+from contextlib import ExitStack, suppress
571+from psutil import STATUS_ZOMBIE
572+from socket import socket, AF_UNIX, SOCK_STREAM
573+from .task import LaunchServiceTask, TaskType
574+
575+
576+log = utils.get_logger()
577+
578+
579+def translate_to_real_address(abstract_address):
580+ """Translate the notional text address to a real UNIX-domain address string.
581+
582+ :param abstract_address: The human-readable abstract socket address string.
583+
584+ Trims off any suffix starting with a comma and replaces any leading string of
585+ 'unix:abstract=' with a zero byte.
586+ """
587+ addr = abstract_address.split(',')[0]
588+ if addr.startswith('unix:abstract='):
589+ return "\0" + ' '.join(addr.split('=')[1:])
590+ elif addr.startswith('unix:path='):
591+ return ' '.join(addr.split('=')[1:])
592+
593+ return addr
594+
595+
596+class BridgePair(object):
597+ """A pair of sockets that make up a bridge between host and session."""
598+
599+ def __init__(self, session_socket, host_address):
600+ """Create a pair of sockets bridging host and session.
601+
602+ A socket bridge pair takes an (already-opened) session socket and the
603+ address of the host socket and opens a connection to that.
604+ """
605+ self.session_socket = session_socket
606+ self.session_socket.setblocking(False)
607+
608+ self.host_socket = socket(AF_UNIX, SOCK_STREAM)
609+ self.host_socket.connect(translate_to_real_address(host_address))
610+ self.host_socket.setblocking(False)
611+
612+ def handle_read_fd(self, fd, session):
613+ """Handle read-available events on one of the sockets.
614+
615+ :param fd: A file descriptor on which a read is available.
616+ :type fd: int -- valid file descriptor.
617+ :param session: A libertine application session object.
618+ :type session: libertine.launcher.Session
619+
620+ Callback to handle a read-available event on one of the bridge pair
621+ socket fds.
622+ """
623+ if fd == self.session_socket.fileno():
624+ if self._copy_data(self.session_socket, self.host_socket) == 0:
625+ self._close_up_shop(session)
626+ elif fd == self.host_socket.fileno():
627+ if self._copy_data(self.host_socket, self.session_socket) == 0:
628+ self._close_up_shop(session)
629+
630+ def _copy_data(self, from_socket, to_socket):
631+ """Copy data between the sockets.
632+
633+ :param from_socket: A socket to be read from.
634+ :type from_socket: socket-object
635+ :param to_socket: A socket to be written to.
636+ :type to_socket: socket-object
637+
638+ Note that this chunks the data 4 kilobytes at a time (which maybe should
639+ be a tuneable parameter).
640+ Also, it's a non-blocking write and that may affect things. Honestly, it
641+ should be a non-blocking write and logic should be added to track
642+ write-available evnts and unsent bytes and all that stuff but not today.
643+ """
644+ try:
645+ b = from_socket.recv(4096)
646+ if len(b) > 0:
647+ to_socket.sendall(b)
648+ log.debug('copied {} bytes from fd to {}'.format(len(b), from_socket, to_socket))
649+ else:
650+ log.info('close detected on {}'.format(from_socket))
651+ return len(b)
652+ except Exception as e:
653+ log.exception(e)
654+ return 0
655+
656+ def _close_up_shop(self, session):
657+ """Clean up.
658+
659+ :param session: A libertine application session object.
660+ :type session: libertine.launcher.Session
661+
662+ Closes both sockets in the pair and calls back the session to remove
663+ this object from its watch list.
664+ """
665+ session.remove_bridge_pair(self)
666+ self.session_socket.close()
667+ self.host_socket.close()
668+
669+
670+class Session(ExitStack):
671+ """Encapsulation of a running application under a Libertine aegis.
672+
673+ A session includes the following.
674+
675+ * A cleansed and remapped set of environment variables.
676+ * A collection of redirected sockets.
677+ * Zero or more running auxiliary programs.
678+ * An executable command line.
679+
680+ A session must be associated with a single aegis (a container, a snap, or
681+ the host itself, depending). The associated aegis is termed the *container*
682+ for historical reasons.
683+
684+ Each application run under a Libertine aegis must have its own session.
685+
686+ A session is constructed in a 'stopped' state. It needs to be started and
687+ the main loop entered, and once the main loop exits it gets torn down and
688+ returned to a stopped state.
689+ """
690+
691+ def __init__(self, config, container):
692+ """Construct a libertine application session for a container.
693+
694+ :param config: A session configuration object.
695+ :param container: The container in which the application will be run.
696+ """
697+ super().__init__()
698+ self._app = None
699+ self._config = config
700+ self._container = container
701+ self._bridge_pairs = []
702+ self._child_processes = []
703+ self._selector = selectors.DefaultSelector()
704+ self._set_signal_handlers()
705+ self.callback(self._shutdown)
706+
707+ self._ensure_paths_exist()
708+
709+ with suppress(AttributeError):
710+ for bridge_config in self._config.socket_bridges:
711+ self._create_bridge_listener(bridge_config)
712+
713+ with suppress(AttributeError):
714+ for task_config in self._config.prelaunch_tasks:
715+ if task_config.task_type == TaskType.LAUNCH_SERVICE:
716+ log.info("launching {}".format(task_config.datum[0]))
717+ task = LaunchServiceTask(task_config)
718+ self._child_processes.append(task)
719+ task.start(self._config.host_environ)
720+
721+ @property
722+ def id(self):
723+ """A unique string identifying this session."""
724+ return self._config.id
725+
726+ def _add_read_fd_handler(self, fd, handler, datum):
727+ """Add a handler to be called when a read event is received on fd.
728+
729+ :param fd: A file descriptor to watch for read events.
730+ :type fd: int -- valid file descriptor.
731+ :param handler: A function to be called when a read on fd becomes available.
732+ :param datum: Data to be passed to handler when called.
733+ """
734+ self._selector.register(fd, selectors.EVENT_READ, (handler, datum))
735+
736+ def _remove_read_fd_handler(self, fd):
737+ """Remove a handler used for reading events on an fd.
738+
739+ :param fd: A file descriptor to be removed from watching read events.
740+ """
741+ self._selector.unregister(fd)
742+
743+ def add_bridge_pair(self, bridge_pair):
744+ """Add a bridge pair to the list of those being monitored.
745+
746+ :param bridge_pair: an active BridgePair object to add to the watch list.
747+ """
748+ self._add_read_fd_handler(bridge_pair.session_socket.fileno(),
749+ bridge_pair.handle_read_fd,
750+ self)
751+ self._add_read_fd_handler(bridge_pair.host_socket.fileno(),
752+ bridge_pair.handle_read_fd,
753+ self)
754+ self._bridge_pairs.append(bridge_pair)
755+
756+ def remove_bridge_pair(self, bridge_pair):
757+ """Remove a bridge pair from the list of those being monitored.
758+
759+ :param bridge_pair: an active BridgePair object to remove from the watch list.
760+ """
761+ self._remove_read_fd_handler(bridge_pair.session_socket.fileno())
762+ self._remove_read_fd_handler(bridge_pair.host_socket.fileno())
763+ self._bridge_pairs.remove(bridge_pair)
764+
765+ def run(self):
766+ """Run the main event loop of the Session.
767+
768+ The main loop monitors the various socket bridges and the contained
769+ child process(es) and dispatches events appropriately.
770+
771+ The event loop is generally terminated by the receipt of a StopIteration
772+ exception.
773+ """
774+ with suppress(StopIteration):
775+ while True:
776+ events = self._selector.select()
777+ for key, mask in events:
778+ handler, datum = key.data
779+ handler(key.fd, datum)
780+ self._container.finish_application(self._app)
781+ self._stop_services()
782+
783+ def start_application(self):
784+ """Connect to the container and start the application running."""
785+ self._container.connect()
786+ self.callback(self._container.disconnect)
787+ self._app = self._container.start_application(self._config.exec_line,
788+ self._config.session_environ)
789+
790+ def _create_bridge_listener(self, bridge_config):
791+ """Create a socket bridge listener for a socket bridge configuration.
792+
793+ :param bridge_config: A socket bridge configuration object.
794+ :type bridge_config: libertine.launcher.config.SocketBridge
795+
796+ The purpose of the listener is to listen on the session socket and
797+ create a socket bridge to the host when a connection from the session is
798+ made.
799+ """
800+ log.debug('creating bridge listener for {} on {}'.
801+ format(bridge_config.env_var, bridge_config.session_address))
802+ sock = socket(AF_UNIX, SOCK_STREAM)
803+ sock.bind(translate_to_real_address(bridge_config.session_address))
804+ sock.listen(5)
805+ self._add_read_fd_handler(sock.fileno(),
806+ self._accept_bridge_connection,
807+ (bridge_config, sock))
808+
809+ def _accept_bridge_connection(self, fd, datum):
810+ """Handle a connection on a session socket.
811+
812+ :param fd: A file descriptor with an active connect operation in
813+ progress.
814+ :type fd: int -- valid file descriptor.
815+ :param datum: Data passed to handler.
816+ """
817+ (bridge_config, sock) = datum
818+ conn = sock.accept()
819+ log.debug('connection of session socket {} accepted'.format(bridge_config.session_address))
820+ self.add_bridge_pair(BridgePair(conn[0], bridge_config.host_address))
821+
822+ def _ensure_paths_exist(self):
823+ """Ensure the required paths all exist for supporting the session."""
824+ directory = utils.get_libertine_runtime_dir()
825+ if not os.path.exists(directory):
826+ os.makedirs(directory)
827+
828+ def _handle_child_died(self):
829+ """Take action when a SIGCHILD has been raised."""
830+ for child in self._child_processes:
831+ if child.wait():
832+ return True
833+
834+ if self._app == None or self._app.status() == STATUS_ZOMBIE:
835+ return True
836+
837+ return False
838+
839+ def _handle_sig_fd(self, fd, dummy):
840+ """Handle read events for captured signals.
841+
842+ :param fd: A file descriptor on which signal information will be sent.
843+ :type fd: int -- valid file descriptor.
844+ :param dummy: A dummy parameter. Required for magic.
845+ """
846+ data = os.read(fd, 4)
847+ sig = struct.unpack('%uB' % len(data), data)
848+ if sig[0] == signal.SIGCHLD:
849+ log.info('SIGCHLD received')
850+ if self._handle_child_died():
851+ raise StopIteration('launched program exited')
852+ elif sig[0] == signal.SIGINT:
853+ log.info('SIGINT received')
854+ raise StopIteration('keyboard interrupt')
855+ elif sig[0] == signal.SIGTERM:
856+ log.info('SIGTERM received')
857+ raise StopIteration('terminate')
858+ else:
859+ log.warning('unknown signal {} received'.format(sig[0]))
860+
861+ def _set_signal_handlers(self):
862+ """Set the signal handlers."""
863+ def noopSignalHandler(*args):
864+ pass
865+ self._sigchld_handler = signal.signal(signal.SIGCHLD, noopSignalHandler)
866+ self._sigint_handler = signal.signal(signal.SIGINT, noopSignalHandler)
867+ self._sigterm_handler = signal.signal(signal.SIGTERM, noopSignalHandler)
868+
869+ sig_r_fd, sig_w_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
870+ signal.set_wakeup_fd(sig_w_fd)
871+ self._add_read_fd_handler(sig_r_fd, self._handle_sig_fd, None)
872+
873+ def _shutdown(self):
874+ """Restore the previous state to the world when the Session is torn down."""
875+ signal.signal(signal.SIGCHLD, self._sigchld_handler)
876+ signal.signal(signal.SIGINT, self._sigint_handler)
877+ signal.signal(signal.SIGTERM, self._sigterm_handler)
878+
879+ for bridge_pair in self._config.socket_bridges:
880+ os.remove(translate_to_real_address(bridge_pair.session_address))
881+
882+ def _stop_services(self):
883+ """Ask any started services to stop."""
884+ for service in self._child_processes:
885+ service.stop()
886
887=== added file 'python/libertine/launcher/task.py'
888--- python/libertine/launcher/task.py 1970-01-01 00:00:00 +0000
889+++ python/libertine/launcher/task.py 2016-10-25 17:37:59 +0000
890@@ -0,0 +1,72 @@
891+# Copyright 2016 Canonical Ltd.
892+#
893+# This program is free software: you can redistribute it and/or modify it
894+# under the terms of the GNU General Public License version 3, as published
895+# by the Free Software Foundation.
896+#
897+# This program is distributed in the hope that it will be useful, but
898+# WITHOUT ANY WARRANTY; without even the implied warranties of
899+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
900+# PURPOSE. See the GNU General Public License for more details.
901+#
902+# You should have received a copy of the GNU General Public License along
903+# with this program. If not, see <http://www.gnu.org/licenses/>.
904+
905+"""Pre- and post-launch tasks surrounding the Libertine launch of an application."""
906+
907+
908+from os import waitpid, WNOHANG
909+from subprocess import Popen
910+
911+
912+class TaskType:
913+ """Namespace used for task type enumeration."""
914+
915+ LAUNCH_SERVICE = 1
916+
917+
918+class TaskConfig(object):
919+ """Encapsulation of the configuration of a single launch task."""
920+
921+ def __init__(self, task_type, datum):
922+ """
923+ :param task_type: The type of launch task.
924+ :tyoe task_type: a TaskType value
925+ :param datum: The data associated with the launch task.
926+ :type datum: anything -- usually a collection of data
927+ """
928+ self.task_type = task_type
929+ self.datum = datum
930+
931+
932+class LaunchServiceTask(object):
933+ """A task that launches a service."""
934+
935+ def __init__(self, config):
936+ """
937+ :param config: The task configuraiton.
938+ :type config: TaskConfig
939+
940+ The constructor unpacks the service commandline from the config datum.
941+ """
942+ self._command_line = config.datum
943+ self._process = None
944+
945+ def start(self, environ=None):
946+ """Start the service.
947+
948+ :param env: An alternate environment dictionary.
949+ """
950+ self._process = Popen(self._command_line, env=environ)
951+
952+ def stop(self):
953+ """Shuts the service down. """
954+ self._process.terminate()
955+
956+ def wait(self):
957+ """Wait for service shutdown to complete.
958+ :return: True if the service process was successfully waited for, False
959+ otherwise (which implies the service is still running).
960+ """
961+ (pid, status) = waitpid(self._process.pid, WNOHANG)
962+ return pid == self._process.pid
963
964=== modified file 'python/libertine/utils.py'
965--- python/libertine/utils.py 2016-10-19 12:57:34 +0000
966+++ python/libertine/utils.py 2016-10-25 17:37:59 +0000
967@@ -30,24 +30,19 @@
968
969 # If someone else sets a handler before this, we wont run this!
970 if not logger.hasHandlers():
971- logger.setLevel(logging.DEBUG)
972- logger.disabled = True
973-
974 stream_handler = logging.StreamHandler()
975- stream_handler.setLevel(logging.DEBUG)
976-
977 formatter = logging.Formatter('%(filename)s:'
978 '%(lineno)d: '
979 '%(levelname)s: '
980 '%(funcName)s():\t'
981 '%(message)s')
982-
983 stream_handler.setFormatter(formatter)
984 logger.addHandler(stream_handler)
985
986- # Only enable the logger if we set this
987- if os.getenv('LIBERTINE_DEBUG') is '1':
988- logger.disabled = False
989+ if 'LIBERTINE_DEBUG' in os.environ:
990+ logger.setLevel(logging.DEBUG)
991+ else:
992+ logger.setLevel(logging.WARNING)
993
994 return logger
995
996
997=== modified file 'tests/unit/CMakeLists.txt'
998--- tests/unit/CMakeLists.txt 2016-08-09 22:37:08 +0000
999+++ tests/unit/CMakeLists.txt 2016-10-25 17:37:59 +0000
1000@@ -19,59 +19,14 @@
1001 test_container_config
1002 )
1003
1004-add_test(test_libertine_public_gir
1005- "/usr/bin/python3" "-m" "testtools.run" "libertine_public_gir_tests"
1006-)
1007-set_tests_properties(test_libertine_public_gir
1008- PROPERTIES
1009- ENVIRONMENT
1010- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}")
1011-set_tests_properties(test_libertine_public_gir
1012- PROPERTIES
1013- WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
1014-
1015-add_test(test_libertine "/usr/bin/python3" "-m" "testtools.run" "discover" "-s" "${CMAKE_CURRENT_SOURCE_DIR}")
1016+add_test(test_libertine "py.test-3" "${CMAKE_CURRENT_SOURCE_DIR}" "-v" "--junit-xml=test_libertine.xml" "--ignore=${CMAKE_CURRENT_SOURCE_DIR}/test_launcher_with_dbus.py")
1017 set_tests_properties(test_libertine
1018- PROPERTIES
1019- ENVIRONMENT
1020- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};PYTHONPATH=${CMAKE_SOURCE_DIR}/python")
1021-
1022-add_test(test_libertine_container_manager
1023- "/usr/bin/python3" "-m" "testtools.run" "libertine_container_manager_tests"
1024-)
1025-set_tests_properties(test_libertine_container_manager
1026- PROPERTIES
1027- ENVIRONMENT
1028- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
1029-
1030-add_test(test_libertine_launch
1031- "/usr/bin/python3" "-m" "testtools.run" "libertine_launch_tests"
1032-)
1033-set_tests_properties(test_libertine_launch
1034- PROPERTIES
1035- ENVIRONMENT
1036- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
1037-
1038-add_test(test_libertine_session_bridge
1039- "/usr/bin/python3" "-m" "testtools.run" "libertine_session_bridge_tests"
1040-)
1041-set_tests_properties(test_libertine_session_bridge
1042- PROPERTIES
1043- ENVIRONMENT
1044- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
1045-
1046-add_test(test_libertine_socket
1047- "/usr/bin/python3" "-m" "testtools.run" "libertine_socket_tests"
1048-)
1049-set_tests_properties(test_libertine_socket
1050- PROPERTIES
1051- ENVIRONMENT
1052- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
1053-
1054-add_test(test_logger
1055- "/usr/bin/python3" "-m" "testtools.run" "libertine_logger_tests"
1056-)
1057-set_tests_properties(test_logger
1058- PROPERTIES
1059- ENVIRONMENT
1060- "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
1061+ PROPERTIES ENVIRONMENT
1062+ "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR};LIBERTINE_DATA_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
1063+
1064+add_test(test_libertine_with_dbus "py.test-3" "${CMAKE_CURRENT_SOURCE_DIR}/test_launcher_with_dbus.py" "-v" "--junit-xml=test_libertine_with_dbus.xml" )
1065+set_tests_properties(test_libertine_with_dbus
1066+ PROPERTIES
1067+ ENVIRONMENT
1068+ "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
1069+
1070
1071=== removed file 'tests/unit/libertine_public_gir_tests.py'
1072--- tests/unit/libertine_public_gir_tests.py 2015-10-08 14:49:00 +0000
1073+++ tests/unit/libertine_public_gir_tests.py 1970-01-01 00:00:00 +0000
1074@@ -1,63 +0,0 @@
1075-# Copyright 2015 Canonical Ltd.
1076-#
1077-# This program is free software: you can redistribute it and/or modify it
1078-# under the terms of the GNU General Public License version 3, as published
1079-# by the Free Software Foundation.
1080-#
1081-# This program is distributed in the hope that it will be useful, but
1082-# WITHOUT ANY WARRANTY; without even the implied warranties of
1083-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1084-# PURPOSE. See the GNU General Public License for more details.
1085-#
1086-# You should have received a copy of the GNU General Public License along
1087-# with this program. If not, see <http://www.gnu.org/licenses/>.
1088-
1089-import os
1090-from testtools import TestCase
1091-from testtools.matchers import Equals
1092-from gi.repository import Libertine
1093-
1094-class TestLibertineGir(TestCase):
1095-
1096- def setUp(self):
1097- super(TestLibertineGir, self).setUp()
1098- self.cmake_source_dir = os.environ['CMAKE_SOURCE_DIR']
1099-
1100- def test_list_containers(self):
1101- os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-config'
1102-
1103- containers = Libertine.list_containers()
1104-
1105- self.assertThat(containers[0], Equals('wily'))
1106- self.assertThat(containers[1], Equals('wily-2'))
1107-
1108- def test_container_path(self):
1109- container_id = 'wily'
1110- os.environ['XDG_CACHE_HOME'] = self.cmake_source_dir + '/libertine-data'
1111-
1112- container_path = Libertine.container_path(container_id)
1113- self.assertThat(container_path, Equals(self.cmake_source_dir + '/libertine-data/libertine-container/wily/rootfs'))
1114-
1115- def test_container_home_path(self):
1116- container_id = 'wily'
1117- os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-home'
1118-
1119- container_home_path = Libertine.container_home_path(container_id)
1120- self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily'))
1121-
1122- def test_container_name(self):
1123- container_id = 'wily'
1124- os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-config'
1125-
1126- container_name = Libertine.container_name(container_id)
1127- self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'"))
1128-
1129- container_id = 'wily-2'
1130- container_name = Libertine.container_name(container_id)
1131- self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)"))
1132-
1133- def test_list_apps_for_container(self):
1134- os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-config'
1135-
1136- apps = Libertine.list_apps_for_container('wily')
1137- self.assertThat(len(apps), Equals(0))
1138
1139=== renamed file 'tests/unit/libertine_launch_tests.py' => 'tests/unit/test_launcher.py'
1140--- tests/unit/libertine_launch_tests.py 2016-06-30 20:45:38 +0000
1141+++ tests/unit/test_launcher.py 2016-10-25 17:37:59 +0000
1142@@ -1,4 +1,4 @@
1143-# Copyright 2015 Canonical Ltd.
1144+# Copyright 2015-2016 Canonical Ltd.
1145 #
1146 # This program is free software: you can redistribute it and/or modify it
1147 # under the terms of the GNU General Public License version 3, as published
1148@@ -13,15 +13,34 @@
1149 # with this program. If not, see <http://www.gnu.org/licenses/>.
1150
1151 import os
1152+import random
1153 import shlex
1154 import shutil
1155+import signal
1156+import string
1157 import subprocess
1158+import sys
1159 import tempfile
1160
1161 from libertine import LibertineApplication
1162-
1163-from testtools import TestCase
1164-from testtools.matchers import Equals, NotEquals
1165+from libertine import LibertineContainer
1166+from libertine import launcher
1167+
1168+from contextlib import suppress
1169+from io import StringIO
1170+from selectors import DefaultSelector, EVENT_READ
1171+from socket import socket, AF_UNIX, SOCK_STREAM
1172+from testtools import TestCase, ExpectedException
1173+from testtools.matchers import Equals, Not, Contains, MatchesPredicate
1174+from threading import Thread, Barrier, BrokenBarrierError
1175+from time import sleep
1176+from unittest.mock import call, patch, MagicMock
1177+
1178+
1179+def _generate_unique_string(prefix=''):
1180+ """Generates a (hopefully) unique string."""
1181+ return prefix + ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(8))
1182+
1183
1184 class TestLibertineLaunch(TestCase):
1185 def setUp(self):
1186@@ -74,3 +93,537 @@
1187 '''
1188 la = LibertineApplication('test', 'foo')
1189 self.assertRaises(FileNotFoundError, la.launch_application)
1190+
1191+
1192+class TestLauncherTaskConfig(TestCase):
1193+ """Unit tests for libertine.launcher.task module."""
1194+
1195+ def test_task_config_ctor(self):
1196+ """Verify Task constructor sets the required attributes."""
1197+ fake_datum = [self.getUniqueString()]
1198+
1199+ task = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, fake_datum)
1200+
1201+ self.assertThat(task.task_type, Equals(launcher.TaskType.LAUNCH_SERVICE))
1202+ self.assertThat(task.datum, Equals(fake_datum))
1203+
1204+
1205+class TestLauncherConfig(TestCase):
1206+ """
1207+ Verifies the defined behaviour of the Libertine Launcher Config class.
1208+ """
1209+
1210+ # a standard set of command-line arguments for when we don't care
1211+ id_arg = ['-i', 'container-id']
1212+ exec_args = ['exec-line', 'one', 'two', 'three']
1213+ basic_args = id_arg + exec_args
1214+
1215+ def test_unqiue_id_generation(self):
1216+ """Ensure each unique configuration has a unique identifier."""
1217+ config1 = launcher.Config(TestLauncherConfig.basic_args[:])
1218+ config2 = launcher.Config(TestLauncherConfig.basic_args[:])
1219+ self.assertThat(config1.id, Not(Equals(config2.id)))
1220+
1221+ def test_mandatory_args(self):
1222+ """Verify that the mandatory CLI args get put where they're supposed to get put."""
1223+ config = launcher.Config(TestLauncherConfig.basic_args[:])
1224+ self.assertEquals(config.exec_line, TestLauncherConfig.exec_args)
1225+
1226+ @patch('sys.stderr', new_callable=StringIO)
1227+ def test_missing_mandatory_args(self, stderr):
1228+ """Make sure missing mandatory CLI args are handled gracefully.
1229+
1230+ Gracefully means a usage message is printed and the application exists.
1231+ """
1232+ with ExpectedException(SystemExit):
1233+ config = launcher.Config(TestLauncherConfig.id_arg)
1234+ message = stderr.getvalue().strip()
1235+ self.assertThat(message, Contains("usage:"))
1236+
1237+ def test_optional_id_arg(self):
1238+ """Make sure optional container id is handled."""
1239+ config = launcher.Config(TestLauncherConfig.basic_args[:])
1240+ self.assertEquals(config.container_id, 'container-id')
1241+
1242+ def test_missing_optional_id_arg(self):
1243+ """Ensure command parsing works fine when container id is left off"""
1244+ config = launcher.Config(TestLauncherConfig.exec_args)
1245+ self.assertEquals(config.container_id, None)
1246+
1247+ def test_one_explicit_environment_arg(self):
1248+ """Verify that one -E arg gets put into the environment."""
1249+ argv = ['-EMY_ENV=something'] + TestLauncherConfig.basic_args[:]
1250+ config = launcher.Config(argv)
1251+ self.assertIn('MY_ENV', config.session_environ)
1252+
1253+ def test_two_explicit_environment_args(self):
1254+ """Verify that multiple -E args gets put into the environment."""
1255+ argv = ['-EMY_ENV=something', '-E', 'SOME_OTHER_ENV=somethingelse'] + TestLauncherConfig.basic_args[:]
1256+ config = launcher.Config(argv)
1257+ self.assertEquals(config.session_environ.get('MY_ENV', 'nothing'), 'something')
1258+ self.assertEquals(config.session_environ.get('SOME_OTHER_ENV', 'nothing'), 'somethingelse')
1259+
1260+ def test_usr_games_is_in_path(self):
1261+ """Makes sure that /usr/games is in $PATH.
1262+
1263+ This was a problem encountered early on.
1264+ """
1265+ with patch.dict('os.environ', {'PATH': '/usr/local/bin:/bin:/usr/bin'}):
1266+ self.assertNotIn('/usr/games', os.environ['PATH'].split(sep=':'))
1267+ config = launcher.Config(TestLauncherConfig.basic_args[:])
1268+ self.assertIn('/usr/games', config.session_environ['PATH'].split(sep=':'))
1269+
1270+ def test_maliit_socket_bridge_from_env(self):
1271+ """Make sure the Maliit socket bridge gets configured.
1272+
1273+ The Maliit socket address is normally found in an environment variable.
1274+ """
1275+ env_key = 'MALIIT_SERVER_ADDRESS'
1276+ bogus_host_address = 'unix:abstract=/tmp/maliit-host-socket'
1277+ maliit_bridge = None
1278+
1279+ with patch.dict('os.environ', {env_key: bogus_host_address}):
1280+ config = launcher.Config(TestLauncherConfig.basic_args[:])
1281+ for bridge in config.socket_bridges:
1282+ if bridge.env_var == env_key:
1283+ maliit_bridge = bridge
1284+
1285+ self.assertIsNotNone(maliit_bridge)
1286+ self.assertThat(maliit_bridge.host_address, Equals(bogus_host_address))
1287+ self.assertThat(maliit_bridge.session_address, Contains('maliit'))
1288+ self.assertThat(config.session_environ.get(env_key, None), Not(Equals(None)))
1289+ self.assertThat(config.session_environ.get(env_key, bogus_host_address),
1290+ Not(Equals(bogus_host_address)))
1291+
1292+ def test_dbus_socket_bridge_from_env(self):
1293+ """Make sure the D-Bus socket bridge gets configured.
1294+
1295+ The D-Bus socket address is normally found in an environment variable.
1296+ """
1297+ env_key = 'DBUS_SESSION_BUS_ADDRESS'
1298+ bogus_host_address = 'unix:abstract=/tmp/dbus-host-socket'
1299+ dbus_bridge = None
1300+
1301+ with patch.dict('os.environ', {env_key: bogus_host_address}):
1302+ config = launcher.Config(TestLauncherConfig.basic_args[:])
1303+ for bridge in config.socket_bridges:
1304+ if bridge.env_var == env_key:
1305+ dbus_bridge = bridge
1306+
1307+ self.assertIsNotNone(dbus_bridge)
1308+ self.assertThat(dbus_bridge.host_address, Equals(bogus_host_address))
1309+ self.assertThat(dbus_bridge.session_address, Contains('dbus'))
1310+ self.assertThat(config.session_environ.get(env_key, None), Not(Equals(None)))
1311+ self.assertThat(config.session_environ.get(env_key, bogus_host_address),
1312+ Not(Equals(bogus_host_address)))
1313+
1314+ def test_default_prelaunch_tasks(self):
1315+ """Ensure 'pasted' is in the default pre-launch task list."""
1316+ def pasted_is_in_list(task_list):
1317+ for t in task_list:
1318+ if t.task_type == launcher.TaskType.LAUNCH_SERVICE and t.datum[0] == "pasted":
1319+ return True
1320+ return False
1321+
1322+ config = launcher.Config(TestLauncherConfig.basic_args[:])
1323+ self.assertThat(config.prelaunch_tasks,
1324+ MatchesPredicate(pasted_is_in_list, "pasted is not in task list"))
1325+
1326+
1327+class TestLauncherSession(TestCase):
1328+ """
1329+ Verifies the defined bahaviour of a Libertine Lunacher session.
1330+
1331+ This set of tests does the alien probe dance on things that can be verified
1332+ without actually requiring the launcher event loop be runing -- except for
1333+ the basic check that an event loop runs and can be terminated.
1334+ """
1335+
1336+ def setUp(self):
1337+ """Set up a mock config and a mock container."""
1338+ super().setUp()
1339+
1340+ fake_session_socket = 'unix:path=/tmp/garbage'
1341+ fake_bridge_config = launcher.SocketBridge('FAKE_SOCKET', 'dummy', fake_session_socket)
1342+ self._mock_config = MagicMock(spec=launcher.Config,
1343+ socket_bridges=[fake_bridge_config])
1344+
1345+ self._fake_session_address = launcher.translate_to_real_address(fake_session_socket)
1346+
1347+ self._mock_container = MagicMock(spec=LibertineContainer)
1348+
1349+ def test_basic_ctor(self):
1350+ """Just test the basic no-frill construction of a session."""
1351+ self._mock_config.id = 'blah'
1352+ session = launcher.Session(self._mock_config, self._mock_container)
1353+ self.assertThat(session.id, Equals('blah'))
1354+
1355+ def test_signals_are_restored(self):
1356+ """Verify that a Session object, when used correctly, cleans up any signal
1357+ handlers it has replaced.
1358+
1359+ This test just makes sure the test environment is left in a clean state
1360+ after testing so that it does not corrupt the results of other unit
1361+ tests.
1362+ """
1363+ old_sigchld_handler = signal.getsignal(signal.SIGCHLD)
1364+ old_sigint_handler = signal.getsignal(signal.SIGINT)
1365+ old_sigterm_handler = signal.getsignal(signal.SIGTERM)
1366+
1367+ with launcher.Session(self._mock_config, self._mock_container) as session:
1368+ pass
1369+
1370+ self.assertThat(signal.getsignal(signal.SIGCHLD), Equals(old_sigchld_handler))
1371+ self.assertThat(signal.getsignal(signal.SIGINT), Equals(old_sigint_handler))
1372+ self.assertThat(signal.getsignal(signal.SIGTERM), Equals(old_sigterm_handler))
1373+
1374+ @patch('libertine.launcher.session.socket.listen')
1375+ @patch('libertine.launcher.session.socket.bind')
1376+ def test_bridge_listener_is_created(self, mock_bind, mock_listen):
1377+ """Verify that a socket connection listener is set up for a socket bridge config passed in."""
1378+ with launcher.Session(self._mock_config, self._mock_container) as session:
1379+ self.assertThat(mock_bind.call_args, Equals(call(self._fake_session_address)))
1380+ self.assertThat(mock_listen.called, Equals(True))
1381+
1382+ def test_sigchld_exits_run(self):
1383+ """Verify that the run() method returns on receipt of SIGCHLD. """
1384+ with launcher.Session(self._mock_config, self._mock_container) as session:
1385+ def run_session_event_loop():
1386+ session.run()
1387+
1388+ session_event_loop = Thread(target=run_session_event_loop)
1389+ session_event_loop.start()
1390+
1391+ os.kill(os.getpid(), signal.SIGCHLD)
1392+
1393+ session_event_loop.join(timeout=0.5)
1394+ self.assertThat(session_event_loop.is_alive(), Equals(False))
1395+
1396+ def test_sigint_exits_run(self):
1397+ """Verify that the run() method returns on receipt of SIGINT. """
1398+ with launcher.Session(self._mock_config, self._mock_container) as session:
1399+ def run_session_event_loop():
1400+ session.run()
1401+
1402+ session_event_loop = Thread(target=run_session_event_loop)
1403+ session_event_loop.start()
1404+
1405+ os.kill(os.getpid(), signal.SIGINT)
1406+
1407+ session_event_loop.join(timeout=0.5)
1408+ self.assertThat(session_event_loop.is_alive(), Equals(False))
1409+
1410+ def test_sigterm_exits_run(self):
1411+ """Verify that the run() method returns on receipt of SIGTERM."""
1412+ with launcher.Session(self._mock_config, self._mock_container) as session:
1413+ def run_session_event_loop():
1414+ session.run()
1415+
1416+ session_event_loop = Thread(target=run_session_event_loop)
1417+ session_event_loop.start()
1418+
1419+ os.kill(os.getpid(), signal.SIGTERM)
1420+
1421+ session_event_loop.join(timeout=0.5)
1422+ self.assertThat(session_event_loop.is_alive(), Equals(False))
1423+
1424+
1425+class TestLauncherServiceTask(TestCase):
1426+ """Verify the expected bahaviour of launch tasks."""
1427+
1428+ def setUp(self):
1429+ """Set up an even loop fixture to watch for signals from spawned tasks. """
1430+ super().setUp()
1431+ self._sigchld_caught = False
1432+
1433+ def noopSignalHandler(*args):
1434+ pass
1435+ original_sigchld_handler = signal.signal(signal.SIGCHLD, noopSignalHandler)
1436+ self.addCleanup(lambda: signal.signal(signal.SIGCHLD, original_sigchld_handler))
1437+
1438+ sig_r_fd, sig_w_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
1439+ signal.set_wakeup_fd(sig_w_fd)
1440+
1441+ pre_loop_barrier = Barrier(2)
1442+ self._post_loop_barrier = Barrier(2)
1443+
1444+ def loop():
1445+ pre_loop_barrier.wait(1)
1446+ selector = DefaultSelector()
1447+ selector.register(sig_r_fd, EVENT_READ)
1448+ with suppress(StopIteration):
1449+ while True:
1450+ events = selector.select(timeout=5)
1451+ if len(events) == 0:
1452+ raise StopIteration
1453+ for key, mask in events:
1454+ if key.fd == sig_r_fd:
1455+ data = os.read(sig_r_fd, 4)
1456+ self._sigchld_caught = True
1457+ raise StopIteration
1458+ self._post_loop_barrier.wait(1)
1459+
1460+ test_loop = Thread(target=loop)
1461+ self.addCleanup(lambda: test_loop.join(1))
1462+ test_loop.start()
1463+ pre_loop_barrier.wait(1)
1464+
1465+ def test_service_sends_SIGCHLD_on_exit(self):
1466+ """Verify a SIGCHLD signal gets raised when the service exits.
1467+
1468+ Runs a fast-exiting 'service' (which should finish on its own in a
1469+ reasonable time) and verifies that a SIGCHLD is raised.
1470+ """
1471+ config = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, ("/bin/true"))
1472+ task = launcher.LaunchServiceTask(config)
1473+ task.start()
1474+ self._post_loop_barrier.wait(1)
1475+ self.assertThat(self._sigchld_caught, Equals(True))
1476+
1477+ def test_service_stop_exits_program(self):
1478+ """Verify the service exists when stopped.
1479+
1480+ Runs a long-running service (which should not normally finish on its
1481+ own) and verifies that a SIGCHLD is received after calling stop() on the
1482+ task.
1483+ """
1484+ config = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, ("/usr/bin/yes"))
1485+ task = launcher.LaunchServiceTask(config)
1486+ task.start()
1487+ sleep(0.05)
1488+ task.stop()
1489+ self._post_loop_barrier.wait(1)
1490+ self.assertThat(self._sigchld_caught, Equals(True))
1491+
1492+
1493+class SessionEventLoopRunning(Thread):
1494+ """Provide a running, stoppable session event loop as a context manager thread."""
1495+
1496+ def __init__(self, session):
1497+ super().__init__()
1498+ self._session = session
1499+
1500+ def __enter__(self):
1501+ """Start the session event loop thread running on context entry."""
1502+ self.start()
1503+ return self
1504+
1505+ def __exit__(self, *exc):
1506+ """Shut down the session event loop thread."""
1507+ join_attempt_count = 0
1508+ max_join_attempts = 5
1509+ while self.is_alive() and join_attempt_count < max_join_attempts:
1510+ self.stop()
1511+ self.join(timeout=0.5)
1512+ join_attempt_count += 1
1513+ assert join_attempt_count < max_join_attempts
1514+ return False
1515+
1516+ def run(self):
1517+ with suppress(BrokenBarrierError):
1518+ self._session.run()
1519+
1520+ def stop(self):
1521+ """Stop the session event loop.
1522+
1523+ May be called multiple times without harm.
1524+
1525+ Has a little nap after raising the TERM signal so the GIL gets
1526+ relinquished and the loop thread has a chance to process its signals.
1527+ """
1528+ os.kill(os.getpid(), signal.SIGTERM)
1529+ sleep(0.0001)
1530+
1531+
1532+class EchoServer(Thread):
1533+ """A test fixture providing some kind of service running on the host at a known address."""
1534+
1535+ socket_address = 'unix:path=/tmp/echo-host-' + _generate_unique_string()
1536+
1537+ def __init__(self):
1538+ super().__init__()
1539+ self.daemon = True
1540+
1541+ real_socket_address = launcher.translate_to_real_address(EchoServer.socket_address)
1542+
1543+ self._socket = socket(AF_UNIX, SOCK_STREAM)
1544+ self._socket.bind(real_socket_address)
1545+ self._socket.listen(5)
1546+ self.start()
1547+
1548+ def run(self):
1549+ while True:
1550+ (s, a) = self._socket.accept()
1551+ b = s.recv(512)
1552+ r = bytes("echo<<{}>>".format(b), 'ascii')
1553+ with suppress(BrokenPipeError):
1554+ s.sendall(r)
1555+ s.close()
1556+
1557+
1558+class TestLauncherSessionSocketBridge(TestCase):
1559+ """Verify the Launcher Session socket bridge functionality."""
1560+
1561+ _fake_session_socket = 'unix:path=/tmp/session-' + _generate_unique_string()
1562+
1563+ @patch('test_launcher.LibertineContainer')
1564+ def setUp(self, mock_container):
1565+ """Construct a test fixture with a single socket bridge configuration."""
1566+ super().setUp()
1567+
1568+ real_socket_address = launcher.translate_to_real_address(self._fake_session_socket)
1569+ with suppress(OSError):
1570+ os.remove(real_socket_address)
1571+
1572+ config = MagicMock(spec=launcher.Config,
1573+ socket_bridges=[launcher.SocketBridge('FAKE_SOCKET',
1574+ host_address=EchoServer.socket_address,
1575+ session_address=self._fake_session_socket)])
1576+ self._session = launcher.Session(config, mock_container)
1577+
1578+ def test_abstract_socket_is_used(self):
1579+ bogus_abstract_path = '/tmp/dbus-host-socket'
1580+ bogus_host_address = 'unix:abstract=' + bogus_abstract_path
1581+
1582+ bogus_host_socket = launcher.translate_to_real_address(bogus_host_address)
1583+
1584+ self.assertThat(bogus_host_socket, Equals('\0' + bogus_abstract_path))
1585+
1586+ def test_connection_acceptance(self):
1587+ """Verify that when at attempt is made to connect to a session bridge it is accepted.
1588+
1589+ This test has to perform some ear-wiggling and hop-dart behaviour to
1590+ make sure it waits until the event loop has called the accept() method
1591+ before it tries to check to see if the accept() method has been called
1592+ (hence the thread barrier). Also, because the real socket accept
1593+ operation does not get performed, the mock accept() call can be
1594+ executed several times by the server before we get around to actually
1595+ checking up on it and we only care that it's called at least once, hence
1596+ the 'called_once' flag.
1597+ """
1598+ barrier = Barrier(2, timeout=0.5)
1599+ called_once = False
1600+ real_session_address = launcher.translate_to_real_address(self._fake_session_socket)
1601+
1602+ def fake_accept(*args, called_once=called_once):
1603+ if not called_once:
1604+ called_once = True
1605+ barrier.wait()
1606+ sock = socket(AF_UNIX, SOCK_STREAM)
1607+ return (sock, 'xyzzy')
1608+
1609+ with patch('libertine.launcher.session.socket.accept', side_effect=fake_accept) as mock_accept:
1610+ with SessionEventLoopRunning(self._session):
1611+ sock = socket(AF_UNIX, SOCK_STREAM)
1612+ sock.connect(real_session_address)
1613+ with suppress(BrokenBarrierError):
1614+ barrier.wait()
1615+ self.assertThat(mock_accept.called, Equals(True), "accept() not called")
1616+
1617+ def test_bridge_socket_relay(self):
1618+ """Test that the whole socket bridge relay functionality works.
1619+
1620+ Sure, this is not really a unit test but a black-box functional test.
1621+ """
1622+ echo_server = EchoServer()
1623+ real_session_address = launcher.translate_to_real_address(self._fake_session_socket)
1624+
1625+ with SessionEventLoopRunning(self._session):
1626+ sock = socket(AF_UNIX, SOCK_STREAM)
1627+ sock.connect(real_session_address)
1628+ sock.sendall(bytes('ping', 'ascii'))
1629+ response = str(sock.recv(1024), 'ascii')
1630+ self.assertThat(response, Contains('ping'))
1631+
1632+
1633+class TestLauncherSessionTask(TestCase):
1634+ """Verify how a Session handles Tasks."""
1635+
1636+ @patch('test_launcher.LibertineContainer')
1637+ def setUp(self, mock_container):
1638+ super().setUp()
1639+
1640+ # Monkey-patch the task object created by the LaunchServiceTask class.
1641+ # This is a little awkward by it's the way Python works.
1642+ p = patch('libertine.launcher.session.LaunchServiceTask')
1643+ mock_service_task_class = p.start()
1644+ self._mock_service_task = mock_service_task_class.return_value
1645+ self._mock_service_task.wait = MagicMock(return_value=True)
1646+ self.addCleanup(p.stop)
1647+
1648+ fake_datum = [self.getUniqueString()]
1649+ task = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, fake_datum)
1650+
1651+ config = MagicMock(spec=launcher.Config, socket_bridges=[], prelaunch_tasks=[task], host_environ={})
1652+ self._session = launcher.Session(config, mock_container)
1653+
1654+ def test_session_starts_prelaunch_task(self):
1655+ """Test that a session starts a pre-launch task."""
1656+ self.assertThat(self._mock_service_task.start.called,
1657+ Equals(True),
1658+ message="task.start() not called")
1659+
1660+ def test_session_stops_prelaunch_task(self):
1661+ """Test that a session starts a pre-launch task."""
1662+ with SessionEventLoopRunning(self._session) as event_loop:
1663+ event_loop.stop()
1664+ self.assertThat(self._mock_service_task.stop.called, Equals(True))
1665+
1666+ def test_session_handles_dying_prelaunch_task(self):
1667+ """Test that a session starts a pre-launch task."""
1668+ with SessionEventLoopRunning(self._session) as event_loop:
1669+ os.kill(os.getpid(), signal.SIGCHLD)
1670+ event_loop.stop()
1671+ self.assertThat(self._mock_service_task.wait.called, Equals(True))
1672+
1673+
1674+class TestLauncherContainerBehavior(TestCase):
1675+ """Verify some expected behaviour when it comes to running the contained application."""
1676+
1677+ def setUp(self):
1678+ super().setUp()
1679+
1680+ self._mock_config = MagicMock(spec=launcher.Config,
1681+ socket_bridges=[],
1682+ session_environ={})
1683+
1684+ # Need to fake the ContainersConfig used internally by
1685+ # LibertineContainer... that whole outfit needs to be refactored for
1686+ # better testing next.
1687+ mock_containers_config = MagicMock()
1688+ mock_containers_config.get_container_type = MagicMock(return_value='mock')
1689+
1690+ self._container_proxy = LibertineContainer(container_id='xyzzy',
1691+ containers_config=mock_containers_config)
1692+
1693+ libertine_mock_patcher = patch.object(self._container_proxy, "container", autospec=True)
1694+ self._mock_container = libertine_mock_patcher.start()
1695+ self.addCleanup(libertine_mock_patcher.stop)
1696+
1697+ def test_start_application(self):
1698+ """Test the start_application() function of the session API."""
1699+ self._mock_config.exec_line = ["/bin/echo", "sis", "boom", "bah"]
1700+
1701+ with launcher.Session(self._mock_config, self._container_proxy) as session:
1702+ with SessionEventLoopRunning(session):
1703+ session.start_application()
1704+
1705+ #self.assertThat(self._mock_container.connect.called, Equals(True))
1706+ #self.assertThat(self._mock_container.disconnect.called, Equals(True))
1707+ self.assertThat(self._mock_container.start_application.called, Equals(True))
1708+ self.assertThat(self._mock_container.start_application.call_args[0][0],
1709+ Equals(self._mock_config.exec_line))
1710+ self.assertThat(self._mock_container.finish_application.called, Equals(True))
1711+
1712+ def test_environment_gets_set(self):
1713+ """Verify that the configured variables are set in the contained execution environments."""
1714+ fake_value = _generate_unique_string()
1715+ fake_var = _generate_unique_string(prefix='V')
1716+ self._mock_config.session_environ[fake_var] = fake_value
1717+ self._mock_config.exec_line = ["/dev/null"]
1718+
1719+ with launcher.Session(self._mock_config, self._container_proxy) as session:
1720+ with SessionEventLoopRunning(session):
1721+ session.start_application()
1722+
1723+ self.assertThat(self._mock_container.start_application.call_args[0][1], Contains(fake_var))
1724
1725=== added file 'tests/unit/test_launcher_with_dbus.py'
1726--- tests/unit/test_launcher_with_dbus.py 1970-01-01 00:00:00 +0000
1727+++ tests/unit/test_launcher_with_dbus.py 2016-10-25 17:37:59 +0000
1728@@ -0,0 +1,103 @@
1729+# Copyright 2016 Canonical Ltd.
1730+#
1731+# This program is free software: you can redistribute it and/or modify it
1732+# under the terms of the GNU General Public License version 3, as published
1733+# by the Free Software Foundation.
1734+#
1735+# This program is distributed in the hope that it will be useful, but
1736+# WITHOUT ANY WARRANTY; without even the implied warranties of
1737+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1738+# PURPOSE. See the GNU General Public License for more details.
1739+#
1740+# You should have received a copy of the GNU General Public License along
1741+# with this program. If not, see <http://www.gnu.org/licenses/>.
1742+
1743+import dbus
1744+import dbusmock
1745+import os
1746+import subprocess
1747+
1748+from libertine import launcher
1749+
1750+from testtools import TestCase
1751+from testtools.matchers import Equals, Contains
1752+from unittest.mock import patch
1753+
1754+
1755+
1756+class MockDBusServer(object):
1757+ """
1758+ A mock object that proxies a mock D-Bus server, for testing things that do
1759+ D-Bus operations in the background.
1760+ """
1761+
1762+ def __init__(self, maliit_server_address):
1763+ dbusmock.DBusTestCase.start_session_bus()
1764+ self.maliit_server_address = maliit_server_address
1765+ self._dbus_connection = dbusmock.DBusTestCase.get_dbus(system_bus=False)
1766+
1767+ def setUp(self):
1768+ self._dbus_mock_server = dbusmock.DBusTestCase.spawn_server(
1769+ 'org.maliit.server',
1770+ '/org/maliit/server/address',
1771+ 'org.maliit.Server.Address',
1772+ system_bus=False,
1773+ stdout=subprocess.PIPE)
1774+ self._mock_interface = self._get_mock_interface()
1775+ self._mock_interface.AddProperty('', 'address', self.maliit_server_address)
1776+
1777+ def cleanUp(self):
1778+ self._dbus_mock_server.terminate()
1779+ self._dbus_mock_server.wait()
1780+
1781+ def getDetails(self):
1782+ return { }
1783+
1784+ def _get_mock_interface(self):
1785+ return dbus.Interface(
1786+ self._dbus_connection.get_object(
1787+ 'org.maliit.server',
1788+ '/org/maliit/server/address',
1789+ 'org.maliit.Server.Address'),
1790+ dbusmock.MOCK_IFACE)
1791+
1792+
1793+class TestLauncherConfigUsingDBus(TestCase):
1794+ """
1795+ Verifies the defined behaviour of the Libertine Launcher Config class with
1796+ reference to a D-Bus server.
1797+ """
1798+ def setUp(self):
1799+ """
1800+ Need to save and restore the os.environ for each test in case they
1801+ change things. That's a global that propagates between tests.
1802+ """
1803+ super().setUp()
1804+ fake_maliit_host_address = 'unix:abstract=/tmp/maliit-host-socket'
1805+ self._dbus_server = self.useFixture(MockDBusServer(maliit_server_address=fake_maliit_host_address))
1806+ self._cli = [ self.getUniqueString(), self.getUniqueString() ]
1807+
1808+ def test_maliit_socket_bridge_from_dbus(self):
1809+ """
1810+ Make sure the Maliit socket bridge gets configured.
1811+
1812+ The Maliit service advertizes it's socket on the D-Bus, although the
1813+ environment variable, if present, overrides that so to test the D-Bus
1814+ advert the environment variable must be clear.
1815+ """
1816+ maliit_bridge = None
1817+ with patch.dict('os.environ'):
1818+ if 'MALIIT_SERVER_ADDRESS' in os.environ:
1819+ del os.environ['MALIIT_SERVER_ADDRESS']
1820+
1821+ config = launcher.Config(self._cli[:])
1822+
1823+ for bridge in config.socket_bridges:
1824+ if bridge.env_var == 'MALIIT_SERVER_ADDRESS':
1825+ maliit_bridge = bridge
1826+
1827+ self.assertIsNotNone(maliit_bridge)
1828+ self.assertThat(maliit_bridge.host_address, Equals(self._dbus_server.maliit_server_address))
1829+ self.assertThat(maliit_bridge.session_address, Contains('maliit'))
1830+ self.assertThat(maliit_bridge.session_address, Contains(config.id))
1831+
1832
1833=== renamed file 'tests/unit/libertine_container_manager_tests.py' => 'tests/unit/test_libertine_container_manager.py'
1834=== added file 'tests/unit/test_libertine_gir.py'
1835--- tests/unit/test_libertine_gir.py 1970-01-01 00:00:00 +0000
1836+++ tests/unit/test_libertine_gir.py 2016-10-25 17:37:59 +0000
1837@@ -0,0 +1,67 @@
1838+# Copyright 2015 Canonical Ltd.
1839+#
1840+# This program is free software: you can redistribute it and/or modify it
1841+# under the terms of the GNU General Public License version 3, as published
1842+# by the Free Software Foundation.
1843+#
1844+# This program is distributed in the hope that it will be useful, but
1845+# WITHOUT ANY WARRANTY; without even the implied warranties of
1846+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1847+# PURPOSE. See the GNU General Public License for more details.
1848+#
1849+# You should have received a copy of the GNU General Public License along
1850+# with this program. If not, see <http://www.gnu.org/licenses/>.
1851+
1852+import os
1853+from testtools import TestCase
1854+from testtools.matchers import Equals
1855+from gi.repository import Libertine
1856+from unittest import skip
1857+from unittest.mock import patch
1858+
1859+
1860+class TestLibertineGir(TestCase):
1861+
1862+ def setUp(self):
1863+ super(TestLibertineGir, self).setUp()
1864+ self.cmake_source_dir = os.environ['LIBERTINE_DATA_DIR']
1865+
1866+ def test_list_containers(self):
1867+ with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}):
1868+ containers = Libertine.list_containers()
1869+
1870+ self.assertThat(containers[0], Equals('wily'))
1871+ self.assertThat(containers[1], Equals('wily-2'))
1872+
1873+ @skip("need to work around cached globals in glib")
1874+ def test_container_path(self):
1875+ container_id = 'wily'
1876+ with patch.dict('os.environ', {'XDG_CACHE_HOME': self.cmake_source_dir + '/libertine-data'}):
1877+ container_path = Libertine.container_path(container_id)
1878+
1879+ self.assertThat(container_path, Equals(self.cmake_source_dir + '/libertine-data/libertine-container/wily/rootfs'))
1880+
1881+ def test_container_home_path(self):
1882+ container_id = 'wily'
1883+ with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-home'}):
1884+ container_home_path = Libertine.container_home_path(container_id)
1885+
1886+ self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily'))
1887+
1888+ def test_container_name(self):
1889+ container_id = 'wily'
1890+ with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}):
1891+ container_name = Libertine.container_name(container_id)
1892+
1893+ self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'"))
1894+
1895+ container_id = 'wily-2'
1896+ container_name = Libertine.container_name(container_id)
1897+
1898+ self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)"))
1899+
1900+ def test_list_apps_for_container(self):
1901+ with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}):
1902+ apps = Libertine.list_apps_for_container('wily')
1903+
1904+ self.assertThat(len(apps), Equals(0))
1905
1906=== renamed file 'tests/unit/libertine_logger_tests.py' => 'tests/unit/test_logger.py'
1907--- tests/unit/libertine_logger_tests.py 2016-08-09 23:34:22 +0000
1908+++ tests/unit/test_logger.py 2016-10-25 17:37:59 +0000
1909@@ -12,38 +12,34 @@
1910 # You should have received a copy of the GNU General Public License along
1911 # with this program. If not, see <http://www.gnu.org/licenses/>.
1912
1913+import logging
1914 import os
1915-import shutil
1916
1917 import libertine.utils
1918
1919 from testtools import TestCase
1920 from testtools.matchers import Equals, NotEquals
1921+from unittest.mock import patch
1922+
1923
1924 class TestLogger(TestCase):
1925- def setUp(self):
1926- super(TestLogger, self).setUp()
1927- os.environ['LIBERTINE_DEBUG'] = '1'
1928- self.addCleanup(self.cleanup)
1929-
1930- def cleanup(self):
1931- if os.getenv('LIBERTINE_DEBUG', None) is not None:
1932- del os.environ['LIBERTINE_DEBUG']
1933
1934 def test_logger_off_by_default(self):
1935- # Need to turn off for this test only!
1936- self.cleanup()
1937- os.unsetenv('LIBERTINE_DEBUG')
1938- l = libertine.utils.get_logger()
1939- self.assertTrue(l.disabled)
1940+ with patch.dict('os.environ'):
1941+ if 'LIBERTINE_DEBUG' in os.environ:
1942+ del os.environ['LIBERTINE_DEBUG']
1943+ l = libertine.utils.get_logger()
1944+ self.assertThat(l.getEffectiveLevel(), Equals(logging.WARNING))
1945
1946 def test_logger_on_with_env_var(self):
1947- l = libertine.utils.get_logger()
1948- self.assertFalse(l.disabled)
1949+ with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}):
1950+ l = libertine.utils.get_logger()
1951+ self.assertThat(l.getEffectiveLevel(), Equals(logging.DEBUG))
1952
1953 def test_logger_only_inits_once(self):
1954- l1 = libertine.utils.get_logger()
1955- l2 = libertine.utils.get_logger()
1956- l3 = libertine.utils.get_logger()
1957- self.assertThat(len(l3.handlers), Equals(1))
1958+ with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}):
1959+ l1 = libertine.utils.get_logger()
1960+ l2 = libertine.utils.get_logger()
1961+ l3 = libertine.utils.get_logger()
1962+ self.assertThat(len(l3.handlers), Equals(1))
1963
1964
1965=== renamed file 'tests/unit/libertine_session_bridge_tests.py' => 'tests/unit/test_session_bridge.py'
1966--- tests/unit/libertine_session_bridge_tests.py 2016-08-09 18:18:14 +0000
1967+++ tests/unit/test_session_bridge.py 2016-10-25 17:37:59 +0000
1968@@ -23,6 +23,7 @@
1969
1970 from testtools import TestCase
1971 from testtools.matchers import Equals, NotEquals
1972+from unittest import skip
1973
1974 import time
1975 import threading
1976@@ -73,6 +74,7 @@
1977 self.assertTrue(os.path.exists(self.session_path))
1978
1979
1980+ @skip("to be replaced by launcher.Session tests")
1981 def test_data_read_from_host_to_session(self):
1982 """
1983 This test shows we are able to proxy data from a host socket to a session client.
1984
1985=== renamed file 'tests/unit/libertine_socket_tests.py' => 'tests/unit/test_sockets.py'
1986=== modified file 'tools/libertine-launch'
1987--- tools/libertine-launch 2016-10-25 15:24:08 +0000
1988+++ tools/libertine-launch 2016-10-25 17:37:59 +0000
1989@@ -1,7 +1,7 @@
1990 #!/usr/bin/python3
1991 # -*- coding: utf-8 -*-
1992
1993-# Copyright (C) 2015 Canonical Ltd.
1994+# Copyright (C) 2015-2016 Canonical Ltd.
1995 # Author: Christopher Townsend <christopher.townsend@canonical.com>
1996
1997 # This program is free software: you can redistribute it and/or modify
1998@@ -16,144 +16,20 @@
1999 # You should have received a copy of the GNU General Public License
2000 # along with this program. If not, see <http://www.gnu.org/licenses/>.
2001
2002-import argparse
2003-import dbus
2004 import os
2005-import random
2006-import string
2007-import libertine.utils
2008-import shlex
2009-import time
2010-from libertine import LibertineApplication, HostSessionSocketPair
2011-
2012-
2013-def get_host_maliit_socket():
2014- address_bus_name = 'org.maliit.server'
2015- address_object_path = '/org/maliit/server/address'
2016- address_interface = 'org.maliit.Server.Address'
2017- address_property = 'address'
2018- address = ''
2019-
2020- try:
2021- session_bus = dbus.SessionBus()
2022- maliit_object = session_bus.get_object('org.maliit.server', '/org/maliit/server/address')
2023-
2024- interface = dbus.Interface(maliit_object, dbus.PROPERTIES_IFACE)
2025- address = interface.Get('org.maliit.Server.Address', 'address')
2026-
2027- partition_key = 'unix:abstract='
2028- address = address.split(',')[0]
2029- address = address.partition(partition_key)[2]
2030- address = "\0%s" % address
2031- except:
2032- pass
2033-
2034- return address
2035-
2036-
2037-def get_host_dbus_socket():
2038- host_dbus_socket = ''
2039-
2040- with open(os.path.join(libertine.utils.get_user_runtime_dir(), 'dbus-session'), 'r') as fd:
2041- dbus_session_str = fd.read()
2042-
2043- fd.close()
2044-
2045- dbus_session_split = dbus_session_str.rsplit('=', 1)
2046- if len(dbus_session_split) > 1:
2047- host_dbus_socket = dbus_session_split[1].rstrip('\n')
2048- # We need to add a \0 to the start of an abstract socket path to connect to it
2049- if dbus_session_str.find('abstract') >= 0:
2050- host_dbus_socket = "\0%s" % host_dbus_socket
2051-
2052- return host_dbus_socket
2053-
2054-
2055-def get_session_socket_path(session_socket_name):
2056- unique_id = ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(8))
2057-
2058- if not os.path.exists(libertine.utils.get_libertine_runtime_dir()):
2059- os.makedirs(libertine.utils.get_libertine_runtime_dir())
2060-
2061- session_socket_path = (
2062- os.path.join(libertine.utils.get_libertine_runtime_dir(), session_socket_name + '-' + unique_id))
2063-
2064- return session_socket_path
2065-
2066-
2067-def get_dbus_session_socket_path():
2068- return get_session_socket_path('host_dbus_session')
2069-
2070-
2071-def get_maliit_session_socket_path():
2072- return get_session_socket_path('host_maliit_session')
2073-
2074-
2075-def set_env_socket_path(session_socket_path, session_env):
2076- os.environ[session_env] = "unix:path=" + session_socket_path
2077-
2078-
2079-def set_dbus_env_socket_path(socket_path):
2080- set_env_socket_path(socket_path, 'DBUS_SESSION_BUS_ADDRESS')
2081-
2082-
2083-def set_maliit_env_socket_path(socket_path):
2084- set_env_socket_path(socket_path, 'MALIIT_SERVER_ADDRESS')
2085-
2086-
2087-def detect_session_bridge_socket(session_socket_path):
2088- retries = 0
2089-
2090- while not os.path.exists(session_socket_path):
2091- if retries >= 10:
2092- raise RuntimeError("Timeout waiting for Libertine Dbus session bridge socket")
2093-
2094- print("Libertine Dbus session bridge socket is not ready. Waiting...")
2095- retries += 1
2096- time.sleep(.5)
2097-
2098+
2099+from libertine import launcher, LibertineContainer
2100+
2101+def main():
2102+ config = launcher.Config()
2103+
2104+ if config.container_id:
2105+ container = LibertineContainer(container_id=config.container_id)
2106+ with launcher.Session(config, container) as session:
2107+ session.start_application()
2108+ session.run()
2109+ else:
2110+ os.execvp(config.exec_line[0], config.exec_line[0:])
2111
2112 if __name__ == '__main__':
2113- arg_parser = argparse.ArgumentParser(description='Launch an application natively or in a Libertine container')
2114- arg_parser.add_argument('-i', '--id',
2115- help=("Container identifier when launching containerized apps"))
2116- arg_parser.add_argument('app_exec_line', nargs=argparse.REMAINDER,
2117- help='exec line')
2118- args = arg_parser.parse_args()
2119-
2120- if args.id:
2121- la = LibertineApplication(args.id, args.app_exec_line)
2122-
2123- # remove problematic environment variables
2124- for e in ['QT_QPA_PLATFORM', 'LD_LIBRARY_PATH', 'FAKECHROOT_BASE', 'FAKECHROOT_CMD_SUBST']:
2125- if e in os.environ:
2126- del os.environ[e]
2127-
2128- socket_paths = []
2129-
2130- maliit_host_path = get_host_maliit_socket()
2131-
2132- # Maliit needs to check with the real session dbus, so it needs to go before setting
2133- # the DBUS_SESSION_ADDRESS to the session socket path
2134- if maliit_host_path:
2135- maliit_session_path = get_maliit_session_socket_path()
2136- socket_paths.append(HostSessionSocketPair(maliit_host_path, maliit_session_path))
2137- set_maliit_env_socket_path(maliit_session_path)
2138-
2139- dbus_host_path = get_host_dbus_socket()
2140-
2141- if dbus_host_path:
2142- dbus_session_path = get_dbus_session_socket_path()
2143- socket_paths.append(HostSessionSocketPair(dbus_host_path, dbus_session_path))
2144- set_dbus_env_socket_path(dbus_session_path)
2145-
2146- la.launch_session_bridge(socket_paths)
2147-
2148- # should detect the maliit socket, but dont know if its around or not here.
2149- detect_session_bridge_socket(dbus_session_path)
2150-
2151- la.launch_pasted()
2152-
2153- la.launch_application()
2154- else:
2155- os.execvp(args.app_exec_line[0], args.app_exec_line[0:])
2156+ main()

Subscribers

People subscribed via source and target branches