Merge lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-proposed-201308281228 into lp:ubuntu/saucy-proposed/checkbox

Proposed by Ubuntu Package Importer
Status: Needs review
Proposed branch: lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-proposed-201308281228
Merge into: lp:ubuntu/saucy-proposed/checkbox
Diff against target: 7511 lines (+5851/-0) (has conflicts)
141 files modified
debian/changelog (+64/-0)
debian/po/ast.po (+5/-0)
debian/po/cs.po (+5/-0)
debian/po/de.po (+5/-0)
debian/po/en_AU.po (+5/-0)
debian/po/en_GB.po (+5/-0)
debian/po/es.po (+5/-0)
debian/po/fr.po (+5/-0)
debian/po/gl.po (+5/-0)
debian/po/he.po (+5/-0)
debian/po/hu.po (+5/-0)
debian/po/id.po (+5/-0)
debian/po/it.po (+5/-0)
debian/po/ja.po (+5/-0)
debian/po/nl.po (+5/-0)
debian/po/oc.po (+5/-0)
debian/po/pl.po (+5/-0)
debian/po/pt_BR.po (+5/-0)
debian/po/ro.po (+5/-0)
debian/po/ru.po (+5/-0)
debian/po/tr.po (+5/-0)
debian/po/uk.po (+5/-0)
debian/po/zh_CN.po (+5/-0)
debian/po/zh_TW.po (+5/-0)
jobs/mediacard.txt.in (+91/-0)
jobs/resource.txt.in (+6/-0)
plainbox/MANIFEST.in.OTHER (+9/-0)
plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy (+30/-0)
plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER (+29/-0)
plainbox/docs/dev/architecture.rst.OTHER (+40/-0)
plainbox/docs/dev/old.rst.OTHER (+343/-0)
plainbox/docs/dev/reference.rst.OTHER (+179/-0)
plainbox/docs/dev/resources.rst.OTHER (+259/-0)
plainbox/docs/dev/trusted-launcher.rst (+209/-0)
plainbox/docs/usage.rst.OTHER (+93/-0)
plainbox/mk-venv.sh.OTHER (+195/-0)
plainbox/plainbox/impl/box.py.OTHER (+130/-0)
plainbox/plainbox/impl/commands/checkbox.py.OTHER (+100/-0)
plainbox/plainbox/impl/commands/run.py.OTHER (+340/-0)
plainbox/plainbox/impl/commands/special.py.OTHER (+159/-0)
plainbox/plainbox/impl/commands/sru.py.OTHER (+271/-0)
plainbox/plainbox/impl/commands/test_run.py.OTHER (+142/-0)
plainbox/plainbox/impl/config.py.OTHER (+544/-0)
plainbox/plainbox/impl/job.py.OTHER (+259/-0)
plainbox/plainbox/impl/runner.py.OTHER (+421/-0)
plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER (+395/-0)
plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER (+280/-0)
plainbox/plainbox/impl/test_box.py.OTHER (+260/-0)
plainbox/plainbox/impl/test_job.py.OTHER (+370/-0)
plainbox/setup.py.OTHER (+60/-0)
po/ace.po (+5/-0)
po/af.po (+5/-0)
po/am.po (+5/-0)
po/ar.po (+5/-0)
po/ast.po (+5/-0)
po/az.po (+5/-0)
po/be.po (+5/-0)
po/bg.po (+5/-0)
po/bn.po (+5/-0)
po/bo.po (+5/-0)
po/br.po (+5/-0)
po/bs.po (+5/-0)
po/ca.po (+5/-0)
po/ca@valencia.po (+5/-0)
po/ckb.po (+5/-0)
po/cs.po (+5/-0)
po/cy.po (+5/-0)
po/da.po (+5/-0)
po/de.po (+5/-0)
po/dv.po (+5/-0)
po/el.po (+5/-0)
po/en_AU.po (+5/-0)
po/en_CA.po (+5/-0)
po/en_GB.po (+5/-0)
po/eo.po (+5/-0)
po/es.po (+5/-0)
po/et.po (+5/-0)
po/eu.po (+5/-0)
po/fa.po (+5/-0)
po/fi.po (+5/-0)
po/fr.po (+5/-0)
po/ga.po (+5/-0)
po/gd.po (+5/-0)
po/gl.po (+5/-0)
po/he.po (+5/-0)
po/hi.po (+5/-0)
po/hr.po (+5/-0)
po/hu.po (+5/-0)
po/hy.po (+5/-0)
po/id.po (+5/-0)
po/is.po (+5/-0)
po/it.po (+5/-0)
po/ja.po (+5/-0)
po/jbo.po (+5/-0)
po/ka.po (+5/-0)
po/kk.po (+5/-0)
po/km.po (+5/-0)
po/kn.po (+5/-0)
po/ko.po (+5/-0)
po/ku.po (+5/-0)
po/ky.po (+5/-0)
po/lt.po (+5/-0)
po/lv.po (+5/-0)
po/mk.po (+5/-0)
po/ml.po (+5/-0)
po/mr.po (+5/-0)
po/ms.po (+5/-0)
po/my.po (+5/-0)
po/nb.po (+5/-0)
po/nds.po (+5/-0)
po/ne.po (+5/-0)
po/nl.po (+5/-0)
po/nn.po (+5/-0)
po/oc.po (+5/-0)
po/pl.po (+5/-0)
po/ps.po (+5/-0)
po/pt.po (+5/-0)
po/pt_BR.po (+5/-0)
po/ro.po (+5/-0)
po/ru.po (+5/-0)
po/sd.po (+5/-0)
po/shn.po (+5/-0)
po/si.po (+5/-0)
po/sk.po (+5/-0)
po/sl.po (+5/-0)
po/sq.po (+5/-0)
po/sr.po (+5/-0)
po/sv.po (+5/-0)
po/ta.po (+5/-0)
po/te.po (+5/-0)
po/th.po (+5/-0)
po/tr.po (+5/-0)
po/ug.po (+5/-0)
po/uk.po (+5/-0)
po/ur.po (+5/-0)
po/uz.po (+5/-0)
po/vi.po (+5/-0)
po/zh_CN.po (+5/-0)
po/zh_HK.po (+5/-0)
po/zh_TW.po (+5/-0)
scripts/color_depth_info (+8/-0)
Text conflict in debian/changelog
Text conflict in debian/po/ast.po
Text conflict in debian/po/cs.po
Text conflict in debian/po/de.po
Text conflict in debian/po/en_AU.po
Text conflict in debian/po/en_GB.po
Text conflict in debian/po/es.po
Text conflict in debian/po/fr.po
Text conflict in debian/po/gl.po
Text conflict in debian/po/he.po
Text conflict in debian/po/hu.po
Text conflict in debian/po/id.po
Text conflict in debian/po/it.po
Text conflict in debian/po/ja.po
Text conflict in debian/po/nl.po
Text conflict in debian/po/oc.po
Text conflict in debian/po/pl.po
Text conflict in debian/po/pt_BR.po
Text conflict in debian/po/ro.po
Text conflict in debian/po/ru.po
Text conflict in debian/po/tr.po
Text conflict in debian/po/uk.po
Text conflict in debian/po/zh_CN.po
Text conflict in debian/po/zh_TW.po
Text conflict in jobs/mediacard.txt.in
Conflict adding files to plainbox.  Created directory.
Conflict because plainbox is not versioned, but has versioned children.  Versioned directory.
Contents conflict in plainbox/MANIFEST.in
Contents conflict in plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy
Conflict adding files to plainbox/docs.  Created directory.
Conflict because plainbox/docs is not versioned, but has versioned children.  Versioned directory.
Conflict adding files to plainbox/docs/dev.  Created directory.
Conflict because plainbox/docs/dev is not versioned, but has versioned children.  Versioned directory.
Contents conflict in plainbox/docs/dev/architecture.rst
Contents conflict in plainbox/docs/dev/old.rst
Contents conflict in plainbox/docs/dev/reference.rst
Contents conflict in plainbox/docs/dev/resources.rst
Contents conflict in plainbox/docs/usage.rst
Contents conflict in plainbox/mk-venv.sh
Conflict adding files to plainbox/plainbox.  Created directory.
Conflict because plainbox/plainbox is not versioned, but has versioned children.  Versioned directory.
Conflict adding files to plainbox/plainbox/impl.  Created directory.
Conflict because plainbox/plainbox/impl is not versioned, but has versioned children.  Versioned directory.
Contents conflict in plainbox/plainbox/impl/box.py
Conflict adding files to plainbox/plainbox/impl/commands.  Created directory.
Conflict because plainbox/plainbox/impl/commands is not versioned, but has versioned children.  Versioned directory.
Contents conflict in plainbox/plainbox/impl/commands/checkbox.py
Contents conflict in plainbox/plainbox/impl/commands/run.py
Contents conflict in plainbox/plainbox/impl/commands/special.py
Contents conflict in plainbox/plainbox/impl/commands/sru.py
Contents conflict in plainbox/plainbox/impl/commands/test_run.py
Contents conflict in plainbox/plainbox/impl/config.py
Contents conflict in plainbox/plainbox/impl/job.py
Contents conflict in plainbox/plainbox/impl/runner.py
Conflict adding files to plainbox/plainbox/impl/secure.  Created directory.
Conflict because plainbox/plainbox/impl/secure is not versioned, but has versioned children.  Versioned directory.
Contents conflict in plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py
Contents conflict in plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py
Contents conflict in plainbox/plainbox/impl/test_box.py
Contents conflict in plainbox/plainbox/impl/test_job.py
Contents conflict in plainbox/setup.py
Text conflict in po/ace.po
Text conflict in po/af.po
Text conflict in po/am.po
Text conflict in po/ar.po
Text conflict in po/ast.po
Text conflict in po/az.po
Text conflict in po/be.po
Text conflict in po/bg.po
Text conflict in po/bn.po
Text conflict in po/bo.po
Text conflict in po/br.po
Text conflict in po/bs.po
Text conflict in po/ca.po
Text conflict in po/ca@valencia.po
Text conflict in po/ckb.po
Text conflict in po/cs.po
Text conflict in po/cy.po
Text conflict in po/da.po
Text conflict in po/de.po
Text conflict in po/dv.po
Text conflict in po/el.po
Text conflict in po/en_AU.po
Text conflict in po/en_CA.po
Text conflict in po/en_GB.po
Text conflict in po/eo.po
Text conflict in po/es.po
Text conflict in po/et.po
Text conflict in po/eu.po
Text conflict in po/fa.po
Text conflict in po/fi.po
Text conflict in po/fr.po
Text conflict in po/ga.po
Text conflict in po/gd.po
Text conflict in po/gl.po
Text conflict in po/he.po
Text conflict in po/hi.po
Text conflict in po/hr.po
Text conflict in po/hu.po
Text conflict in po/hy.po
Text conflict in po/id.po
Text conflict in po/is.po
Text conflict in po/it.po
Text conflict in po/ja.po
Text conflict in po/jbo.po
Text conflict in po/ka.po
Text conflict in po/kk.po
Text conflict in po/km.po
Text conflict in po/kn.po
Text conflict in po/ko.po
Text conflict in po/ku.po
Text conflict in po/ky.po
Text conflict in po/lt.po
Text conflict in po/lv.po
Text conflict in po/mk.po
Text conflict in po/ml.po
Text conflict in po/mr.po
Text conflict in po/ms.po
Text conflict in po/my.po
Text conflict in po/nb.po
Text conflict in po/nds.po
Text conflict in po/ne.po
Text conflict in po/nl.po
Text conflict in po/nn.po
Text conflict in po/oc.po
Text conflict in po/pl.po
Text conflict in po/ps.po
Text conflict in po/pt.po
Text conflict in po/pt_BR.po
Text conflict in po/ro.po
Text conflict in po/ru.po
Text conflict in po/sd.po
Text conflict in po/shn.po
Text conflict in po/si.po
Text conflict in po/sk.po
Text conflict in po/sl.po
Text conflict in po/sq.po
Text conflict in po/sr.po
Text conflict in po/sv.po
Text conflict in po/ta.po
Text conflict in po/te.po
Text conflict in po/th.po
Text conflict in po/tr.po
Text conflict in po/ug.po
Text conflict in po/uk.po
Text conflict in po/ur.po
Text conflict in po/uz.po
Text conflict in po/vi.po
Text conflict in po/zh_CN.po
Text conflict in po/zh_HK.po
Text conflict in po/zh_TW.po
Text conflict in scripts/color_depth_info
To merge this branch: bzr merge lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-proposed-201308281228
Reviewer Review Type Date Requested Status
Ubuntu Development Team Pending
Review via email: mp+182624@code.launchpad.net

Description of the change

The package importer has detected a possible inconsistency between the package history in the archive and the history in bzr. As the archive is authoritative the importer has made lp:ubuntu/saucy-proposed/checkbox reflect what is in the archive and the old bzr branch has been pushed to lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-proposed-201308281228. This merge proposal was created so that an Ubuntu developer can review the situations and perform a merge/upload if necessary. There are three typical cases where this can happen.
  1. Where someone pushes a change to bzr and someone else uploads the package without that change. This is the reason that this check is done by the importer. If this appears to be the case then a merge/upload should be done if the changes that were in bzr are still desirable.
  2. The importer incorrectly detected the above situation when someone made a change in bzr and then uploaded it.
  3. The importer incorrectly detected the above situation when someone just uploaded a package and didn't touch bzr.

If this case doesn't appear to be the first situation then set the status of the merge proposal to "Rejected" and help avoid the problem in future by filing a bug at https://bugs.launchpad.net/udd linking to this merge proposal.

(this is an automatically generated message)

To post a comment you must log in.

Unmerged revisions

1884. By Daniel Manrique

* New upstream release (LP: #1180545):

* Launchpad automated translation updates

[ Alberto Milone ]
* scripts/graphics_stress_test, scripts/rotation_test: make sure to
  always reset the "screen" variable. Somehow the NVIDIA driver manages
  to make it unusable after the first time. (LP: #1172667)

[ Brendan Donegan ]
* checkbox/parsers/submission.py - publish kernel-release information to
  interested parties.
* scripts/rendercheck_test - change nargs='+' to action='append' for blacklist
  option so it works as expected.
  jobs/rendercheck.txt.in - blacklist gradients test as it is known to produce
  false positives. (LP: #1093718)
* plugins/hexr_transport.py - added plugin for submitting to HEXR and
  certification based on certify_new_transport from checkbox-certification.
  examples/checkbox-qt.ini - blacklisted hexr_transport as we won't use it
  examples/checkbox-cli.ini - blacklisted hexr_transport as we won't use it
  examples/checkbox-urwid.ini - blacklisted hexr_transport as we won't use it

[ Daniel Manrique ]
* Ensured that button strings from the "continue" dialog are translatable
  (LP: #1176695)

[ Jeff Lane ]
* checkbox/parsers/cpuinfo.py - split on first instance of ':' in cpuinfo
  output lines to avoid splitting into more than 2 items. Also fixed a pep8
  issue discovered while working on this. (LP: #1180496)
* scripts/cpu_offlining: Modified script to no longer offline cpu0 to resolve
  a bug on ARM. Modified output so most of it is redirected to stderr for
  fail cases, we don't need that much for success cases. (LP: #1078897)
* jobs/mediacard.txt.in: Modified test instructions to be less confusing
  (LP: #970857)
* scripts/cpu_topology: define the cpuinfo nested dicts on creation rather
  than define elements during parsing of /proc/cpuinfo (LP: #1111878)
* scripts/lsmod_info: Corrected error handling for the check_output() call to
  trap the correct error. (LP: #1103647)
* jobs/camera.txt.in: removed an extraneous requres line for gir1.2
  scripts/camera_test: added code to determine what version of gst we're
  using and set video type and plugin accordingly. (LP: #1100594)
* scripts/network_check: added ability to specify custom target URL for
  debugging failures (LP: #1128017)
* scripts/removable_storage_test: Added error handling to trap OSError on
  non-writable media and modified output to handle subsequent
  ZeroDivisionError issues when summarizing test results.
  jobs/media.txt.in: Modified instructions for SD/SDHC to specify using
  UNLOCKED cards to avoid issues when testing read-only media (LP: #1153894)

[ Sylvain Pineau ]
* jobs/suspend.txt.in, scripts/gpu_test: Remove the need of running the script
  with the root user, restore the workspaces switch and the HTML5 video
  playback ; remove the extra suspend/resume (LP: #1172851)
* checkbox/parsers/udevadm.py: Only filter devices without product AND vendor
  information (LP: #1167733)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'checkbox/parsers/cpuinfo.py'
=== modified file 'debian/changelog'
--- debian/changelog 2013-08-02 10:56:56 +0000
+++ debian/changelog 2013-08-28 12:36:13 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1checkbox (0.16.4) saucy; urgency=low2checkbox (0.16.4) saucy; urgency=low
23
3 [ Jeff Lane ]4 [ Jeff Lane ]
@@ -224,6 +225,69 @@
224225
225 -- Daniel Manrique <roadmr@ubuntu.com> Wed, 15 May 2013 16:39:12 -0400226 -- Daniel Manrique <roadmr@ubuntu.com> Wed, 15 May 2013 16:39:12 -0400
226227
228=======
229checkbox (0.16.1) saucy; urgency=low
230
231 * New upstream release (LP: #1180545):
232
233 * Launchpad automated translation updates
234
235 [ Alberto Milone ]
236 * scripts/graphics_stress_test, scripts/rotation_test: make sure to
237 always reset the "screen" variable. Somehow the NVIDIA driver manages
238 to make it unusable after the first time. (LP: #1172667)
239
240 [ Brendan Donegan ]
241 * checkbox/parsers/submission.py - publish kernel-release information to
242 interested parties.
243 * scripts/rendercheck_test - change nargs='+' to action='append' for blacklist
244 option so it works as expected.
245 jobs/rendercheck.txt.in - blacklist gradients test as it is known to produce
246 false positives. (LP: #1093718)
247 * plugins/hexr_transport.py - added plugin for submitting to HEXR and
248 certification based on certify_new_transport from checkbox-certification.
249 examples/checkbox-qt.ini - blacklisted hexr_transport as we won't use it
250 examples/checkbox-cli.ini - blacklisted hexr_transport as we won't use it
251 examples/checkbox-urwid.ini - blacklisted hexr_transport as we won't use it
252
253 [ Daniel Manrique ]
254 * Ensured that button strings from the "continue" dialog are translatable
255 (LP: #1176695)
256
257 [ Jeff Lane ]
258 * checkbox/parsers/cpuinfo.py - split on first instance of ':' in cpuinfo
259 output lines to avoid splitting into more than 2 items. Also fixed a pep8
260 issue discovered while working on this. (LP: #1180496)
261 * scripts/cpu_offlining: Modified script to no longer offline cpu0 to resolve
262 a bug on ARM. Modified output so most of it is redirected to stderr for
263 fail cases, we don't need that much for success cases. (LP: #1078897)
264 * jobs/mediacard.txt.in: Modified test instructions to be less confusing
265 (LP: #970857)
266 * scripts/cpu_topology: define the cpuinfo nested dicts on creation rather
267 than define elements during parsing of /proc/cpuinfo (LP: #1111878)
268 * scripts/lsmod_info: Corrected error handling for the check_output() call to
269 trap the correct error. (LP: #1103647)
270 * jobs/camera.txt.in: removed an extraneous requres line for gir1.2
271 scripts/camera_test: added code to determine what version of gst we're
272 using and set video type and plugin accordingly. (LP: #1100594)
273 * scripts/network_check: added ability to specify custom target URL for
274 debugging failures (LP: #1128017)
275 * scripts/removable_storage_test: Added error handling to trap OSError on
276 non-writable media and modified output to handle subsequent
277 ZeroDivisionError issues when summarizing test results.
278 jobs/media.txt.in: Modified instructions for SD/SDHC to specify using
279 UNLOCKED cards to avoid issues when testing read-only media (LP: #1153894)
280
281 [ Sylvain Pineau ]
282 * jobs/suspend.txt.in, scripts/gpu_test: Remove the need of running the script
283 with the root user, restore the workspaces switch and the HTML5 video
284 playback ; remove the extra suspend/resume (LP: #1172851)
285 * checkbox/parsers/udevadm.py: Only filter devices without product AND vendor
286 information (LP: #1167733)
287
288 -- Daniel Manrique <roadmr@ubuntu.com> Wed, 15 May 2013 16:39:12 -0400
289
290>>>>>>> MERGE-SOURCE
227checkbox (0.16) saucy; urgency=low291checkbox (0.16) saucy; urgency=low
228292
229 * New upstream release (LP: #1178403):293 * New upstream release (LP: #1178403):
230294
=== modified file 'debian/po/ast.po'
--- debian/po/ast.po 2013-08-02 10:56:56 +0000
+++ debian/po/ast.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/cs.po'
--- debian/po/cs.po 2013-08-02 10:56:56 +0000
+++ debian/po/cs.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/de.po'
--- debian/po/de.po 2013-08-02 10:56:56 +0000
+++ debian/po/de.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/en_AU.po'
--- debian/po/en_AU.po 2013-08-02 10:56:56 +0000
+++ debian/po/en_AU.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/en_GB.po'
--- debian/po/en_GB.po 2013-08-02 10:56:56 +0000
+++ debian/po/en_GB.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/es.po'
--- debian/po/es.po 2013-08-02 10:56:56 +0000
+++ debian/po/es.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/fr.po'
--- debian/po/fr.po 2013-08-02 10:56:56 +0000
+++ debian/po/fr.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/gl.po'
--- debian/po/gl.po 2013-08-02 10:56:56 +0000
+++ debian/po/gl.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/he.po'
--- debian/po/he.po 2013-08-02 10:56:56 +0000
+++ debian/po/he.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/hu.po'
--- debian/po/hu.po 2013-08-02 10:56:56 +0000
+++ debian/po/hu.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/id.po'
--- debian/po/id.po 2013-08-02 10:56:56 +0000
+++ debian/po/id.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/it.po'
--- debian/po/it.po 2013-08-02 10:56:56 +0000
+++ debian/po/it.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/ja.po'
--- debian/po/ja.po 2013-08-02 10:56:56 +0000
+++ debian/po/ja.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/nl.po'
--- debian/po/nl.po 2013-08-02 10:56:56 +0000
+++ debian/po/nl.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/oc.po'
--- debian/po/oc.po 2013-08-02 10:56:56 +0000
+++ debian/po/oc.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/pl.po'
--- debian/po/pl.po 2013-08-02 10:56:56 +0000
+++ debian/po/pl.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/pt_BR.po'
--- debian/po/pt_BR.po 2013-08-02 10:56:56 +0000
+++ debian/po/pt_BR.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/ro.po'
--- debian/po/ro.po 2013-08-02 10:56:56 +0000
+++ debian/po/ro.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/ru.po'
--- debian/po/ru.po 2013-08-02 10:56:56 +0000
+++ debian/po/ru.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/tr.po'
--- debian/po/tr.po 2013-08-02 10:56:56 +0000
+++ debian/po/tr.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/uk.po'
--- debian/po/uk.po 2013-08-02 10:56:56 +0000
+++ debian/po/uk.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/zh_CN.po'
--- debian/po/zh_CN.po 2013-08-02 10:56:56 +0000
+++ debian/po/zh_CN.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'debian/po/zh_TW.po'
--- debian/po/zh_TW.po 2013-08-02 10:56:56 +0000
+++ debian/po/zh_TW.po 2013-08-28 12:36:13 +0000
@@ -15,8 +15,13 @@
15"MIME-Version: 1.0\n"15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"17"Content-Transfer-Encoding: 8bit\n"
18<<<<<<< TREE
18"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"19"X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
19"X-Generator: Launchpad (build 16700)\n"20"X-Generator: Launchpad (build 16700)\n"
21=======
22"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
23"X-Generator: Launchpad (build 16598)\n"
24>>>>>>> MERGE-SOURCE
2025
21#. Type: string26#. Type: string
22#. Description27#. Description
2328
=== modified file 'jobs/graphics.txt.in'
=== modified file 'jobs/mediacard.txt.in'
--- jobs/mediacard.txt.in 2013-08-02 10:56:56 +0000
+++ jobs/mediacard.txt.in 2013-08-28 12:36:13 +0000
@@ -78,6 +78,52 @@
78 The verification of this test is automated. Do not change the78 The verification of this test is automated. Do not change the
79 automatically selected result.79 automatically selected result.
8080
81<<<<<<< TREE
82=======
83plugin: user-interact
84name: mediacard/sd-insert-after-suspend
85depends: suspend/suspend_advanced
86command: removable_storage_watcher --memorycard insert sdio usb scsi
87_description:
88 PURPOSE:
89 This test will check that the systems media card reader can
90 detect the insertion of an UNLOCKED SD card after the system
91 has been suspended
92 STEPS:
93 1. Click "Test" and insert an UNLOCKED SD card into the reader.
94 If a file browser opens up, you can safely close it.
95 (Note: this test will time-out after 20 seconds.)
96 2. Do not remove the device after this test.
97 VERIFICATION:
98 The verification of this test is automated. Do not change the
99 automatically selected result.
100
101plugin: shell
102name: mediacard/sd-storage-after-suspend
103depends: mediacard/sd-insert-after-suspend
104user: root
105command: removable_storage_test -s 268400000 --memorycard sdio usb scsi
106_description:
107 This test is automated and executes after the mediacard/sd-insert-after-suspend test
108 is run. It tests reading and writing to the SD card after the system has been suspended.
109
110plugin: user-interact
111name: mediacard/sd-remove-after-suspend
112depends: mediacard/sd-insert-after-suspend
113command: removable_storage_watcher --memorycard remove sdio usb scsi
114_description:
115 PURPOSE:
116 This test will check that the system correctly detects
117 the removal of an SD card from the systems card reader
118 after the system has been suspended.
119 STEPS:
120 1. Click "Test" and remove the SD card from the reader.
121 (Note: this test will time-out after 20 seconds.)
122 VERIFICATION:
123 The verification of this test is automated. Do not change the
124 automatically selected result.
125
126>>>>>>> MERGE-SOURCE
81plugin: shell127plugin: shell
82name: mediacard/sd-preinserted128name: mediacard/sd-preinserted
83user: root129user: root
@@ -130,6 +176,51 @@
130 automatically selected result.176 automatically selected result.
131177
132plugin: user-interact178plugin: user-interact
179<<<<<<< TREE
180=======
181name: mediacard/sdhc-insert-after-suspend
182depends: suspend/suspend_advanced
183command: removable_storage_watcher --memorycard insert sdio usb scsi
184_description:
185 PURPOSE:
186 This test will check that the systems media card reader can
187 detect the insertion of an UNLOCKED SDHC media card after the
188 system has been suspended
189 STEPS:
190 1. Click "Test" and insert an UNLOCKED SDHC card into the reader.
191 If a file browser opens up, you can safely close it.
192 (Note: this test will time-out after 20 seconds.)
193 2. Do not remove the device after this test.
194 VERIFICATION:
195 The verification of this test is automated. Do not change the
196 automatically selected result.
197
198plugin: shell
199name: mediacard/sdhc-storage-after-suspend
200depends: mediacard/sdhc-insert-after-suspend
201user: root
202command: removable_storage_test -s 268400000 --memorycard sdio usb scsi
203_description:
204 This test is automated and executes after the mediacard/sdhc-insert-after-suspend test
205 is run. It tests reading and writing to the SDHC card after the system has been suspended.
206
207plugin: user-interact
208name: mediacard/sdhc-remove-after-suspend
209depends: mediacard/sdhc-insert-after-suspend
210command: removable_storage_watcher --memorycard remove sdio usb scsi
211_description:
212 PURPOSE:
213 This test will check that the system correctly detects the removal
214 of an SDHC card from the systems card reader after the system has been suspended.
215 STEPS:
216 1. Click "Test" and remove the SDHC card from the reader.
217 (Note: this test will time-out after 20 seconds.)
218 VERIFICATION:
219 The verification of this test is automated. Do not change the
220 automatically selected result.
221
222plugin: user-interact
223>>>>>>> MERGE-SOURCE
133name: mediacard/cf-insert224name: mediacard/cf-insert
134command: removable_storage_watcher --memorycard insert sdio usb scsi225command: removable_storage_watcher --memorycard insert sdio usb scsi
135_description:226_description:
136227
=== modified file 'jobs/resource.txt.in'
--- jobs/resource.txt.in 2013-07-12 14:51:00 +0000
+++ jobs/resource.txt.in 2013-08-28 12:36:13 +0000
@@ -56,6 +56,12 @@
56command:56command:
57 find -H $(echo "$PATH" | sed -e 's/:/ /g') -maxdepth 1 -type f -executable -printf "name: %f\n\n"57 find -H $(echo "$PATH" | sed -e 's/:/ /g') -maxdepth 1 -type f -executable -printf "name: %f\n\n"
5858
59name: executable
60plugin: resource
61description: Generates a resource for all available executables
62command:
63 find -H $(echo "$PATH" | sed -e 's/:/ /g') -maxdepth 1 -type f -executable -printf "name: %f\n\n"
64
59name: device65name: device
60plugin: resource66plugin: resource
61command: udev_resource67command: udev_resource
6268
=== added directory 'plainbox'
=== added file 'plainbox/MANIFEST.in.OTHER'
--- plainbox/MANIFEST.in.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/MANIFEST.in.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,9 @@
1include README.md
2include COPYING
3include mk-interesting-graphs.sh
4recursive-include plainbox/test-data/ *.json *.xml *.txt
5recursive-include docs *.rst
6include docs/conf.py
7include plainbox/data/report/hardware-1_0.rng
8include contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy
9include contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy
010
=== added directory 'plainbox/contrib'
=== added directory 'plainbox/contrib/policykit_auth_admin_keep'
=== added file 'plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy'
--- plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy 1970-01-01 00:00:00 +0000
+++ plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy 2013-08-28 12:36:13 +0000
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE policyconfig PUBLIC
3 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
4 "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
5<policyconfig>
6
7 <!--
8 Policy definitions for PlainBox system actions.
9 (C) 2013 Canonical Ltd.
10 Author: Sylvain Pineau <sylvain.pineau@canonical.com>
11 -->
12
13 <vendor>PlainBox</vendor>
14 <vendor_url>https://launchpad.net/checkbox</vendor_url>
15 <icon_name>checkbox</icon_name>
16
17 <action id="org.freedesktop.policykit.pkexec.run-plainbox-job">
18 <description>Run Job command</description>
19 <message>Please enter your password. Some tests require root access to run properly. Your password will never be stored and will never be submitted with test results.</message>
20 <defaults>
21 <allow_any>no</allow_any>
22 <allow_inactive>no</allow_inactive>
23 <allow_active>auth_admin_keep</allow_active>
24 </defaults>
25 <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/checkbox-trusted-launcher</annotate>
26 <annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
27 </action>
28
29</policyconfig>
30
031
=== added directory 'plainbox/contrib/policykit_yes'
=== added file 'plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER'
--- plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE policyconfig PUBLIC
3 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
4 "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
5<policyconfig>
6
7 <!--
8 Policy definitions for PlainBox system actions.
9 (C) 2013 Canonical Ltd.
10 Author: Sylvain Pineau <sylvain.pineau@canonical.com>
11 -->
12
13 <vendor>PlainBox</vendor>
14 <vendor_url>https://launchpad.net/checkbox</vendor_url>
15 <icon_name>checkbox</icon_name>
16
17 <action id="org.freedesktop.policykit.pkexec.run-plainbox-job">
18 <description>Run Job command</description>
19 <defaults>
20 <allow_any>no</allow_any>
21 <allow_inactive>no</allow_inactive>
22 <allow_active>yes</allow_active>
23 </defaults>
24 <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/checkbox-trusted-launcher</annotate>
25 <annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
26 </action>
27
28</policyconfig>
29
030
=== added directory 'plainbox/docs'
=== added directory 'plainbox/docs/dev'
=== added file 'plainbox/docs/dev/architecture.rst.OTHER'
--- plainbox/docs/dev/architecture.rst.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/docs/dev/architecture.rst.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,40 @@
1PlainBox Architecture
2=====================
3
4This document explains the architecture of PlainBox internals. It should be
5always up-to-date and accurate to the extent of the scope of this overview.
6
7.. toctree::
8 :maxdepth: 3
9
10 trusted-launcher.rst
11 config.rst
12 resources.rst
13 old.rst
14
15General design considerations
16^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17
18PlainBox is a reimplementation of CheckBox that replaces a reactor / event /
19plugin architecture with a monolithic core and tightly integrated components.
20
21The implementation models a few of the externally-visible concepts such as
22jobs, resources and resource programs but also has some additional design that
23was not present in CheckBox before.
24
25The goal of the rewrite is to provide the right model and APIs for user
26interfaces in order to build the kind of end-user solution that we could not
27build with CheckBox.
28
29This is expressed by additional functionality that is there only to provide the
30higher layers with the right data (failure reason, descriptions, etc.). The
31code is also intended to be highly testable. Test coverage at the time of
32writing this document was exceeding 80%
33
34The core requirement for the current phase of PlainBox development is feature
35parity with CheckBox and gradual shift from one to another in the daily
36responsibilities of the Hardware Certification team. Currently PlainBox
37implements a large chunk of core / essential features from CheckBox. While not
38all features are present the core is considered almost feature complete at this
39stage.
40
041
=== added file 'plainbox/docs/dev/old.rst.OTHER'
--- plainbox/docs/dev/old.rst.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/docs/dev/old.rst.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,343 @@
1Old Architecture Notes
2======================
3
4.. warning::
5
6 This section needs maintenance
7
8Application Skeleton
9^^^^^^^^^^^^^^^^^^^^
10
11This skeleton represents a typical application based on PlainBox. It enumerates
12the essential parts of the APIs from the point of view of an application
13developer.
14
151. Instantiate :class:`plainbox.impl.checkbox.CheckBox` then call
16 :meth:`plainbox.impl.checkbox.CheckBox.get_builtin_jobs()` to discover all
17 known jobs. In the future this might be replaced by a step that obtains jobs
18 from a named provider.
19
203. Instantiate :class:`plainbox.impl.runner.JobRunner` so that we can run jobs
21
224. Instantiate :class:`plainbox.impl.session.SessionState` so that we can keep
23 track of application state.
24
25 - Potentially restore an earlier, interrupted, testing session by calling
26 :meth:`plainbox.impl.session.SessionState.restore()`
27
28 - Potentially remove an earlier, interrupted, testing session by calling
29 :meth:`plainbox.impl.session.SessionState.discard()`
30
31 - Potentially start a new test session by calling
32 :meth:`plainbox.impl.session.SessionState.open()`
33
345. Allow the user to select jobs that should be executed and update session
35 state by calling
36 :meth:`plainbox.impl.session.SessionState.update_desired_job_list()`
37
386. For each job in :attr:`plainbox.impl.SessionState.run_list`:
39
40 1. Check if we want to run the job (if we have a result for it from previous
41 runs) or if we must run it (for jobs that cannot be persisted across
42 suspend)
43
44 2. Check if the job can be started by looking at
45 :meth:`plainbox.impl.session.JobState.can_start()`
46
47 - optionally query for additional data on why a job cannot be started and
48 present that to the user.
49
50 - optionally abort the sequence and go to step 5 or the outer loop.
51
52 3. Call :meth:`plainbox.impl.runner.JobRunner.run_job()` with the current
53 job and store the result.
54
55 - optionally ask the user to perform some manipulation
56
57 - optionally ask the user to qualify the outcome
58
59 - optionally ask the user for additional comments
60
61 4. Call :meth:`plainbox.impl.session.SessionState.update_job_result()` to
62 update readiness of jobs that depend on the outcome or output of current
63 job.
64
65 5. Call :meth:`plainbox.impl.session.SessionState.checkpoint()` to ensure
66 that testing can resume after system crash or shutdown.
67
687. Instantiate the selected state exporter, for example
69 :class:`plainbox.impl.exporters.json.JSONSessionStateExporter` so that we
70 can use it to save test results.
71
72 - optionally pass configuration options to customize the subset and the
73 presentation of the session state
74
758. Call
76 :meth:`plainbox.impl.exporters.SessionStateExporterBase.get_session_data_subset()`
77 followed by :meth:`plainbox.impl.exporters.SessionStateExporterBase.dump()`
78 to save results to a file.
79
809. Call :meth:`plainbox.impl.session.SessionState.close()` to remove any
81 nonvolatile temporary storage that was needed for the session.
82
83Essential classes
84=================
85
86:class:`~plainbox.impl.session.SessionState`
87^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
88
89Class representing all state needed during a single program session.
90
91Usage
92-----
93
94The general idea is that you feed the session with a list of known jobs and
95a subset of jobs that you want to run and in return get an ordered list of
96jobs to run.
97
98It is expected that the user will select / deselect and run jobs. This
99class can react to both actions by recomputing the dependency graph and
100updating the read states accordingly.
101
102As the user runs subsequent jobs the results of those jobs are exposed to
103the session with :meth:`update_job_result()`. This can cause subsequent
104jobs to become available (not inhibited by anything). Note that there is no
105notification of changes at this time.
106
107The session does almost nothing by itself, it learns about everything by
108observing job results coming from the job runner
109(:class:`plainbox.impl.runner.JobRunner`) that applications need to
110instantiate.
111
112Suspend and resume
113------------------
114
115The session can save check-point data after each job is executed. This
116allows the system to survive and continue after a catastrophic failure
117(broken suspend, power failure) or continue across tests that require the
118machine to reboot.
119
120.. todo::
121
122 Create a section on suspend/resume design
123
124Implementation notes
125--------------------
126
127Internally it ties into :class:`plainbox.impl.depmgr.DependencySolver` for
128resolving dependencies. The way the session objects are used allows them to
129return various problems back to the UI level - those are all the error
130classes from :mod:`plainbox.impl.depmgr`:
131
132 - :class:`plainbox.impl.depmgr.DependencyCycleError`
133
134 - :class:`plainbox.impl.depmgr.DependencyDuplicateError`
135
136 - :class:`plainbox.impl.depmgr.DependencyMissingError`
137
138Normally *none* of those errors should ever happen, they are only provided
139so that we don't choke when a problem really happens. Everything is checked
140and verified early before starting a job so typical unit and integration
141testing should capture broken job definitions (for example, with cyclic
142dependencies) being added to the repository.
143
144Implementation issues
145---------------------
146
147There are two issues that are known at this time:
148
149* There is too much checkbox-specific knowledge which really belongs
150 elsewhere. We are working to remove that so that non-checkbox jobs
151 can be introduced later. There is a branch in progress that entirely
152 removes that and moves it to a new concept called SessionController.
153 In that design the session delegates understanding of results to a
154 per-job session controller and exposes some APIs to alter the state
155 that was previously internal (most notably a way to add new jobs and
156 resources).
157
158* The way jobs are currently selected is unfortunate because of local jobs
159 that can add new jobs to the system. This causes considerable complexity
160 at the application level where the application must check if each
161 executed job is a 'local' job and re-compute the desired_job_list. This
162 should be replaced by a matcher function that can be passed to
163 SessionState once so that desired_job_list is re-evaluated internally
164 whenever job_list changes.
165
166
167:class:`~plainbox.impl.job.JobDefinition`
168^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
169
170:term:`CheckBox` has a concept of a :term:`job`. Jobs are named units of
171testing work that can be executed. Typical jobs range from automated CPU power
172management checks, BIOS tests, semi-automated peripherals testing to all manual
173validation by following a script (intended for humans).
174
175Jobs are distributed in plain text files, formated as a loose RFC822 documents
176where typically a single text file contains a few dozen different jobs that
177belong to one topic, for example, all bluetooth tests.
178
179Tests have a number of properties that will not be discussed in detail here,
180they are all documented in :class:`plainbox.impl.job.JobDefinition`. From the
181architecture point of view the four essential properties of a job are *name*,
182*plugin* and *requires* and *depends*. Those are discussed in detail below.
183
184JobDefinition.name
185------------------
186
187The *name* field must be unique and is referred to by other parts of the system
188(such as whitelists). Typically jobs follow a simple naming pattern
189'category/detail', eg, 'networking/modem_connection'. The name must be _unique_
190and this is enforced by the core.
191
192JobDefinition.plugin
193--------------------
194
195The *plugin* field is an archaism from CheckBox and a misnomer (as PlainBox
196does not have any plugins). In the CheckBox architecture it would instruct the
197core which plugin should process that job. In PlainBox it is a way to encode
198what type of a job is being processed. There is a finite set of types that are
199documented below.
200
201plugin == "shell"
202#################
203
204This value is used for fully automated jobs. Everything the job needs to do is
205automated (preparation, execution, verification) and fully handled by the
206command that is associated with a job.
207
208plugin == "manual"
209##################
210
211This value is used for fully manual jobs. It has no special handling in the core
212apart from requiring a human-provided outcome (pass/fail classification)
213
214.. _local:
215
216plugin == "local"
217#################
218
219This value is used for special job generator jobs. The output of such jobs is
220interpreted as additional jobs and is identical in effect to loading such jobs
221from a job definition file.
222
223There are two practical uses for such jobs:
224
225* Some local jobs are used to generate a number of jobs for each object.
226 This is needed where the tested machine may have a number of such objects
227 and each requires unique testing. A good example is a computer where all
228 network tests are explicitly "instantiated" for each network card
229 present.
230
231 This is a valid use case but is rather unfortunate for architecture of
232 PlainBox and there is a desire to replace it with equally-expressive
233 pattern jobs. The advantage is that unlike local jobs (which cannot be
234 "discovered" without enduring any potential side effects that may be
235 caused by the job script command) pattern jobs would allow the core to
236 determine the names of jobs that can be generated and, for example,
237 automatically determine that a pattern job needs to be executed as a
238 dependency of a phantom (yet undetermined) job with a given name.
239
240 The solution with "pattern" jobs may be executed in future phases of
241 PlainBox development. Currently there is no support for that at all.
242
243 Currently PlainBox cannot determine job dependencies across local jobs.
244 That is, unless a local job is explicitly requested (in the desired job
245 list) PlainBox will not be able to run a job that is generated by a local
246 job at all and will treat it as if that job never existed.
247
248* Some local jobs are used to create a form of informal "category".
249 Typically all such jobs have a leading and trailing double underscore,
250 for example '__audio__'. This is currently being used by CheckBox for
251 building a hierarchical tree of tests that the user may select.
252
253 Since this has the same flaws as described above (for pattern jobs) it
254 will likely be replaced by an explicit category field that can be
255 specified each job.
256
257plugin == "resource"
258####################
259
260This value is used for special "data" or "environment" jobs. Their output is
261parsed as a list of RFC822 records and is kept by the core during a testing session.
262
263They are primarily used to determine if a given job can be started. For
264example, a particular bluetooth test may use the _requires_ field to indicate
265that it depends (via a resource dependency) on a job that enumerates devices
266and that one of those devices must be a bluetooth device.
267
268plugin == "user-interact"
269#########################
270
271For all intents and purposes it is equivalent to "manual". The actual
272difference is that a user is expected to perform some physical manipulation
273before an automated test.
274
275plugin == "user-verify"
276#######################
277
278For all intents and purposes it is equivalent to "manual". The actual
279difference is that a user is expected to perform manual verification after an
280automated test.
281
282JobDefinition.depends
283---------------------
284
285The *depends* field is used to express dependencies between two jobs. If job A
286has depends on job B then A cannot start if B is not both finished and
287successful. PlainBox understands this dependency and can automatically sort and
288execute jobs in proper order. In many places of the code this is referred to as
289a "direct dependency" (in contrast to "resource dependency")
290
291The actual syntax is not strictly specified, PlainBox interprets this field as
292a list of tokens delimited by comma or any whitespace (including newlines).
293
294A job may depend on any number of other jobs. There are a number of failure
295modes associated with this feature, all of which are detected and handled by
296PlainBox. Typically they only arise when during CheckBox job development
297(editing actual job files) and are always a sign of a human error. No released
298version of CheckBox or PlainBox should ever encounter any of those issues.
299
300The actual problems are:
301
302* dependency cycles, where job either directly or indirectly depends on
303 itself
304
305* missing dependencies where some job refers to a job that is not defined
306 anywhere.
307
308* duplicate jobs where two jobs with the same name (but different
309 definition) are being introduced to the system.
310
311In all of those cases the core removes the offending job and tries to work
312regardless of the problem. This is intended more as a development aid rather
313than a reliability feature as no released versions of either project should
314cause this problem.
315
316JobDefinition.command
317---------------------
318
319The *command* field is used when the job needs to call an external command.
320Typically all shell jobs define a command to run.
321
322"Manual" jobs can also define a command to run as part of the test procedure.
323
324JobDefinition.user
325------------------
326
327The *user* field is used when the job requires to run as a specific user
328(e.g. root).
329
330The job command will be run via pkexec to get the necessary
331permissions.
332
333.. _environ:
334
335JobDefinition.environ
336---------------------
337
338The *environ* field is used to pass additional environmental keys from the user
339session to the new environment set up when the job command is run by another
340user (root, most of the time).
341
342The actual syntax is not strictly specified, PlainBox interprets this field as
343a list of tokens delimited by comma or any whitespace (including newlines).
0344
=== added file 'plainbox/docs/dev/reference.rst.OTHER'
--- plainbox/docs/dev/reference.rst.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/docs/dev/reference.rst.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,179 @@
1.. _code_reference:
2
3.. toctree::
4 :maxdepth: 2
5
6Code reference
7==============
8
9.. note::
10
11 Unless stated otherwise all API is unstable. PlainBox does not offer
12 general API stability at this time.
13
14.. automodule:: plainbox
15 :members:
16 :undoc-members:
17 :show-inheritance:
18
19.. automodule:: plainbox.public
20 :members:
21 :undoc-members:
22 :show-inheritance:
23
24.. automodule:: plainbox.abc
25 :members:
26 :undoc-members:
27 :show-inheritance:
28
29.. automodule:: plainbox.tests
30 :members:
31 :undoc-members:
32 :show-inheritance:
33
34.. automodule:: plainbox.testing_utils
35 :members:
36 :undoc-members:
37 :show-inheritance:
38
39.. automodule:: plainbox.testing_utils.cwd
40 :members:
41 :undoc-members:
42 :show-inheritance:
43
44.. automodule:: plainbox.testing_utils.io
45 :members:
46 :undoc-members:
47 :show-inheritance:
48
49.. automodule:: plainbox.testing_utils.testcases
50 :members:
51 :undoc-members:
52 :show-inheritance:
53
54.. automodule:: plainbox.vendor
55 :members:
56
57.. automodule:: plainbox.vendor.extcmd
58 :members:
59 :undoc-members:
60 :show-inheritance:
61
62.. automodule:: plainbox.impl
63 :members:
64 :undoc-members:
65 :show-inheritance:
66
67.. automodule:: plainbox.impl.commands
68 :members:
69 :undoc-members:
70 :show-inheritance:
71
72.. automodule:: plainbox.impl.commands.selftest
73 :members:
74 :undoc-members:
75 :show-inheritance:
76
77.. automodule:: plainbox.impl.exporter
78 :members:
79 :undoc-members:
80 :show-inheritance:
81
82.. automodule:: plainbox.impl.exporter.json
83 :members:
84 :undoc-members:
85 :show-inheritance:
86
87.. automodule:: plainbox.impl.exporter.rfc822
88 :members:
89 :undoc-members:
90 :show-inheritance:
91
92.. automodule:: plainbox.impl.exporter.text
93 :members:
94 :undoc-members:
95 :show-inheritance:
96
97.. automodule:: plainbox.impl.secure
98 :members:
99 :undoc-members:
100 :show-inheritance:
101
102.. automodule:: plainbox.impl.secure.checkbox_trusted_launcher
103 :members:
104 :undoc-members:
105 :show-inheritance:
106
107.. automodule:: plainbox.impl.transport
108 :members:
109 :undoc-members:
110 :show-inheritance:
111
112.. automodule:: plainbox.impl.transport.certification
113 :members:
114 :undoc-members:
115 :show-inheritance:
116
117.. automodule:: plainbox.impl.box
118 :members:
119 :undoc-members:
120 :show-inheritance:
121
122.. automodule:: plainbox.impl.checkbox
123 :members:
124 :undoc-members:
125 :show-inheritance:
126
127.. automodule:: plainbox.impl.config
128 :members:
129 :undoc-members:
130 :show-inheritance:
131
132.. automodule:: plainbox.impl.depmgr
133 :members:
134 :undoc-members:
135 :show-inheritance:
136
137.. automodule:: plainbox.impl.integration_tests
138 :members:
139 :undoc-members:
140 :show-inheritance:
141
142.. automodule:: plainbox.impl.job
143 :members:
144 :undoc-members:
145 :show-inheritance:
146
147.. automodule:: plainbox.impl.mock_job
148 :members:
149 :undoc-members:
150 :show-inheritance:
151
152.. automodule:: plainbox.impl.resource
153 :members:
154 :undoc-members:
155 :show-inheritance:
156
157.. automodule:: plainbox.impl.result
158 :members:
159 :undoc-members:
160 :show-inheritance:
161
162.. automodule:: plainbox.impl.rfc822
163 :members:
164 :show-inheritance:
165
166.. automodule:: plainbox.impl.runner
167 :members:
168 :undoc-members:
169 :show-inheritance:
170
171.. automodule:: plainbox.impl.session
172 :members:
173 :undoc-members:
174 :show-inheritance:
175
176.. automodule:: plainbox.impl.testing_utils
177 :members:
178 :undoc-members:
179 :show-inheritance:
0180
=== added file 'plainbox/docs/dev/resources.rst.OTHER'
--- plainbox/docs/dev/resources.rst.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/docs/dev/resources.rst.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,259 @@
1Resources
2=========
3
4Resources are a mechanism that allows to constrain certain :term:`job` to
5execute only on devices with appropriate hardware or software dependencies.
6This mechanism allows some types of jobs to publish resource objects to an
7abstract namespace and to a way to evaluate a resource program to determine if
8a job can be started.
9
10Resources in PlainBox
11=====================
12
13The following chapters explain how resources actually work in :term:`PlainBox`.
14Currently there *is* a subtle difference between this and the original
15:term:`CheckBox` implementation.
16
17Resource programs
18-----------------
19
20Resource programs are multi-line statements that can be embedded in job
21definitions. By far, the most common use case is to check if a required package
22is installed, and thus, the job can use it as a part of a test. A check like
23this looks like this::
24
25 package.name == "fwts"
26
27This resource program codifies that the job needs the ``fwts`` package to run.
28There is a companion job with the same name that interrogates the local package
29database and publishes a set of resource objects. Each such object is a
30collection of arbitrary key-value pairs. The ``package`` job simply publishes
31the ``name`` and ``version`` of each installed package but the mechanism is
32generic and applies to all resources.
33
34As stated, resource programs can be multi-line, a real world example of that is
35presented below::
36
37 device.category == 'CDROM'
38 optical_drive.cd == 'writable'
39
40This example is much like the one above, referring to some resources, here
41coming from jobs ``device`` and ``optical_drive``. What is important to point
42out is that, as a rule of a thumb, multi line programs have an implicit ``and``
43operator between each line. This program would only evaluate to True if there
44is a writable CD-ROM available.
45
46Each resource program is composed of resource expressions. Each line maps
47directly onto one expression so the example program above uses two resource
48expressions.
49
50Resource expressions
51--------------------
52
53Resource expressions are evaluated like normal python programs. They use all of
54the same syntax, semantics and behavior. None of the operators are overridden
55to do anything unexpected. The evaluator tries to follow the principle of least
56surprise but this is not always possible.
57
58Resource expressions cannot execute arbitrary python code. In general almost
59everything is disallowed, except as noted below:
60
61* Expressions can use any literals (strings, numbers, True, False, lists and tuples)
62* Expressions can use boolean operators (``and``, ``or``, ``not``)
63* Expressions can use all comparison operators
64* Expressions can use all binary and unary operators
65* Expressions can use the set membership operator (``in``)
66* Expressions can use read-only attribute access
67
68Anything else is rejected as an invalid resource expression.
69
70In addition to that, each resource expression must use exactly one variable,
71which must be used like an object with attributes. The name of that variable
72must correspond to the name of the job that generates resources. Attempts to
73use more than one variable or to not use any variables are detected early and
74rejected as invalid resource expressions.
75
76The name of the variable determines which resource group to use. It must match
77the name of the job that generates such resources.
78
79In the examples elsewhere in this page the ``package`` resources are generated
80by the ``package`` job. PlainBox uses this to know which resources to try but
81also to implicitly to express dependencies so that the ``package`` job does not
82have to be explicitly selected and marked for execution prior to the job that
83in fact depends on it. This is all done automatically.
84
85Evaluation
86----------
87
88Due to mandatory compatibility with existing :term:`CheckBox` jobs there are
89some unexpected aspects of how evaluation is performed. Those are marked as
90**unexpected** below:
91
921. First PlainBox looks at the resource program and splits it into lines. Each
93 non-empty line is parsed and converted to a resource expression.
94
952. **unexpected** Each resource expression is repeatedly evaluated, once for
96 each resource from the group determined by the variable name. All exceptions
97 are silently ignored and treated as if the iteration had evaluated to False.
98 The whole resource expression evaluates to ``True`` if any of the iterations
99 evaluated to ``True``. In other words, there is an implicit ``any()`` around
100 each resource expression, iterating over all resources.
101
1023. **unexpected** The resource program evaluates to ``True`` only if all
103 resource expressions evaluated to ``True``. In other words, there is an
104 implicit ``and`` between each line.
105
106Limitations
107-----------
108
109The design of resource programs has the following shortcomings. The list is
110non-exhaustive, it only contains issues that we came across found not to work
111in practice.
112
113Exactly one variable per expression
114^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
115
116Each resource expression must refer to exactly one variable. This is a side
117effect of the way the evaluator works. It basically bind one object (a
118particular resource) to that variable and evaluates the expression.
119
120The expression parser / syntax analyzer identifies expressions with this
121problem early and rejects them with an appropriate error message. Here are
122some examples of hypothetical expressions that exhibit this problem.
123
124"I want to have mplayer and an audio device so that I can play some sounds"::
125
126 device.category == "AUDIO" and package.name == "mplayer"
127
128To work around this, split the expression to two separate expressions. The
129evaluator will put an implicit ``and`` between them and it will do exactly what
130you intended::
131
132 device.category == "AUDIO"
133 package.name == "mplayer"
134
135"I want to always run this test"::
136
137 True
138
139To work around this, simply remove the requirement program entirely!
140
141"I want to never run this test"::
142
143 False
144
145To work around this remove this job from the selection. You may also use a
146special resource that produces one constant value, and check that it is equal
147to something different.
148
149Exactly one resource bound to a variable at once
150^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
151
152It's not possible to refer to two different resources, from the same resource
153group, in one resource expression. In other terms, the variable always points
154to one object, it is not a collection of objects.
155
156For example, let's consider this program::
157
158 package.name == 'xorg' and package.name == 'procps'
159
160Seemingly the intent was to ensure that both ``xorg`` and ``procps`` are
161installed. The reason why this does not work is that at each iteration of the
162the expression evaluator, the name ``package`` refers to exactly one resource
163object. In other words, that expression is equivalent to this one::
164
165 A == True and A == False
166
167This type of error is not captured by our limited semantic analyzer. It will
168silently evaluate to False and inhibit the job from being stated.
169
170To work around this, split the expression to two consecutive lines. As stated
171in rule 3 in the list above, there is an implicit ``and`` operator between all
172expressions. A working example that expresses the same intent looks like this::
173
174 package.name == 'xorg'
175 package.name == 'procps'
176
177Operator != is useless
178^^^^^^^^^^^^^^^^^^^^^^
179
180This is strange at first but quickly becomes obvious once you recall rule 2
181from the list above. That rule states that the expression is evaluated
182repeatedly for each resource from a particular group and that any ``True``
183iteration marks the whole expression as ``True``).
184
185Let's look at a real-world example::
186
187 xinput.device_class == 'XITouchClass' and xinput.touch_mode != 'dependent'
188
189So seemingly, the intent here was to have at least ``xinput`` resource with a
190``device_class`` attribute equal to ``XITouchClass`` that has ``touch_mode``
191attribute equal to anything but ``dependent``.
192
193Now let's assume that we have exactly two resources in the ``xinput`` group::
194
195 device_class: XITouchClass
196 touch_mode: dependant
197
198 device_class: XITouchClass
199 touch_mode: something else
200
201Now, this expression will evaluate to ``True``, as the second resource fulfils
202the requirements. Is this what the test designer had expected? That's hard to
203say. The problem here is that this expression can be understood as *at least
204one resource isn't something* **or** *all resources weren't something*. Both
205are equally valid desires and, depending on how the test is implemented, may or
206many not work correctly in practice.
207
208Currently there is no workaround. We are considering adding a new syntax that
209would allow to specify this explicitly. The proposal is documented below as
210"implicit any(), explicit all()"
211
212Everything is a string
213^^^^^^^^^^^^^^^^^^^^^^
214
215Resource programs are regular python programs evaluated in unusual ways but
216all of the variables that are exposed through the resource object are strings.
217
218This has considerable impact on comparison, unless you are comparing to a
219string the comparison will always silently fail as python has dynamic but
220strict, not loose types (there is no implicit type conversion). To alleviate
221this problem several type names / conversion functions are allowed in
222requirement programs. Those are:
223
224* :py:class:`int`, to convert to integer numbers
225* :py:class:`float`, to convert to floating point numbers
226* :py:class:`bool`, to convert to a boolean context
227
228Considered enhancements
229-----------------------
230
231We are currently considering one improvement to resource programs. This would
232allow us to introduce a fix that resolves some issues in a backwards compatible
233way. Technical aspects are not yet resolved as that extension would not be
234available in :term:`CheckBox` until CheckBox can be built on top of
235:term:`PlainBox`
236
237Implicit any(), explicit all()
238^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239
240This proposal changes the way resource expressions are evaluated.
241
242The implicit ``any()`` implemented as a loop over all resources from the
243resource group designated by variable name would be configurable.
244
245A developer may choose to wrap the whole expression in the ``all()`` function
246to indicate that the expression inside ``all()`` must evaluate to ``True`` for
247**all** iterations (all resources).
248
249This would allow solving the case where a job can only run, for example, when a
250certain package is **not** installed. This could be expressed as::
251
252 all(package.name != 'ubuntu-desktop')
253
254Resources in CheckBox
255=====================
256
257The following chapters explain how resources originally worked in
258:term:`CheckBox`. Only notable differences from :term:`PlainBox` implementation
259are listed.
0260
=== added file 'plainbox/docs/dev/trusted-launcher.rst'
--- plainbox/docs/dev/trusted-launcher.rst 1970-01-01 00:00:00 +0000
+++ plainbox/docs/dev/trusted-launcher.rst 2013-08-28 12:36:13 +0000
@@ -0,0 +1,209 @@
1Running jobs as root
2====================
3
4:term:`PlainBox` is started without any privilege.
5But several tests need to start commands requiring privileges.
6
7Such tests will call a trusted launcher, a standalone script
8which does not depend on the :term:`PlainBox` core modules.
9`polkit <http://www.freedesktop.org/wiki/Software/polkit>`_
10will control access to system resources.
11The trusted launcher has to be started using
12`pkexec <http://www.freedesktop.org/software/polkit/docs/0.105/pkexec.1.html>`_
13so that the related policy file works as expected.
14
15To avoid a security hole that allows anyone to run anything as root,
16the launcher can only run jobs installed in a system-wide directory.
17This way we are not weaken the trust system as root access is required
18to install both components (the trusted runner and jobs).
19The :term:`PlainBox` process will send an identifier which is matched by a well-known
20list in the trusted launcher. This identifier is the job hash:
21
22.. code-block:: bash
23
24 $ pkexec trusted-launcher JOB-HASH
25
26See :meth:`plainbox.impl.secure.checkbox_trusted_launcher.BaseJob.get_checksum()` for details about job hashes.
27
28Using Polkit
29^^^^^^^^^^^^
30
31Available authentication methods
32--------------------------------
33
34.. note::
35
36 Only applicable to the package version of PlainBox
37
38PlainBox comes with two authentication methods but both aim to retain the
39granted privileges for the life of the :term:`PlainBox` process.
40
41* The first method will ask the password only once and show the following
42 agent on desktop systems (a text-based agent is available for servers):
43
44 .. code-block:: text
45
46 +-----------------------------------------------------------------------------+
47 | [X] Authenticate |
48 +-----------------------------------------------------------------------------+
49 | |
50 | [Icon] Please enter your password. Some tests require root access to run |
51 | properly. Your password will never be stored and will never be |
52 | submitted with test results. |
53 | |
54 | An application is attempting to perform an action that requires |
55 | privileges. |
56 | Authentication as the super user is required to perform this action. |
57 | |
58 | Password: [________________________________________________________] |
59 | |
60 | [V] Details: |
61 | Action: org.freedesktop.policykit.pkexec.run-plainbox-job |
62 | Vendor: PlainBox |
63 | |
64 | [Cancel] [Authenticate] |
65 +-----------------------------------------------------------------------------+
66
67 The following policy file has to be installed in :file:`/usr/share/polkit-1/actions/`
68 on Ubuntu systems.
69 Asking the password just one time and keeps the authentication for forthcoming
70 calls is provided by the **allow_active** element and the **auth_admin_keep** value.
71
72 Check the `polkit actions <http://www.freedesktop.org/software/polkit/docs/0.105/polkit.8.html#polkit-declaring-actions>`_
73 documentation for details about the other parameters.
74
75 .. code-block:: xml
76
77 <?xml version="1.0" encoding="UTF-8"?>
78 <!DOCTYPE policyconfig PUBLIC
79 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
80 "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
81 <policyconfig>
82
83 <vendor>PlainBox</vendor>
84 <vendor_url>https://launchpad.net/checkbox</vendor_url>
85 <icon_name>checkbox</icon_name>
86
87 <action id="org.freedesktop.policykit.pkexec.run-plainbox-job">
88 <description>Run Job command</description>
89 <message>Authentication is required to run a job command.</message>
90 <defaults>
91 <allow_any>no</allow_any>
92 <allow_inactive>no</allow_inactive>
93 <allow_active>auth_admin_keep</allow_active>
94 </defaults>
95 <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/checkbox-trusted-launcher</annotate>
96 <annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
97 </action>
98
99 </policyconfig>
100
101* The second method is only intended to be used in headless mode (like `SRU`).
102 The only difference with the above method is that **allow_active** will be set to **yes**.
103
104.. note::
105
106 The two policy files are available in the PlainBox :file:`contrib/` directory.
107
108Environment settings with pkexec
109--------------------------------
110
111`pkexec <http://www.freedesktop.org/software/polkit/docs/0.105/pkexec.1.html>`_
112allows an authorized user to execute a command as another user.
113But the environment that ``command`` will run it, will be set to a minimal known
114and safe environment in order to avoid injecting code through ``LD_LIBRARY_PATH``
115or similar mechanisms.
116
117However, some jobs commands require specific enviroment variables such as the
118name of an access point for a wireless test. Those kind of variables must be
119available to the trusted launcher.
120To do so, the enviromment mapping is sent to the launcher like key/value pairs
121are sent to the env(1) command:
122
123.. code-block:: bash
124
125 $ pkexec trusted-launcher JOB-HASH [NAME=VALUE [NAME=VALUE ...]]
126
127Each NAME will be set to VALUE in the environment given that they are known
128and defined in the :ref:`JobDefinition.environ <environ>` parameter.
129
130Checkbox trusted launcher
131^^^^^^^^^^^^^^^^^^^^^^^^^
132
133The checkbox trusted launcher is the minimal code needed to be able to run a
134:term:`CheckBox` job command.
135
136It offers base classes for the following core subclasses:
137
138* :class:`plainbox.impl.rfc822.RFC822Record`
139* :class:`plainbox.impl.job.JobDefinition`
140
141The only duplicated code is the RFC822 parser, where all logging features have
142been removed.
143
144The :class:`plainbox.impl.secure.checkbox_trusted_launcher.Runner` class just
145executes the command process with :py:func:`os.execve`.
146
147Internally the checkbox trusted launcher looks for jobs in the system locations defined in
148:attr:`plainbox.impl.secure.checkbox_trusted_launcher.Runner.CHECKBOXES` which defaults to :file:`/usr/share/checkbox*`.
149This way the launcher can match all :term:`CheckBox` variants, like ``checkbox-oem(-.*)?``
150
151Usage
152-----
153
154.. code-block:: text
155
156 checkbox-trusted-launcher [-h] (--hash HASH | --warmup)
157 [--via LOCAL-JOB-HASH]
158 [NAME=VALUE [NAME=VALUE ...]]
159
160 positional arguments:
161 NAME=VALUE Set each NAME to VALUE in the string environment
162
163 optional arguments:
164 -h, --help show this help message and exit
165 --hash HASH job hash to match
166 --warmup Return immediately, only useful when used with
167 pkexec(1)
168 --via LOCAL-JOB-HASH Local job hash to use to match the generated job
169
170.. note::
171
172 Check all job hashes with ``plainbox special -J``
173
174As stated in the polkit chapter, only a trusted subset of the environment mapping
175will be set using :py:func:`os.execve` to run the command.
176Only the variables defined in the job environ property are allowed to avoid
177compromising the root environment.
178Needed modifications like adding ``CHECKBOX_SHARE`` and new paths to scripts are
179managed by the checkbox-trusted-launcher.
180
181Authentication on PlainBox startup
182----------------------------------
183
184To avoid prompting the password at the first test requiring privileges, :term:`PlainBox`
185will call the ``checkbox-trusted-launcher`` with the ``--warmup`` option.
186It's like a NOOP and it will return immediately, but thanks to the installed policy file
187the authentication will be kept.
188
189.. note::
190
191 When running the development version from a branch, the usual polkit
192 authentication agent will pop up to ask the password each and every time.
193 This is the only difference.
194
195Special case of jobs using the CheckBox local plugin
196----------------------------------------------------
197
198For jobs generated from :ref:`local <local>` jobs (e.g. disk/read_performance.*)
199the trusted launcher is started with ``--via`` meaning that we have to first
200eval a local job to find a hash match.
201Once a match is found, the job command is executed using :py:func:`os.execve`.
202
203.. code-block:: bash
204
205 $ pkexec checkbox-trusted-launcher --hash JOB-HASH --via LOCAL-JOB-HASH
206
207.. note::
208
209 it will obviously fail if any local job can ever generate another local job.
0210
=== added file 'plainbox/docs/usage.rst.OTHER'
--- plainbox/docs/usage.rst.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/docs/usage.rst.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,93 @@
1.. _usage:
2
3Usage
4=====
5
6Currently :term:`PlainBox` has no graphical user interface. To use it you need
7to use the command line.
8
9Basically there is just one command that does everything we can do so far, that
10is :command:`plainbox run`. It has a number of options that tell it which
11:term:`job` to run and what to do with results.
12
13PlainBox has built-in help system so running :command:`plainbox run --help`
14will give you instant information about all the various arguments and options
15that are available. This document is not intended to replace that.
16
17Running a specific job
18^^^^^^^^^^^^^^^^^^^^^^
19
20To run a specific :term:`job` pass it to the ``--include-pattern`` or ``-i``
21option.
22
23For example, to run the ``cpu/scaling_test`` job:
24
25.. code-block:: bash
26
27 $ plainbox run -i cpu/scaling_test
28
29.. note::
30
31 The option ``-i`` can be provided any number of times.
32
33Running jobs related to a specific area
34^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35
36PlainBox has no concept of job categories but you can simulate that by
37running all jobs that follow a specific naming pattern. For example, to run
38all of the USB tests you can run the following command:
39
40.. code-block:: bash
41
42 $ plainbox run -i 'usb/.*'
43
44To list all known jobs run:
45
46.. code-block:: bash
47
48 plainbox special --list-jobs
49
50Running a white list
51^^^^^^^^^^^^^^^^^^^^
52
53To run a :term:`white list` pass the ``--whitelist`` or ``-w`` option.
54
55For example, to run the default white list run:
56
57.. code-block:: bash
58
59 $ plainbox run -w /usr/share/checkbox/data/whitelists/default.whitelist
60
61Saving test results as XML
62^^^^^^^^^^^^^^^^^^^^^^^^^^
63
64To generate an XML file that can be sent to the :term:`certification website`
65you need to pass two additional options:
66
671. ``--output-format=xml``
682. ``--output-file=NAME`` where *NAME* is a file name
69
70For example, to get the default certification tests ready to be submitted
71run this command:
72
73.. code-block:: bash
74
75 $ plainbox run --whitelist=/usr/share/checkbox/data/whitelists/default.whitelist --output-format=xml --output-file=submission.xml
76
77Running stable release update tests
78^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
79
80PlainBox has special support for running stable release updates tests in an
81automated manner. This runs all the jobs from the *sru.whitelist* and sends the
82results to the certification website.
83
84To run SRU tests you will need to know the so-called :term:`Secure ID` of the
85device you are testing. Once you know that all you need to do is run:
86
87.. code-block:: bash
88
89 $ plainbox sru $secure_id submission.xml
90
91The second argument, submission.xml, is a name of the fallback file that is
92only created when sending the data to the certification website fails to work
93for any reason.
094
=== added file 'plainbox/mk-venv.sh.OTHER'
--- plainbox/mk-venv.sh.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/mk-venv.sh.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,195 @@
1#!/bin/sh
2# Create a virtualenv for working with plainbox.
3#
4# This ensures that 'plainbox' command exists and is in PATH and that the
5# plainbox module is correctly located can be imported.
6#
7# This is how Zygmunt Krynicki works, feel free to use or adjust to your needs
8
9VENV_PATH=
10install_missing=0
11# Parse arguments:
12while [ -n "$1" ]; do
13 case "$1" in
14 --help|-h)
15 echo "Usage: mk-venv.sh [LOCATION]"
16 echo ""
17 echo "Create a virtualenv for working with plainbox in LOCATION"
18 exit 0
19 ;;
20 --install-missing)
21 install_missing=1
22 shift
23 ;;
24 *)
25 if [ -z "$VENV_PATH" ]; then
26 VENV_PATH="$1"
27 shift
28 else
29 echo "Error: too many arguments: '$1'"
30 exit 1
31 fi
32 ;;
33 esac
34done
35
36# Apply defaults to arguments without values
37if [ -z "$VENV_PATH" ]; then
38 # Use sensible defaults for vagrant
39 if [ "$LOGNAME" = "vagrant" ]; then
40 VENV_PATH=/tmp/venv
41 else
42 VENV_PATH=/ramdisk/venv
43 fi
44fi
45
46# Do a sanity check on lsb_release that is missing on Fedora the last time I
47# had a look at it.
48if [ "x$(which lsb_release)" = "x" ]; then
49 echo "This script requires the 'lsb_release' command"
50 exit 1
51fi
52
53# The code below is a mixture of Debian/Ubuntu packages and pypi packages.
54# It is designed to work on Ubuntu 12.04 or later.
55# There are _some_ differences between how each release is handled.
56#
57# Non Ubuntu systems are not tested as they don't have the required checkbox
58# package. Debian might be supported once we have JobBox and stuff like Fedora
59# would need a whole new approach but patches are welcome [CLA required]
60if [ "$(lsb_release --short --id)" != "Ubuntu" ] && [ $(lsb_release --short --id --upstream) != "Ubuntu" ]; then
61 echo "Only Ubuntu is supported by this script."
62 echo "If you are interested in using it with your distribution"
63 echo "then please join us in #ubuntu-quality on freenode"
64 echo
65 echo "Alternatively you can use vagrant to develop plainbox"
66 echo "on any operating system, even Windows ;-)"
67 echo
68 echo "See: http://www.vagrantup.com/ for details"
69 exit 1
70fi
71# From now on we can assume a Debian-like system
72
73# Do some conditional stuff depending on the particular Ubuntu release
74enable_system_site=0
75install_coverage=0
76install_distribute=0
77install_pip=0
78# We need:
79# python3:
80# because that's what plainbox is written in
81# python3-dev
82# because we may pip-install stuff as well and we want to build native extensions
83# python3-pkg-resources:
84# because it is used by plainbox to locate files and extension points
85# python3-setuptools:
86# because it is used by setup.py
87# python3-lxml:
88# because that's how we validate RealaxNG schemas
89# python3-mock:
90# because that's what we used to construct some of our tests
91# python3-sphinx:
92# because that's how we build our documentation
93# python-virtualenv:
94# because that's how we create the virtualenv to work in
95# checkbox:
96# because plainbox depends on it as a job provider
97required_pkgs_base="python3 python3-dev python3-pkg-resources python3-setuptools python3-lxml python3-mock python3-sphinx python-virtualenv checkbox"
98
99# The defaults, install everything from pip and all the base packages
100enable_system_site=1
101install_distribute=1
102install_pip=1
103install_coverage=1
104install_requests=1
105required_pkgs="$required_pkgs_base"
106
107case "$(lsb_release --short --release)" in
108 12.04|0.2)
109 # Ubuntu 12.04, this is the LTS release that we have to support despite
110 # any difficulties. It has python3.2 and all of our core dependencies
111 # although some packages are old by 13.04 standards, make sure to be
112 # careful with testing against older APIs.
113 ;;
114 12.10)
115 ;;
116 13.04)
117 # On Raring we can use the system package for python3-requests
118 install_distribute=0
119 install_pip=0
120 install_requests=0
121 required_pkgs="$required_pkgs_base python3-requests"
122 ;;
123 *)
124 echo "Using this version of Ubuntu for development is not supported"
125 echo "Unsupported version: $(lsb_release --short --release)"
126 exit 1
127 ;;
128esac
129
130# Check if we can create a virtualenv
131if [ ! -d $(dirname $VENV_PATH) ]; then
132 echo "This script requires $(dirname $VENV_PATH) directory to exist"
133 echo "You can use different directory by passing it as argument"
134 echo "For a quick temporary location just pass /tmp/venv"
135 exit 1
136fi
137
138# Check if there's one already there
139if [ -d $VENV_PATH ]; then
140 echo "$VENV_PATH seems to already exist"
141 exit 1
142fi
143
144# Ensure that each required package is installed
145for pkg_name in $required_pkgs; do
146 # Ensure virtualenv is installed
147 if [ "$(dpkg -s $pkg_name 2>/dev/null | grep '^Status' 2>/dev/null)" != "Status: install ok installed" ]; then
148 if [ "$install_missing" -eq 1 ]; then
149 echo "Installing required package: $pkg_name"
150 sudo apt-get install $pkg_name --yes
151 else
152 echo "Required package is not installed: '$pkg_name'"
153 echo "Either install it manually with:"
154 echo "$ sudo apt-get install $pkg_name"
155 echo "Or rerun this script with --install-missing"
156 exit 1
157 fi
158 fi
159done
160
161# Create a virtualenv
162if [ $enable_system_site -eq 1 ]; then
163 virtualenv --system-site-packages -p python3 $VENV_PATH
164else
165 virtualenv -p python3 $VENV_PATH
166fi
167
168# Activate it to install additional stuff
169. $VENV_PATH/bin/activate
170
171# Install / upgrade distribute
172if [ $install_distribute -eq 1 ]; then
173 pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/coverage-3.6.tar.gz
174fi
175
176# Install / upgrade pip
177if [ $install_pip -eq 1 ]; then
178 pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/pip-1.3.1.tar.gz
179fi
180
181# Install coverage if required
182if [ $install_coverage -eq 1 ]; then
183 pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/coverage-3.6.tar.gz
184fi
185
186# Install requests if required
187if [ $install_requests -eq 1 ]; then
188 pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/requests-1.1.0.tar.gz
189fi
190
191# "develop" plainbox
192http_proxy=http://127.0.0.1:9/ python3 setup.py develop
193
194echo "To activate your virtualenv run:"
195echo "$ . $VENV_PATH/bin/activate"
0196
=== added directory 'plainbox/plainbox'
=== added directory 'plainbox/plainbox/impl'
=== added file 'plainbox/plainbox/impl/box.py.OTHER'
--- plainbox/plainbox/impl/box.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/box.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,130 @@
1# This file is part of Checkbox.
2#
3# Copyright 2012 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.box` -- command line interface
22==================================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
30from argparse import _ as argparse_gettext
31from logging import basicConfig
32from logging import getLogger
33import argparse
34import sys
35
36from plainbox import __version__ as version
37from plainbox.impl.applogic import PlainBoxConfig
38from plainbox.impl.checkbox import CheckBox
39from plainbox.impl.commands.run import RunCommand
40from plainbox.impl.commands.selftest import SelfTestCommand
41from plainbox.impl.commands.special import SpecialCommand
42from plainbox.impl.commands.sru import SRUCommand
43from plainbox.impl.commands.check_config import CheckConfigCommand
44
45
46logger = getLogger("plainbox.box")
47
48
49class PlainBox:
50 """
51 High-level plainbox object
52 """
53
54 def __init__(self):
55 self._checkbox = CheckBox()
56
57 def main(self, argv=None):
58 # TODO: setup sane logging system that works just as well for Joe user
59 # that runs checkbox from the CD as well as for checkbox developers and
60 # custom debugging needs. It would be perfect^Hdesirable not to create
61 # another broken, never-rotated, uncapped logging crap that kills my
62 # SSD by writing junk to ~/.cache/
63 basicConfig(level="WARNING")
64 config = PlainBoxConfig.get()
65 parser = self._construct_parser(config)
66 ns = parser.parse_args(argv)
67 # Set the desired log level
68 getLogger("").setLevel(ns.log_level)
69 # Argh the horrror!
70 #
71 # Since CPython revision cab204a79e09 (landed for python3.3)
72 # http://hg.python.org/cpython/diff/cab204a79e09/Lib/argparse.py
73 # the argparse module behaves differently than it did in python3.2
74 #
75 # In practical terms subparsers are now optional in 3.3 so all of the
76 # commands are no longer required parameters.
77 #
78 # To compensate, on python3.3 and beyond, when the user just runs
79 # plainbox without specifying the command, we manually, explicitly do
80 # what python3.2 did: call parser.error(_('too few arguments'))
81 if (sys.version_info[:2] >= (3, 3)
82 and getattr(ns, "command", None) is None):
83 parser.error(argparse_gettext("too few arguments"))
84 else:
85 return ns.command.invoked(ns)
86
87 def _construct_parser(self, config):
88 parser = ArgumentParser(
89 prog="plainbox", formatter_class=ArgumentDefaultsHelpFormatter)
90 parser.add_argument(
91 "-v", "--version", action="version",
92 version="{}.{}.{}".format(*version[:3]))
93 parser.add_argument(
94 "-l", "--log-level", action="store",
95 choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
96 default='WARNING',
97 help=argparse.SUPPRESS)
98 subparsers = parser.add_subparsers()
99 RunCommand(self._checkbox).register_parser(subparsers)
100 SpecialCommand(self._checkbox).register_parser(subparsers)
101 SelfTestCommand().register_parser(subparsers)
102 SRUCommand(self._checkbox, config).register_parser(subparsers)
103 CheckConfigCommand(config).register_parser(subparsers)
104 #group = parser.add_argument_group(title="user interface options")
105 #group.add_argument(
106 # "-u", "--ui", action="store",
107 # default=None, choices=('headless', 'text', 'graphics'),
108 # help="select the UI front-end (defaults to auto)")
109 return parser
110
111
112def main(argv=None):
113 # Instantiate a global plainbox instance
114 # XXX: Allow one to control the checkbox= argument via
115 # environment or config.
116 box = PlainBox()
117 retval = box.main(argv)
118 raise SystemExit(retval)
119
120
121def get_builtin_jobs():
122 raise NotImplementedError("get_builtin_jobs() not implemented")
123
124
125def save(something, somewhere):
126 raise NotImplementedError("save() not implemented")
127
128
129def run(*args, **kwargs):
130 raise NotImplementedError("run() not implemented")
0131
=== added directory 'plainbox/plainbox/impl/commands'
=== added file 'plainbox/plainbox/impl/commands/checkbox.py.OTHER'
--- plainbox/plainbox/impl/commands/checkbox.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/commands/checkbox.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,100 @@
1# This file is part of Checkbox.
2#
3# Copyright 2012-2013 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.commands.checkbox` -- mix-in for checkbox commands
22======================================================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29import re
30from argparse import FileType
31
32
33class CheckBoxInvocationMixIn:
34
35 def __init__(self, checkbox):
36 self.checkbox = checkbox
37
38 def get_job_list(self, ns):
39 """
40 Load and return a list of JobDefinition instances
41 """
42 return self.checkbox.get_builtin_jobs()
43
44 def _get_matching_job_list(self, ns, job_list):
45 # Find jobs that matched patterns
46 matching_job_list = []
47 # Pre-seed the include pattern list with data read from
48 # the whitelist file.
49 if ns.whitelist:
50 ns.include_pattern_list.extend([
51 pattern.strip()
52 for pattern in ns.whitelist.readlines()])
53 # Decide which of the known jobs to include
54 for job in job_list:
55 # Reject all jobs that match any of the exclude
56 # patterns, matching strictly from the start to
57 # the end of the line.
58 for pattern in ns.exclude_pattern_list:
59 regexp_pattern = r"^{pattern}$".format(pattern=pattern)
60 if re.match(regexp_pattern, job.name):
61 break
62 else:
63 # Then accept (include) all job that matches
64 # any of include patterns, matching strictly
65 # from the start to the end of the line.
66 for pattern in ns.include_pattern_list:
67 regexp_pattern = r"^{pattern}$".format(pattern=pattern)
68 if re.match(regexp_pattern, job.name):
69 matching_job_list.append(job)
70 break
71 return matching_job_list
72
73
74class CheckBoxCommandMixIn:
75 """
76 Mix-in class for plainbox commands that want to discover and load checkbox
77 jobs
78 """
79
80 def enhance_parser(self, parser):
81 """
82 Add common options for job selection to an existing parser
83 """
84 group = parser.add_argument_group(title="job definition options")
85 group.add_argument(
86 '-i', '--include-pattern', action="append",
87 metavar='PATTERN', default=[], dest='include_pattern_list',
88 help=("Run jobs matching the given regular expression. Matches "
89 "from the start to the end of the line."))
90 group.add_argument(
91 '-x', '--exclude-pattern', action="append",
92 metavar="PATTERN", default=[], dest='exclude_pattern_list',
93 help=("Do not run jobs matching the given regular expression. "
94 "Matches from the start to the end of the line."))
95 # TODO: Find a way to handle the encoding of the file
96 group.add_argument(
97 '-w', '--whitelist',
98 metavar="WHITELIST",
99 type=FileType("rt"),
100 help="Load whitelist containing run patterns")
0101
=== added file 'plainbox/plainbox/impl/commands/run.py.OTHER'
--- plainbox/plainbox/impl/commands/run.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/commands/run.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,340 @@
1# This file is part of Checkbox.
2#
3# Copyright 2012-2013 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.commands.run` -- run sub-command
22====================================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29from argparse import FileType
30from logging import getLogger
31from os.path import join
32from shutil import copyfileobj
33import io
34import sys
35
36from requests.exceptions import ConnectionError, InvalidSchema, HTTPError
37
38from plainbox.impl.commands import PlainBoxCommand
39from plainbox.impl.commands.checkbox import CheckBoxCommandMixIn
40from plainbox.impl.commands.checkbox import CheckBoxInvocationMixIn
41from plainbox.impl.depmgr import DependencyDuplicateError
42from plainbox.impl.exporter import ByteStringStreamTranslator
43from plainbox.impl.exporter import get_all_exporters
44from plainbox.impl.result import JobResult
45from plainbox.impl.runner import JobRunner
46from plainbox.impl.runner import authenticate_warmup
47from plainbox.impl.runner import slugify
48from plainbox.impl.session import SessionState
49from plainbox.impl.transport import get_all_transports
50
51
52logger = getLogger("plainbox.commands.run")
53
54
55class RunInvocation(CheckBoxInvocationMixIn):
56
57 def __init__(self, checkbox, ns):
58 super(RunInvocation, self).__init__(checkbox)
59 self.ns = ns
60
61 def run(self):
62 ns = self.ns
63 if ns.output_format == '?':
64 self._print_output_format_list(ns)
65 return 0
66 elif ns.output_options == '?':
67 self._print_output_option_list(ns)
68 return 0
69 elif ns.transport == '?':
70 self._print_transport_list(ns)
71 return 0
72 else:
73 exporter = self._prepare_exporter(ns)
74 transport = self._prepare_transport(ns)
75 job_list = self.get_job_list(ns)
76 return self._run_jobs(ns, job_list, exporter, transport)
77
78 def _print_output_format_list(self, ns):
79 print("Available output formats: {}".format(
80 ', '.join(get_all_exporters())))
81
82 def _print_output_option_list(self, ns):
83 print("Each format may support a different set of options")
84 for name, exporter_cls in get_all_exporters().items():
85 print("{}: {}".format(
86 name, ", ".join(exporter_cls.supported_option_list)))
87
88 def _print_transport_list(self, ns):
89 print("Available transports: {}".format(
90 ', '.join(get_all_transports())))
91
92 def _prepare_exporter(self, ns):
93 exporter_cls = get_all_exporters()[ns.output_format]
94 if ns.output_options:
95 option_list = ns.output_options.split(',')
96 else:
97 option_list = None
98 try:
99 exporter = exporter_cls(option_list)
100 except ValueError as exc:
101 raise SystemExit(str(exc))
102 return exporter
103
104 def _prepare_transport(self, ns):
105 if ns.transport not in get_all_transports():
106 return None
107 transport_cls = get_all_transports()[ns.transport]
108 try:
109 return transport_cls(ns.transport_where, ns.transport_options)
110 except ValueError as exc:
111 raise SystemExit(str(exc))
112
113 def ask_for_resume(self, prompt=None, allowed=None):
114 # FIXME: Add support/callbacks for a GUI
115 if prompt is None:
116 prompt = "Do you want to resume the previous session [Y/n]? "
117 if allowed is None:
118 allowed = ('', 'y', 'Y', 'n', 'N')
119 answer = None
120 while answer not in allowed:
121 answer = input(prompt)
122 return False if answer in ('n', 'N') else True
123
124 def _run_jobs(self, ns, job_list, exporter, transport=None):
125 # Ask the password before anything else in order to run jobs requiring
126 # privileges
127 print("[ Authentication ]".center(80, '='))
128 return_code = authenticate_warmup()
129 if return_code:
130 raise SystemExit(return_code)
131 # Compute the run list, this can give us notification about problems in
132 # the selected jobs. Currently we just display each problem
133 matching_job_list = self._get_matching_job_list(ns, job_list)
134 print("[ Analyzing Jobs ]".center(80, '='))
135 # Create a session that handles most of the stuff needed to run jobs
136 try:
137 session = SessionState(job_list)
138 except DependencyDuplicateError as exc:
139 # Handle possible DependencyDuplicateError that can happen if
140 # someone is using plainbox for job development.
141 print("The job database you are currently using is broken")
142 print("At least two jobs contend for the name {0}".format(
143 exc.job.name))
144 print("First job defined in: {0}".format(exc.job.origin))
145 print("Second job defined in: {0}".format(
146 exc.duplicate_job.origin))
147 raise SystemExit(exc)
148 with session.open():
149 if session.previous_session_file():
150 if self.ask_for_resume():
151 session.resume()
152 else:
153 session.clean()
154 self._update_desired_job_list(session, matching_job_list)
155 if (sys.stdin.isatty() and sys.stdout.isatty() and not
156 ns.not_interactive):
157 outcome_callback = self.ask_for_outcome
158 else:
159 outcome_callback = None
160 runner = JobRunner(
161 session.session_dir,
162 session.jobs_io_log_dir,
163 outcome_callback=outcome_callback,
164 dry_run=ns.dry_run
165 )
166 self._run_jobs_with_session(ns, session, runner)
167 # Get a stream with exported session data.
168 exported_stream = io.BytesIO()
169 data_subset = exporter.get_session_data_subset(session)
170 exporter.dump(data_subset, exported_stream)
171 exported_stream.seek(0) # Need to rewind the file, puagh
172 # Write the stream to file if requested
173 self._save_results(ns.output_file, exported_stream)
174 # Invoke the transport?
175 if transport:
176 exported_stream.seek(0)
177 try:
178 transport.send(exported_stream.read())
179 except InvalidSchema as exc:
180 print("Invalid destination URL: {0}".format(exc))
181 except ConnectionError as exc:
182 print(("Unable to connect "
183 "to destination URL: {0}").format(exc))
184 except HTTPError as exc:
185 print(("Server returned an error when "
186 "receiving or processing: {0}").format(exc))
187
188 # FIXME: sensible return value
189 return 0
190
191 def _save_results(self, output_file, input_stream):
192 if output_file is sys.stdout:
193 print("[ Results ]".center(80, '='))
194 # This requires a bit more finesse, as exporters output bytes
195 # and stdout needs a string.
196 translating_stream = ByteStringStreamTranslator(
197 output_file, "utf-8")
198 copyfileobj(input_stream, translating_stream)
199 else:
200 print("Saving results to {}".format(output_file.name))
201 copyfileobj(input_stream, output_file)
202 if output_file is not sys.stdout:
203 output_file.close()
204
205 def ask_for_outcome(self, prompt=None, allowed=None):
206 if prompt is None:
207 prompt = "what is the outcome? "
208 if allowed is None:
209 allowed = (JobResult.OUTCOME_PASS,
210 JobResult.OUTCOME_FAIL,
211 JobResult.OUTCOME_SKIP)
212 answer = None
213 while answer not in allowed:
214 print("Allowed answers are: {}".format(", ".join(allowed)))
215 answer = input(prompt)
216 return answer
217
218 def _update_desired_job_list(self, session, desired_job_list):
219 problem_list = session.update_desired_job_list(desired_job_list)
220 if problem_list:
221 print("[ Warning ]".center(80, '*'))
222 print("There were some problems with the selected jobs")
223 for problem in problem_list:
224 print(" * {}".format(problem))
225 print("Problematic jobs will not be considered")
226
227 def _run_jobs_with_session(self, ns, session, runner):
228 # TODO: run all resource jobs concurrently with multiprocessing
229 # TODO: make local job discovery nicer, it would be best if
230 # desired_jobs could be managed entirely internally by SesionState. In
231 # such case the list of jobs to run would be changed during iteration
232 # but would be otherwise okay).
233 print("[ Running All Jobs ]".center(80, '='))
234 again = True
235 while again:
236 again = False
237 for job in session.run_list:
238 # Skip jobs that already have result, this is only needed when
239 # we run over the list of jobs again, after discovering new
240 # jobs via the local job output
241 if session.job_state_map[job.name].result.outcome is not None:
242 continue
243 self._run_single_job_with_session(ns, session, runner, job)
244 session.persistent_save()
245 if job.plugin == "local":
246 # After each local job runs rebuild the list of matching
247 # jobs and run everything again
248 new_matching_job_list = self._get_matching_job_list(
249 ns, session.job_list)
250 self._update_desired_job_list(
251 session, new_matching_job_list)
252 again = True
253 break
254
255 def _run_single_job_with_session(self, ns, session, runner, job):
256 print("[ {} ]".format(job.name).center(80, '-'))
257 if job.description is not None:
258 print(job.description)
259 print("^" * len(job.description.splitlines()[-1]))
260 print()
261 job_state = session.job_state_map[job.name]
262 logger.debug("Job name: %s", job.name)
263 logger.debug("Plugin: %s", job.plugin)
264 logger.debug("Direct dependencies: %s", job.get_direct_dependencies())
265 logger.debug("Resource dependencies: %s",
266 job.get_resource_dependencies())
267 logger.debug("Resource program: %r", job.requires)
268 logger.debug("Command: %r", job.command)
269 logger.debug("Can start: %s", job_state.can_start())
270 logger.debug("Readiness: %s", job_state.get_readiness_description())
271 if job_state.can_start():
272 print("Running... (output in {}.*)".format(
273 join(session.jobs_io_log_dir, slugify(job.name))))
274 job_result = runner.run_job(job)
275 print("Outcome: {}".format(job_result.outcome))
276 print("Comments: {}".format(job_result.comments))
277 else:
278 job_result = JobResult({
279 'job': job,
280 'outcome': JobResult.OUTCOME_NOT_SUPPORTED,
281 'comments': job_state.get_readiness_description()
282 })
283 if job_result is not None:
284 session.update_job_result(job, job_result)
285
286
287class RunCommand(PlainBoxCommand, CheckBoxCommandMixIn):
288
289 def __init__(self, checkbox):
290 self.checkbox = checkbox
291
292 def invoked(self, ns):
293 return RunInvocation(self.checkbox, ns).run()
294
295 def register_parser(self, subparsers):
296 parser = subparsers.add_parser("run", help="run a test job")
297 parser.set_defaults(command=self)
298 group = parser.add_argument_group(title="user interface options")
299 group.add_argument(
300 '--not-interactive', action='store_true',
301 help="Skip tests that require interactivity")
302 group.add_argument(
303 '-n', '--dry-run', action='store_true',
304 help="Don't actually run any jobs")
305 group = parser.add_argument_group("output options")
306 assert 'text' in get_all_exporters()
307 group.add_argument(
308 '-f', '--output-format', default='text',
309 metavar='FORMAT', choices=['?'] + list(
310 get_all_exporters().keys()),
311 help=('Save test results in the specified FORMAT'
312 ' (pass ? for a list of choices)'))
313 group.add_argument(
314 '-p', '--output-options', default='',
315 metavar='OPTIONS',
316 help=('Comma-separated list of options for the export mechanism'
317 ' (pass ? for a list of choices)'))
318 group.add_argument(
319 '-o', '--output-file', default='-',
320 metavar='FILE', type=FileType("wb"),
321 help=('Save test results to the specified FILE'
322 ' (or to stdout if FILE is -)'))
323 group.add_argument(
324 '-t', '--transport',
325 metavar='TRANSPORT', choices=['?'] + list(
326 get_all_transports().keys()),
327 help=('use TRANSPORT to send results somewhere'
328 ' (pass ? for a list of choices)'))
329 group.add_argument(
330 '--transport-where',
331 metavar='WHERE',
332 help=('Where to send data using the selected transport.'
333 ' This is passed as-is and is transport-dependent.'))
334 group.add_argument(
335 '--transport-options',
336 metavar='OPTIONS',
337 help=('Comma-separated list of key-value options (k=v) to '
338 ' be passed to the transport.'))
339 # Call enhance_parser from CheckBoxCommandMixIn
340 self.enhance_parser(parser)
0341
=== added file 'plainbox/plainbox/impl/commands/special.py.OTHER'
--- plainbox/plainbox/impl/commands/special.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/commands/special.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,159 @@
1# This file is part of Checkbox.
2#
3# Copyright 2012-2013 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.commands.special` -- special sub-command
22============================================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29from logging import getLogger
30
31from plainbox.impl.commands import PlainBoxCommand
32from plainbox.impl.commands.checkbox import CheckBoxCommandMixIn
33from plainbox.impl.commands.checkbox import CheckBoxInvocationMixIn
34
35
36logger = getLogger("plainbox.commands.special")
37
38
39class SpecialInvocation(CheckBoxInvocationMixIn):
40
41 def __init__(self, checkbox, ns):
42 super(SpecialInvocation, self).__init__(checkbox)
43 self.ns = ns
44
45 def run(self):
46 ns = self.ns
47 job_list = self.get_job_list(ns)
48 # Now either do a special action or run the jobs
49 if ns.special == "list-jobs":
50 self._print_job_list(ns, job_list)
51 elif ns.special == "list-job-hashes":
52 self._print_job_hash_list(ns, job_list)
53 elif ns.special == "list-expr":
54 self._print_expression_list(ns, job_list)
55 elif ns.special == "dep-graph":
56 self._print_dot_graph(ns, job_list)
57 # Always succeed
58 return 0
59
60 def _get_matching_job_list(self, ns, job_list):
61 matching_job_list = super(
62 SpecialInvocation, self)._get_matching_job_list(ns, job_list)
63 # As a special exception, when ns.special is set and we're either
64 # listing jobs or job dependencies then when no run pattern was
65 # specified just operate on the whole set. The ns.special check
66 # prevents people starting plainbox from accidentally running _all_
67 # jobs without prompting.
68 if ns.special is not None and not ns.include_pattern_list:
69 matching_job_list = job_list
70 return matching_job_list
71
72 def _print_job_list(self, ns, job_list):
73 matching_job_list = self._get_matching_job_list(ns, job_list)
74 for job in matching_job_list:
75 print("{}".format(job))
76
77 def _print_job_hash_list(self, ns, job_list):
78 matching_job_list = self._get_matching_job_list(ns, job_list)
79 for job in matching_job_list:
80 print("{} {}".format(job.get_checksum(), job))
81
82 def _print_expression_list(self, ns, job_list):
83 matching_job_list = self._get_matching_job_list(ns, job_list)
84 expressions = set()
85 for job in matching_job_list:
86 prog = job.get_resource_program()
87 if prog is not None:
88 for expression in prog.expression_list:
89 expressions.add(expression.text)
90 for expression in sorted(expressions):
91 print(expression)
92
93 def _print_dot_graph(self, ns, job_list):
94 matching_job_list = self._get_matching_job_list(ns, job_list)
95 print('digraph dependency_graph {')
96 print('\tnode [shape=box];')
97 for job in matching_job_list:
98 if job.plugin == "resource":
99 print('\t"{}" [shape=ellipse,color=blue];'.format(job.name))
100 elif job.plugin == "attachment":
101 print('\t"{}" [color=green];'.format(job.name))
102 elif job.plugin == "local":
103 print('\t"{}" [shape=invtriangle,color=red];'.format(
104 job.name))
105 elif job.plugin == "shell":
106 print('\t"{}" [];'.format(job.name))
107 elif job.plugin in ("manual", "user-verify", "user-interact"):
108 print('\t"{}" [color=orange];'.format(job.name))
109 for dep_name in job.get_direct_dependencies():
110 print('\t"{}" -> "{}";'.format(job.name, dep_name))
111 prog = job.get_resource_program()
112 if ns.dot_resources and prog is not None:
113 for expression in prog.expression_list:
114 print('\t"{}" [shape=ellipse,color=blue];'.format(
115 expression.resource_name))
116 print('\t"{}" -> "{}" [style=dashed, label="{}"];'.format(
117 job.name, expression.resource_name,
118 expression.text.replace('"', "'")))
119 print("}")
120
121
122class SpecialCommand(PlainBoxCommand, CheckBoxCommandMixIn):
123 """
124 Implementation of ``$ plainbox special``
125 """
126
127 def __init__(self, checkbox):
128 self.checkbox = checkbox
129
130 def invoked(self, ns):
131 return SpecialInvocation(self.checkbox, ns).run()
132
133 def register_parser(self, subparsers):
134 parser = subparsers.add_parser(
135 "special", help="special/internal commands")
136 parser.set_defaults(command=self)
137 group = parser.add_mutually_exclusive_group(required=True)
138 group.add_argument(
139 '-j', '--list-jobs',
140 help="List jobs instead of running them",
141 action="store_const", const="list-jobs", dest="special")
142 group.add_argument(
143 '-J', '--list-job-hashes',
144 help="List jobs with hashes instead of running them",
145 action="store_const", const="list-job-hashes", dest="special")
146 group.add_argument(
147 '-e', '--list-expressions',
148 help="List all unique resource expressions",
149 action="store_const", const="list-expr", dest="special")
150 group.add_argument(
151 '-d', '--dot',
152 help="Print a graph of jobs instead of running them",
153 action="store_const", const="dep-graph", dest="special")
154 parser.add_argument(
155 '--dot-resources',
156 help="Render resource relationships (for --dot)",
157 action='store_true')
158 # Call enhance_parser from CheckBoxCommandMixIn
159 self.enhance_parser(parser)
0160
=== added file 'plainbox/plainbox/impl/commands/sru.py.OTHER'
--- plainbox/plainbox/impl/commands/sru.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/commands/sru.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,271 @@
1# This file is part of Checkbox.
2#
3#
4# Copyright 2013 Canonical Ltd.
5# Written by:
6# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
7#
8# Checkbox is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# Checkbox is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
20
21"""
22:mod:`plainbox.impl.commands.sru` -- sru sub-command
23====================================================
24
25.. warning::
26
27 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
28"""
29import logging
30import os
31import tempfile
32
33from requests.exceptions import ConnectionError, InvalidSchema, HTTPError
34
35from plainbox.impl.applogic import get_matching_job_list
36from plainbox.impl.applogic import run_job_if_possible
37from plainbox.impl.checkbox import WhiteList
38from plainbox.impl.commands import PlainBoxCommand
39from plainbox.impl.commands.check_config import CheckConfigInvocation
40from plainbox.impl.config import ValidationError, Unset
41from plainbox.impl.depmgr import DependencyDuplicateError
42from plainbox.impl.exporter import ByteStringStreamTranslator
43from plainbox.impl.exporter.xml import XMLSessionStateExporter
44from plainbox.impl.runner import JobRunner
45from plainbox.impl.session import SessionState
46from plainbox.impl.transport.certification import CertificationTransport
47from plainbox.impl.transport.certification import InvalidSecureIDError
48
49
50logger = logging.getLogger("plainbox.commands.sru")
51
52
53class _SRUInvocation:
54 """
55 Helper class instantiated to perform a particular invocation of the sru
56 command. Unlike the SRU command itself, this class is instantiated each
57 time.
58 """
59
60 def __init__(self, checkbox, config, ns):
61 self.checkbox = checkbox
62 self.config = config
63 self.ns = ns
64 self.whitelist = WhiteList.from_file(os.path.join(
65 self.checkbox.whitelists_dir, "sru.whitelist"))
66 self.job_list = self.checkbox.get_builtin_jobs()
67 # XXX: maybe allow specifying system_id from command line?
68 self.exporter = XMLSessionStateExporter(system_id=None)
69 self.session = None
70 self.runner = None
71
72 def run(self):
73 # Compute the run list, this can give us notification about problems in
74 # the selected jobs. Currently we just display each problem
75 # Create a session that handles most of the stuff needed to run jobs
76 try:
77 self.session = SessionState(self.job_list)
78 except DependencyDuplicateError as exc:
79 # Handle possible DependencyDuplicateError that can happen if
80 # someone is using plainbox for job development.
81 print("The job database you are currently using is broken")
82 print("At least two jobs contend for the name {0}".format(
83 exc.job.name))
84 print("First job defined in: {0}".format(exc.job.origin))
85 print("Second job defined in: {0}".format(
86 exc.duplicate_job.origin))
87 raise SystemExit(exc)
88 with self.session.open():
89 self._set_job_selection()
90 self.runner = JobRunner(
91 self.session.session_dir,
92 self.session.jobs_io_log_dir,
93 command_io_delegate=self,
94 outcome_callback=None, # SRU runs are never interactive
95 dry_run=self.ns.dry_run
96 )
97 self._run_all_jobs()
98 if self.config.fallback_file is not Unset:
99 self._save_results()
100 self._submit_results()
101 # FIXME: sensible return value
102 return 0
103
104 def _set_job_selection(self):
105 desired_job_list = get_matching_job_list(self.job_list, self.whitelist)
106 problem_list = self.session.update_desired_job_list(desired_job_list)
107 if problem_list:
108 logger.warning("There were some problems with the selected jobs")
109 for problem in problem_list:
110 logger.warning("- %s", problem)
111 logger.warning("Problematic jobs will not be considered")
112
113 def _save_results(self):
114 print("Saving results to {0}".format(self.config.fallback_file))
115 data = self.exporter.get_session_data_subset(self.session)
116 with open(self.config.fallback_file, "wt", encoding="UTF-8") as stream:
117 translating_stream = ByteStringStreamTranslator(stream, "UTF-8")
118 self.exporter.dump(data, translating_stream)
119
120 def _submit_results(self):
121 print("Submitting results to {0} for secure_id {1}".format(
122 self.config.c3_url, self.config.secure_id))
123 options_string = "secure_id={0}".format(self.config.secure_id)
124 # Create the transport object
125 try:
126 transport = CertificationTransport(
127 self.config.c3_url, options_string, self.config)
128 except InvalidSecureIDError as exc:
129 print(exc)
130 return False
131 # Prepare the data for submission
132 data = self.exporter.get_session_data_subset(self.session)
133 with tempfile.NamedTemporaryFile(mode='w+b') as stream:
134 # Dump the data to the temporary file
135 self.exporter.dump(data, stream)
136 # Flush and rewind
137 stream.flush()
138 stream.seek(0)
139 try:
140 # Send the data, reading from the temporary file
141 result = transport.send(stream)
142 if 'url' in result:
143 print("Successfully sent, submission status at {0}".format(
144 result['url']))
145 else:
146 print("Successfully sent, server response: {0}".format(
147 result))
148
149 except InvalidSchema as exc:
150 print("Invalid destination URL: {0}".format(exc))
151 except ConnectionError as exc:
152 print("Unable to connect to destination URL: {0}".format(exc))
153 except HTTPError as exc:
154 print(("Server returned an error when "
155 "receiving or processing: {0}").format(exc))
156 except IOError as exc:
157 print("Problem reading a file: {0}".format(exc))
158
159 def _run_all_jobs(self):
160 again = True
161 while again:
162 again = False
163 for job in self.session.run_list:
164 # Skip jobs that already have result, this is only needed when
165 # we run over the list of jobs again, after discovering new
166 # jobs via the local job output
167 result = self.session.job_state_map[job.name].result
168 if result.outcome is not None:
169 continue
170 self._run_single_job(job)
171 self.session.persistent_save()
172 if job.plugin == "local":
173 # After each local job runs rebuild the list of matching
174 # jobs and run everything again
175 self._set_job_selection()
176 again = True
177 break
178
179 def _run_single_job(self, job):
180 print("- {}:".format(job.name), end=' ')
181 job_state, job_result = run_job_if_possible(
182 self.session, self.runner, self.config, job)
183 print("{0}".format(job_result.outcome))
184 if job_result.comments is not None:
185 print("comments: {0}".format(job_result.comments))
186 if job_state.readiness_inhibitor_list:
187 print("inhibitors:")
188 for inhibitor in job_state.readiness_inhibitor_list:
189 print(" * {}".format(inhibitor))
190 self.session.update_job_result(job, job_result)
191
192
193class SRUCommand(PlainBoxCommand):
194 """
195 Command for running Stable Release Update (SRU) tests.
196
197 Stable release updates are periodic fixes for nominated bugs that land in
198 existing supported Ubuntu releases. To ensure a certain level of quality
199 all SRU updates affecting hardware enablement are automatically tested
200 on a pool of certified machines.
201
202 This command is _temporary_ and will eventually migrate to the checkbox
203 side. Its intended lifecycle is for the development and validation of
204 plainbox core on realistic workloads.
205 """
206
207 def __init__(self, checkbox, config):
208 self.checkbox = checkbox
209 self.config = config
210
211 def invoked(self, ns):
212 # Copy command-line arguments over configuration variables
213 try:
214 if ns.secure_id:
215 self.config.secure_id = ns.secure_id
216 if ns.fallback_file and ns.fallback_file is not Unset:
217 self.config.fallback_file = ns.fallback_file
218 if ns.c3_url:
219 self.config.c3_url = ns.c3_url
220 except ValidationError as exc:
221 print("Configuration problems prevent running SRU tests")
222 print(exc)
223 return 1
224 # Run check-config, if requested
225 if ns.check_config:
226 retval = CheckConfigInvocation(self.config).run()
227 if retval != 0:
228 return retval
229 return _SRUInvocation(self.checkbox, self.config, ns).run()
230
231 def register_parser(self, subparsers):
232 parser = subparsers.add_parser(
233 "sru", help="run automated stable release update tests")
234 parser.set_defaults(command=self)
235 parser.add_argument(
236 "--check-config",
237 action="store_true",
238 help="Run plainbox check-config before starting")
239 group = parser.add_argument_group("sru-specific options")
240 # Set defaults from based on values from the config file
241 group.set_defaults(
242 secure_id=self.config.secure_id,
243 c3_url=self.config.c3_url,
244 fallback_file=self.config.fallback_file)
245 group.add_argument(
246 '--secure-id', metavar="SECURE-ID",
247 action='store',
248 # NOTE: --secure-id is optional only when set in a config file
249 required=self.config.secure_id is Unset,
250 help=("Associate submission with a machine using this SECURE-ID"
251 " (%(default)s)"))
252 group.add_argument(
253 '--fallback', metavar="FILE",
254 dest='fallback_file',
255 action='store',
256 default=Unset,
257 help=("If submission fails save the test report as FILE"
258 " (%(default)s)"))
259 group.add_argument(
260 '--destination', metavar="URL",
261 dest='c3_url',
262 action='store',
263 help=("POST the test report XML to this URL"
264 " (%(default)s)"))
265 group = parser.add_argument_group(title="execution options")
266 group.add_argument(
267 '-n', '--dry-run',
268 action='store_true',
269 default=False,
270 help=("Skip all usual jobs."
271 " Only local, resource and attachment jobs are started"))
0272
=== added file 'plainbox/plainbox/impl/commands/test_run.py.OTHER'
--- plainbox/plainbox/impl/commands/test_run.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/commands/test_run.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,142 @@
1# This file is part of Checkbox.
2#
3# Copyright 2013 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6# Daniel Manrique <roadmr@ubuntu.com>
7#
8# Checkbox is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# Checkbox is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
20
21"""
22plainbox.impl.commands.test_run
23===============================
24
25Test definitions for plainbox.impl.run module
26"""
27
28import os
29import shutil
30import tempfile
31
32from inspect import cleandoc
33from mock import patch
34from unittest import TestCase
35
36from plainbox.impl.box import main
37from plainbox.testing_utils.io import TestIO
38
39
40class TestRun(TestCase):
41
42 def setUp(self):
43 # session data are kept in XDG_CACHE_HOME/plainbox/.session
44 # To avoid resuming a real session, we have to select a temporary
45 # location instead
46 self._sandbox = tempfile.mkdtemp()
47 self._env = os.environ
48 os.environ['XDG_CACHE_HOME'] = self._sandbox
49
50 def test_help(self):
51 with TestIO(combined=True) as io:
52 with self.assertRaises(SystemExit) as call:
53 main(['run', '--help'])
54 self.assertEqual(call.exception.args, (0,))
55 self.maxDiff = None
56 expected = """
57 usage: plainbox run [-h] [--not-interactive] [-n] [-f FORMAT] [-p OPTIONS]
58 [-o FILE] [-t TRANSPORT] [--transport-where WHERE]
59 [--transport-options OPTIONS] [-i PATTERN] [-x PATTERN]
60 [-w WHITELIST]
61
62 optional arguments:
63 -h, --help show this help message and exit
64
65 user interface options:
66 --not-interactive Skip tests that require interactivity
67 -n, --dry-run Don't actually run any jobs
68
69 output options:
70 -f FORMAT, --output-format FORMAT
71 Save test results in the specified FORMAT (pass ? for
72 a list of choices)
73 -p OPTIONS, --output-options OPTIONS
74 Comma-separated list of options for the export
75 mechanism (pass ? for a list of choices)
76 -o FILE, --output-file FILE
77 Save test results to the specified FILE (or to stdout
78 if FILE is -)
79 -t TRANSPORT, --transport TRANSPORT
80 use TRANSPORT to send results somewhere (pass ? for a
81 list of choices)
82 --transport-where WHERE
83 Where to send data using the selected transport. This
84 is passed as-is and is transport-dependent.
85 --transport-options OPTIONS
86 Comma-separated list of key-value options (k=v) to be
87 passed to the transport.
88
89 job definition options:
90 -i PATTERN, --include-pattern PATTERN
91 Run jobs matching the given regular expression.
92 Matches from the start to the end of the line.
93 -x PATTERN, --exclude-pattern PATTERN
94 Do not run jobs matching the given regular expression.
95 Matches from the start to the end of the line.
96 -w WHITELIST, --whitelist WHITELIST
97 Load whitelist containing run patterns
98 """
99 self.assertEqual(io.combined, cleandoc(expected) + "\n")
100
101 def test_run_without_args(self):
102 with TestIO(combined=True) as io:
103 with self.assertRaises(SystemExit) as call:
104 with patch('plainbox.impl.commands.run.authenticate_warmup') as mock_warmup:
105 mock_warmup.return_value = 0
106 main(['run'])
107 self.assertEqual(call.exception.args, (0,))
108 expected = """
109 ===============================[ Authentication ]===============================
110 ===============================[ Analyzing Jobs ]===============================
111 ==============================[ Running All Jobs ]==============================
112 ==================================[ Results ]===================================
113 """
114 self.assertEqual(io.combined, cleandoc(expected) + "\n")
115
116 def test_output_format_list(self):
117 with TestIO(combined=True) as io:
118 with self.assertRaises(SystemExit) as call:
119 main(['run', '--output-format=?'])
120 self.assertEqual(call.exception.args, (0,))
121 expected = """
122 Available output formats: json, rfc822, text, xml
123 """
124 self.assertEqual(io.combined, cleandoc(expected) + "\n")
125
126 def test_output_option_list(self):
127 with TestIO(combined=True) as io:
128 with self.assertRaises(SystemExit) as call:
129 main(['run', '--output-option=?'])
130 self.assertEqual(call.exception.args, (0,))
131 expected = """
132 Each format may support a different set of options
133 json: with-io-log, squash-io-log, flatten-io-log, with-run-list, with-job-list, with-resource-map, with-job-defs, with-attachments, with-comments, machine-json
134 rfc822: with-io-log, squash-io-log, flatten-io-log, with-run-list, with-job-list, with-resource-map, with-job-defs, with-attachments, with-comments
135 text: with-io-log, squash-io-log, flatten-io-log, with-run-list, with-job-list, with-resource-map, with-job-defs, with-attachments, with-comments
136 xml:
137 """
138 self.assertEqual(io.combined, cleandoc(expected) + "\n")
139
140 def tearDown(self):
141 shutil.rmtree(self._sandbox)
142 os.environ = self._env
0143
=== added file 'plainbox/plainbox/impl/config.py.OTHER'
--- plainbox/plainbox/impl/config.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/config.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,544 @@
1# This file is part of Checkbox.
2#
3# Copyright 2013 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.config` -- configuration
22============================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE A STABLE PUBLIC API
27"""
28
29from abc import ABCMeta, abstractmethod
30import collections
31import configparser
32import logging
33import re
34
35
36logger = logging.getLogger("plainbox.config")
37
38
39class INameTracking(metaclass=ABCMeta):
40 """
41 Interface for classes that are instantiated as a part of definition of
42 another class. The purpose of this interface is to allow instances to learn
43 about the name (python identifier) that was assigned to the instance at
44 class definition time.
45
46 Subclasses must define the _set_tracked_name() method.
47 """
48
49 @abstractmethod
50 def _set_tracked_name(self, name):
51 """
52 Set the that corresponds to the symbol used in class definition. This
53 can be a no-op if the name was already set by other means
54 """
55
56
57class ConfigMetaData:
58 """
59 Class containing meta-data about a Config class
60
61 Sub-classes of this class are automatically added to each Config subclass
62 as a Meta class-level attribute.
63
64 This class has typically two attributes:
65
66 :cvar variable_list:
67 A list of all Variable objects defined in the class
68
69 :cvar section_list:
70 A list of all Section object defined in the class
71
72 :cvar filename_list:
73 A list of config files (pathnames) to read on call to
74 :meth:`Config.read`
75 """
76 variable_list = []
77 section_list = []
78 filename_list = []
79
80
81class UnsetType:
82 """
83 Class of the Unset object
84 """
85
86 def __str__(self):
87 return "unset"
88
89 def __repr__(self):
90 return "Unset"
91
92
93Unset = UnsetType()
94
95
96class Variable(INameTracking):
97 """
98 Variable that can be used in a configuration systems
99 """
100
101 _KIND_CHOICE = (bool, int, float, str)
102
103 def __init__(self, name=None, *, section='DEFAULT', kind=str,
104 default=Unset, validator_list=None, help_text=None):
105 # Ensure kind is correct
106 if kind not in self._KIND_CHOICE:
107 raise ValueError("unsupported kind")
108 # Ensure that we have a validator_list, even if empty
109 if validator_list is None:
110 validator_list = []
111 # Insert a KindValidator as the first validator to run
112 validator_list.insert(0, KindValidator)
113 # Assign all the attributes
114 self._name = name
115 self._section = section
116 self._kind = kind
117 self._default = default
118 self._validator_list = validator_list
119 self._help_text = help_text
120 self._validate_default_value()
121 # Workaround for Sphinx breaking if __doc__ is a property
122 self.__doc__ = self.help_text or self.__class__.__doc__
123
124 def _validate_default_value(self):
125 """
126 Validate the default value, unless it is Unset
127 """
128 if self.default is Unset:
129 return
130 for validator in self.validator_list:
131 message = validator(self, self.default)
132 if message is not None:
133 raise ValidationError(self, self.default, message)
134
135 def _set_tracked_name(self, name):
136 """
137 Internal method used by :meth:`ConfigMeta.__new__`
138 """
139 if self._name is None:
140 self._name = name
141
142 @property
143 def name(self):
144 """
145 name of this variable
146 """
147 return self._name
148
149 @property
150 def section(self):
151 """
152 name of the section this variable belongs to (in a configuration file)
153 """
154 return self._section
155
156 @property
157 def kind(self):
158 """
159 the "poor man's type", can be only str (default), bool, float or int
160 """
161 return self._kind
162
163 @property
164 def default(self):
165 """
166 a default value, if any
167 """
168 return self._default
169
170 @property
171 def validator_list(self):
172 """
173 a optional list of :class:`Validator` instances that are enforced on
174 the value
175 """
176 return self._validator_list
177
178 @property
179 def help_text(self):
180 """
181 an optional help text associated with this variable
182 """
183 return self._help_text
184
185 def __repr__(self):
186 return "<Variable name:{!r}>".format(self.name)
187
188 def __get__(self, instance, owner):
189 """
190 Get the value of a variable
191
192 Missing variables return the default value
193 """
194 if instance is None:
195 return self
196 try:
197 return instance._get_variable(self._name)
198 except KeyError:
199 return self.default
200
201 def __set__(self, instance, new_value):
202 """
203 Set the value of a variable
204
205 :raises ValidationError: if the new value is incorrect
206 """
207 # Check it against all validators
208 for validator in self.validator_list:
209 message = validator(self, new_value)
210 if message is not None:
211 raise ValidationError(self, new_value, message)
212 # Assign it to the backing store of the instance
213 instance._set_variable(self.name, new_value)
214
215 def __delete__(self, instance):
216 # NOTE: this is quite confusing, this method is a companion to __get__
217 # and __set__ but __del__ is entirely unrelated (object garbage
218 # collected, do final cleanup) so don't think this is a mistake
219 instance._del_variable(self._name)
220
221
222class Section(INameTracking):
223 """
224 A section of a configuration file.
225 """
226
227 def __init__(self, name=None, *, help_text=None):
228 self._name = name
229 self._help_text = help_text
230 # Workaround for Sphinx breaking if __doc__ is a property
231 self.__doc__ = self.help_text or self.__class__.__doc__
232
233 def _set_tracked_name(self, name):
234 """
235 Internal method used by :meth:`ConfigMeta.__new__`
236 """
237 if self._name is None:
238 self._name = name
239
240 @property
241 def name(self):
242 """
243 name of this section
244 """
245 return self._name
246
247 @property
248 def help_text(self):
249 """
250 an optional help text associated with this section
251 """
252 return self._help_text
253
254 def __get__(self, instance, owner):
255 if instance is None:
256 return self
257 try:
258 return instance._get_section(self._name)
259 except KeyError:
260 return Unset
261
262 def __set__(self, instance, new_value):
263 instance._set_section(self.name, new_value)
264
265 def __delete__(self, instance):
266 instance._del_section(self.name)
267
268
269class ConfigMeta(type):
270 """
271 Meta class for all configuration classes.
272
273 This meta class handles assignment of '_name' attribute to each
274 :class:`Variable` instance created in the class body.
275
276 It also accumulates such instances and assigns them to variable_list in a
277 helper Meta class which is assigned back to the namespace
278 """
279
280 def __new__(mcls, name, bases, namespace, **kwargs):
281 # Keep track of variables and sections from base class
282 variable_list = []
283 section_list = []
284 if 'Meta' in namespace:
285 if hasattr(namespace['Meta'], 'variable_list'):
286 variable_list = namespace['Meta'].variable_list[:]
287 if hasattr(namespace['Meta'], 'section_list'):
288 section_list = namespace['Meta'].section_list[:]
289 # Discover all Variable and Section instances
290 # defined in the class namespace
291 for name, item in namespace.items():
292 if isinstance(item, INameTracking):
293 item._set_tracked_name(name)
294 if isinstance(item, Variable):
295 variable_list.append(item)
296 elif isinstance(item, Section):
297 section_list.append(item)
298 # Get or create the class of the 'Meta' object.
299 #
300 # This class should always inherit from ConfigMetaData and whatever the
301 # user may have defined as Meta.
302 Meta_name = "Meta"
303 Meta_bases = (ConfigMetaData,)
304 Meta_ns = {
305 'variable_list': variable_list,
306 'section_list': section_list
307 }
308 if 'Meta' in namespace:
309 user_Meta_cls = namespace['Meta']
310 if not isinstance(user_Meta_cls, type):
311 raise TypeError("Meta must be a class")
312 Meta_bases = (user_Meta_cls, ConfigMetaData)
313 # Create a new type for the Meta class
314 namespace['Meta'] = type.__new__(
315 type(ConfigMetaData), Meta_name, Meta_bases, Meta_ns)
316 # Create a new type for the Config subclass
317 return type.__new__(mcls, name, bases, namespace)
318
319 @classmethod
320 def __prepare__(mcls, name, bases, **kwargs):
321 return collections.OrderedDict()
322
323
324class PlainBoxConfigParser(configparser.ConfigParser):
325 """
326 A simple ConfigParser subclass that does not lowercase
327 key names.
328 """
329 def optionxform(self, option):
330 return option
331
332
333class Config(metaclass=ConfigMeta):
334 """
335 Base class for configuration systems
336
337 :ivar _var:
338 storage backend for Variable definitions
339
340 :ivar _section:
341 storage backend for Section definitions
342
343 :ivar _filename_list:
344 list of pathnames to files that were loaded by the last call to
345 :meth:`read()`
346
347 :ivar _problem_list:
348 list of :class:`ValidationError` that were detected by the last call to
349 :meth:`read()`
350 """
351
352 def __init__(self):
353 """
354 Initialize an empty Config object
355 """
356 self._var = {}
357 self._section = {}
358 self._filename_list = []
359 self._problem_list = []
360
361 @property
362 def problem_list(self):
363 """
364 list of :class:`ValidationError` that were detected by the last call to
365 :meth:`read()`
366 """
367 return self._problem_list
368
369 @property
370 def filename_list(self):
371 """
372 list of pathnames to files that were loaded by the last call to
373 :meth:`read()`
374 """
375 return self._filename_list
376
377 @classmethod
378 def get(cls):
379 """
380 Get an instance of this Config class with all the configuration loaded
381 from default locations. The locations are determined by
382 Meta.filename_list attribute.
383
384 :returns: fresh :class:`Config` instance
385
386 """
387 self = cls()
388 self.read(cls.Meta.filename_list)
389 return self
390
391 def read(self, filename_list):
392 """
393 Load and merge settings from many files.
394
395 This method tries to open each file from the list of filenames, parse
396 it as an INI file using :class:`PlainBoxConfigParser` (a simple
397 ConfigParser subclass that respects the case of key names). The list of
398 files actually accessed is saved as available as
399 :attr:`Config.filename_list`.
400
401 If any problem is detected during parsing (e.g. syntax errors) those
402 are captured and added to the :attr:`Config.problem_list`.
403
404 After all files are loaded each :class:`Variable` and :class:`Section`
405 defined in the :class:`Config` class is assigned with the data from the
406 merged configuration data.
407
408 Any variables that cannot be assigned and raise
409 :class:`ValidationError` are ignored but the list of problems is saved.
410
411 All unused configuration (extra variables that are not defined as
412 either Variable or Section class) is silently ignored.
413
414 .. note::
415 This method resets :ivar:`_problem_list` and
416 :ivar:`_filename_list`.
417 """
418 parser = PlainBoxConfigParser()
419 # Reset filename list and problem list
420 self._filename_list = []
421 self._problem_list = []
422 # Try loading all of the config files
423 try:
424 self._filename_list = parser.read(filename_list)
425 except configparser.Error as exc:
426 self._problem_list.append(exc)
427 # Pick a reader function appropriate for the kind of variable
428 reader_fn = {
429 str: parser.get,
430 bool: parser.getboolean,
431 int: parser.getint,
432 float: parser.getfloat
433 }
434 # Load all variables that we know about
435 for variable in self.Meta.variable_list:
436 # Access the variable in the configuration file
437 try:
438 value = reader_fn[variable.kind](
439 variable.section, variable.name)
440 except configparser.NoSectionError:
441 continue
442 except configparser.NoOptionError:
443 continue
444 # Try to assign it
445 try:
446 variable.__set__(self, value)
447 except ValidationError as exc:
448 self.problem_list.append(exc)
449 # Load all sections that we know about
450 for section in self.Meta.section_list:
451 try:
452 # Access the section in the configuration file
453 value = dict(parser.items(section.name))
454 except configparser.NoSectionError:
455 continue
456 # Assign it
457 section.__set__(self, value)
458
459 def _get_variable(self, name):
460 """
461 Internal method called by :meth:`Variable.__get__`
462 """
463 return self._var[name]
464
465 def _set_variable(self, name, value):
466 """
467 Internal method called by :meth:`Variable.__set__`
468 """
469 self._var[name] = value
470
471 def _del_variable(self, name):
472 """
473 Internal method called by :meth:`Variable.__delete__`
474 """
475 del self._var[name]
476
477 def _get_section(self, name):
478 """
479 Internal method called by :meth:`Section.__get__`
480 """
481 return self._section[name]
482
483 def _set_section(self, name, value):
484 """
485 Internal method called by :meth:`Section.__set__`
486 """
487 self._section[name] = value
488
489 def _del_section(self, name):
490 """
491 Internal method called by :meth:`Section.__delete__`
492 """
493 del self._section[name]
494
495
496class ValidationError(ValueError):
497 """
498 Exception raised when configuration variables fail to validate
499 """
500
501 def __init__(self, variable, new_value, message):
502 self.variable = variable
503 self.new_value = new_value
504 self.message = message
505
506 def __str__(self):
507 return self.message
508
509
510class IValidator(metaclass=ABCMeta):
511 """
512 An interface for variable vale validators
513 """
514
515 @abstractmethod
516 def __call__(self, variable, new_value):
517 """
518 Check if a value is appropriate for the variable.
519
520 :returns: None if everything is okay
521 :returns: string that describes the problem if the value cannot be used
522 """
523
524
525def KindValidator(variable, new_value):
526 """
527 A validator ensuring that values match the "kind" of the variable.
528 """
529 if not isinstance(new_value, variable.kind):
530 return "expected a {}".format(variable.kind.__name__)
531
532
533class PatternValidator(IValidator):
534 """
535 A validator ensuring that values match a given pattern
536 """
537
538 def __init__(self, pattern_text):
539 self.pattern_text = pattern_text
540 self.pattern = re.compile(pattern_text)
541
542 def __call__(self, variable, new_value):
543 if not self.pattern.match(new_value):
544 return "does not match pattern: {!r}".format(self.pattern_text)
0545
=== added file 'plainbox/plainbox/impl/job.py.OTHER'
--- plainbox/plainbox/impl/job.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/job.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,259 @@
1# This file is part of Checkbox.
2#
3# Copyright 2012, 2013 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.job` -- job definition
22==========================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29import logging
30import os
31import re
32
33from plainbox.abc import IJobDefinition
34from plainbox.impl.config import Unset
35from plainbox.impl.resource import ResourceProgram
36from plainbox.impl.secure.checkbox_trusted_launcher import BaseJob
37
38
39logger = logging.getLogger("plainbox.job")
40
41
42class JobDefinition(BaseJob, IJobDefinition):
43 """
44 Job definition class.
45
46 Thin wrapper around the RFC822 record that defines a checkbox job
47 definition
48 """
49
50 @property
51 def name(self):
52 return self.__getattr__('name')
53
54 @property
55 def requires(self):
56 try:
57 return self.__getattr__('requires')
58 except AttributeError:
59 return None
60
61 @property
62 def description(self):
63 try:
64 return self.__getattr__('description')
65 except AttributeError:
66 return None
67
68 @property
69 def depends(self):
70 try:
71 return self.__getattr__('depends')
72 except AttributeError:
73 return None
74
75 @property
76 def via(self):
77 """
78 The checksum of the "parent" job when the current JobDefinition comes
79 from a job output using the local plugin
80 """
81 return self._via
82
83 @property
84 def origin(self):
85 """
86 The Origin object associated with this JobDefinition
87
88 May be None
89 """
90 return self._origin
91
92 def __init__(self, data, origin=None, checkbox=None, via=None):
93 super(JobDefinition, self).__init__(data)
94 self._resource_program = None
95 self._origin = origin
96 self._checkbox = checkbox
97 self._via = via
98
99 def __str__(self):
100 return self.name
101
102 def __repr__(self):
103 return "<JobDefinition name:{!r} plugin:{!r}>".format(
104 self.name, self.plugin)
105
106 def __getattr__(self, attr):
107 if attr in self._data:
108 return self._data[attr]
109 gettext_attr = "_{}".format(attr)
110 if gettext_attr in self._data:
111 value = self._data[gettext_attr]
112 # TODO: feed through gettext
113 return value
114 raise AttributeError(attr)
115
116 def _get_persistance_subset(self):
117 state = {}
118 state['data'] = {}
119 for key, value in self._data.items():
120 state['data'][key] = value
121 if self.via is not None:
122 state['via'] = self.via
123 return state
124
125 def __eq__(self, other):
126 if not isinstance(other, JobDefinition):
127 return False
128 return self.get_checksum() == other.get_checksum()
129
130 def __ne__(self, other):
131 if not isinstance(other, JobDefinition):
132 return True
133 return self.get_checksum() != other.get_checksum()
134
135 def get_resource_program(self):
136 """
137 Return a ResourceProgram based on the 'requires' expression.
138
139 The program instance is cached in the JobDefinition and is not
140 compiled or validated on subsequent calls.
141
142 Returns ResourceProgram or None
143 Raises ResourceProgramError or SyntaxError
144 """
145 if self.requires is not None and self._resource_program is None:
146 self._resource_program = ResourceProgram(self.requires)
147 return self._resource_program
148
149 def get_direct_dependencies(self):
150 """
151 Compute and return a set of direct dependencies
152
153 To combat a simple mistake where the jobs are space-delimited any
154 mixture of white-space (including newlines) and commas are allowed.
155 """
156 if self.depends:
157 return {name for name in re.split('[\s,]+', self.depends)}
158 else:
159 return set()
160
161 def get_resource_dependencies(self):
162 """
163 Compute and return a set of resource dependencies
164 """
165 program = self.get_resource_program()
166 if program:
167 return program.required_resources
168 else:
169 return set()
170
171 @classmethod
172 def from_rfc822_record(cls, record):
173 """
174 Create a JobDefinition instance from rfc822 record
175
176 The record must be a RFC822Record instance.
177
178 Only the 'name' and 'plugin' keys are required.
179 All other data is stored as is and is entirely optional.
180 """
181 for key in ['plugin', 'name']:
182 if key not in record.data:
183 raise ValueError(
184 "Required record key {!r} was not found".format(key))
185 return cls(record.data, record.origin)
186
187 def modify_execution_environment(self, env, session_dir, config=None):
188 """
189 Alter execution environment as required to execute this job.
190
191 The environment is modified in place.
192
193 The session_dir argument can be passed to scripts to know where to
194 create temporary data. This data will persist during the lifetime of
195 the session.
196
197 The config argument (which defaults to None) should be a PlainBoxConfig
198 object. It is used to provide values for missing environment variables
199 that are required by the job (as expressed by the environ key in the
200 job definition file).
201
202 Computes and modifies the dictionary of additional values that need to
203 be added to the base environment. Note that all changes to the
204 environment (modifications, not replacements) depend on the current
205 environment. This may be of importance when attempting to setup the
206 test session as another user.
207
208 This environment has additional PATH, PYTHONPATH entries. It also uses
209 fixed LANG so that scripts behave as expected. Lastly it sets
210 CHECKBOX_SHARE that is required by some scripts.
211 """
212 # XXX: this obviously requires a checkbox object to know where stuff is
213 # but during the transition we may not have one available.
214 assert self._checkbox is not None
215 # Use PATH that can lookup checkbox scripts
216 if self._checkbox.extra_PYTHONPATH:
217 env['PYTHONPATH'] = os.pathsep.join(
218 [self._checkbox.extra_PYTHONPATH]
219 + env.get("PYTHONPATH", "").split(os.pathsep))
220 # Update PATH so that scripts can be found
221 env['PATH'] = os.pathsep.join(
222 [self._checkbox.extra_PATH]
223 + env.get("PATH", "").split(os.pathsep))
224 # Add CHECKBOX_SHARE that is needed by one script
225 env['CHECKBOX_SHARE'] = self._checkbox.CHECKBOX_SHARE
226 # Add CHECKBOX_DATA (temporary checkbox data)
227 env['CHECKBOX_DATA'] = session_dir
228 # Inject additional variables that are requested in the config
229 if config is not None and config.environment is not Unset:
230 for env_var in config.environment:
231 # Don't override anything that is already present in the
232 # current environment. This will allow users to customize
233 # variables without editing any config files.
234 if env_var in env:
235 continue
236 # If the environment section of the configuration file has a
237 # particular variable then copy it over.
238 env[env_var] = config.environment[env_var]
239
240 def create_child_job_from_record(self, record):
241 """
242 Create a new JobDefinition from RFC822 record.
243
244 This method should only be used to create additional jobs from local
245 jobs (plugin local). The intent is two-fold:
246 1) to encapsulate the sharing of the embedded checkbox reference.
247 2) to set the ``via`` attribute (to aid the trusted launcher)
248 """
249 job = self.from_rfc822_record(record)
250 job._checkbox = self._checkbox
251 job._via = self.get_checksum()
252 return job
253
254 @classmethod
255 def from_json_record(cls, record):
256 """
257 Create a JobDefinition instance from JSON record
258 """
259 return cls(record['data'], via=record.get('via'))
0260
=== added file 'plainbox/plainbox/impl/runner.py.OTHER'
--- plainbox/plainbox/impl/runner.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/runner.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,421 @@
1# This file is part of Checkbox.
2#
3# Copyright 2012 Canonical Ltd.
4# Written by:
5# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.runner` -- job runner
22=========================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29import collections
30import datetime
31import json
32import logging
33import os
34import string
35
36from plainbox.vendor import extcmd
37
38from plainbox.abc import IJobRunner
39from plainbox.impl.result import JobResult, IOLogRecord, IoLogEncoder
40
41logger = logging.getLogger("plainbox.runner")
42
43
44def slugify(_string):
45 """
46 Slugify - like Django does for URL - transform a random string to a valid
47 slug that can be later used in filenames
48 """
49 valid_chars = frozenset(
50 "-_.{}{}".format(string.ascii_letters, string.digits))
51 return ''.join(c if c in valid_chars else '_' for c in _string)
52
53
54def io_log_write(log, stream):
55 """
56 JSON call to serialize io_log objects to disk
57 """
58 json.dump(
59 log, stream, ensure_ascii=False, indent=None, cls=IoLogEncoder,
60 separators=(',', ':'))
61
62
63def authenticate_warmup():
64 """
65 Call the checkbox trusted launcher in warmup mode.
66
67 This will use the corresponding PolicyKit action and start the
68 authentication agent (depending on the installed policy file)
69 """
70 warmup_popen = extcmd.ExternalCommand()
71 return warmup_popen.call(
72 ['pkexec', 'checkbox-trusted-launcher', '--warmup'])
73
74
75class CommandIOLogBuilder(extcmd.DelegateBase):
76 """
77 Delegate for extcmd that builds io_log entries.
78
79 IO log entries are records kept by JobResult.io_log and correspond to all
80 of the data that was written by called process. The format is a sequence of
81 tuples (delay, stream_name, data).
82 """
83
84 def on_begin(self, args, kwargs):
85 """
86 Internal method of extcmd.DelegateBase
87
88 Called when a command is being invoked.
89 Begins tracking time (relative time entries) and creates the empty
90 io_log list.
91 """
92 logger.debug("io log starting for command: %r", args)
93 self.io_log = []
94 self.last_msg = datetime.datetime.utcnow()
95
96 def on_line(self, stream_name, line):
97 """
98 Internal method of IOLogBuilder
99
100 Appends each line to the io_log. Maintains a timestamp of the last
101 message so that approximate delay between each piece of output can be
102 recorded as well.
103 """
104 now = datetime.datetime.utcnow()
105 delay = now - self.last_msg
106 self.last_msg = now
107 record = IOLogRecord(delay.total_seconds(), stream_name, line)
108 self.io_log.append(record)
109 logger.debug("io log captured %r", record)
110
111
112class CommandOutputWriter(extcmd.DelegateBase):
113 """
114 Delegate for extcmd that writes output to a file on disk.
115
116 The file itself is only opened once on_begin() gets called by extcmd. This
117 makes it safe to instantiate this without worrying about dangling
118 resources.
119 """
120
121 def __init__(self, stdout_path, stderr_path):
122 """
123 Initialize new writer.
124
125 Just records output paths.
126 """
127 self.stdout_path = stdout_path
128 self.stderr_path = stderr_path
129
130 def on_begin(self, args, kwargs):
131 """
132 Internal method of extcmd.DelegateBase
133
134 Called when a command is being invoked
135 """
136 self.stdout = open(self.stdout_path, "wb")
137 self.stderr = open(self.stderr_path, "wb")
138
139 def on_end(self, returncode):
140 """
141 Internal method of extcmd.DelegateBase
142
143 Called when a command finishes running
144 """
145 self.stdout.close()
146 self.stderr.close()
147
148 def on_line(self, stream_name, line):
149 """
150 Internal method of extcmd.DelegateBase
151
152 Called for each line of output.
153 """
154 if stream_name == 'stdout':
155 self.stdout.write(line)
156 elif stream_name == 'stderr':
157 self.stderr.write(line)
158
159
160class FallbackCommandOutputPrinter(extcmd.DelegateBase):
161 """
162 Delegate for extcmd that prints all output to stdout.
163
164 This delegate is only used as a fallback when no delegate was explicitly
165 provided to a JobRunner instance.
166 """
167
168 def __init__(self, prompt):
169 self._prompt = prompt
170 self._lineno = collections.defaultdict(int)
171 self._abort = False
172
173 def on_line(self, stream_name, line):
174 if self._abort:
175 return
176 self._lineno[stream_name] += 1
177 try:
178 print("(job {}, <{}:{:05}>) {}".format(
179 self._prompt, stream_name, self._lineno[stream_name],
180 line.decode('UTF-8').rstrip()))
181 except UnicodeDecodeError:
182 self._abort = True
183
184
185class JobRunner(IJobRunner):
186 """
187 Runner for jobs - executes jobs and produces results
188
189 The runner is somewhat de-coupled from jobs and session. It still carries
190 all checkbox-specific logic about the various types of plugins.
191
192 The runner consumes jobs and configuration objects and produces job result
193 objects. The runner can operate in dry-run mode, when enabled, most jobs
194 are never started. Only jobs listed in DRY_RUN_PLUGINS are executed.
195 """
196
197 # List of plugins that are still executed
198 _DRY_RUN_PLUGINS = ('local', 'resource', 'attachment')
199
200 def __init__(self, session_dir, jobs_io_log_dir,
201 command_io_delegate=None, outcome_callback=None,
202 dry_run=False):
203 """
204 Initialize a new job runner.
205
206 Uses the specified session_dir as CHECKBOX_DATA environment variable.
207 Uses the specified IO delegate for extcmd.ExternalCommandWithDelegate
208 to track IO done by the called commands (optional, a simple console
209 printer is provided if missing).
210 """
211 self._session_dir = session_dir
212 self._jobs_io_log_dir = jobs_io_log_dir
213 self._command_io_delegate = command_io_delegate
214 self._outcome_callback = outcome_callback
215 self._dry_run = dry_run
216
217 def run_job(self, job, config=None):
218 """
219 Run the specified job an return the result
220 """
221 logger.info("Running %r", job)
222 func_name = "_plugin_" + job.plugin.replace('-', '_')
223 try:
224 runner = getattr(self, func_name)
225 except AttributeError:
226 return JobResult({
227 'job': job,
228 'outcome': JobResult.OUTCOME_NOT_IMPLEMENTED,
229 'comment': 'This plugin is not supported'
230 })
231 else:
232 if self._dry_run and job.plugin not in self._DRY_RUN_PLUGINS:
233 return self._dry_run_result(job)
234 else:
235 return runner(job, config)
236
237 def _dry_run_result(self, job):
238 """
239 Produce the result that is used when running in dry-run mode
240 """
241 return JobResult({
242 'job': job,
243 'outcome': JobResult.OUTCOME_SKIP,
244 'comments': "Job skipped in dry-run mode"
245 })
246
247 def _plugin_shell(self, job, config):
248 return self._just_run_command(job, config)
249
250 _plugin_attachment = _plugin_shell
251
252 def _plugin_resource(self, job, config):
253 return self._just_run_command(job, config)
254
255 def _plugin_local(self, job, config):
256 return self._just_run_command(job, config)
257
258 def _plugin_manual(self, job, config):
259 if self._outcome_callback is None:
260 return JobResult({
261 'job': job,
262 'outcome': JobResult.OUTCOME_SKIP,
263 'comment': "non-interactive test run"
264 })
265 else:
266 result = self._just_run_command(job, config)
267 # XXX: make outcome writable
268 result._data['outcome'] = self._outcome_callback()
269 return result
270
271 _plugin_user_interact = _plugin_manual
272 _plugin_user_verify = _plugin_manual
273
274 def _just_run_command(self, job, config):
275 # Run the embedded command
276 return_code, io_log = self._run_command(job, config)
277 # Convert the return of the command to the outcome of the job
278 if return_code == 0:
279 outcome = JobResult.OUTCOME_PASS
280 else:
281 outcome = JobResult.OUTCOME_FAIL
282 # Create a result object and return it
283 return JobResult({
284 'job': job,
285 'outcome': outcome,
286 'return_code': return_code,
287 'io_log': io_log
288 })
289
290 def _get_script_env(self, job, config=None, only_changes=False):
291 """
292 Compute the environment the script will be executed in
293 """
294 # Get a proper environment
295 env = dict(os.environ)
296 # Use non-internationalized environment
297 env['LANG'] = 'C.UTF-8'
298 # Allow the job to customize anything
299 job.modify_execution_environment(env, self._session_dir, config)
300 # If a differential environment is requested return only the subset
301 # that has been altered.
302 #
303 # XXX: This will effectively give the root user our PATH which _may_ be
304 # good bud _might_ be dangerous. This will need some peer review.
305 if only_changes:
306 return {key: value
307 for key, value in env.items()
308 if key not in os.environ or os.environ[key] != value
309 or key in job.get_environ_settings()}
310 else:
311 return env
312
313 def _get_command_trusted(self, job, config=None):
314 # When the job requires to run as root then elevate our permissions
315 # via pkexec(1). Since pkexec resets environment we need to somehow
316 # pass the extra things we require. To do that we pass the list of
317 # changed environment variables in addition to the job hash.
318 cmd = ['checkbox-trusted-launcher', '--hash', job.get_checksum()] + [
319 "{key}={value}".format(key=key, value=value)
320 for key, value in self._get_script_env(
321 job, config, only_changes=True
322 ).items()
323 ]
324 if job.via is not None:
325 cmd += ['--via', job.via]
326 return cmd
327
328 def _get_command_src(self, job, config=None):
329 # Running PlainBox from source doesn't require the trusted launcher
330 # That's why we use the env(1)' command and pass it the list of
331 # changed environment variables.
332 # The whole pkexec and env part gets prepended to the command
333 # we were supposed to run.
334 cmd = ['env']
335 cmd += [
336 "{key}={value}".format(key=key, value=value)
337 for key, value in self._get_script_env(
338 job, only_changes=True
339 ).items()
340 ]
341 cmd += ['bash', '-c', job.command]
342 return cmd
343
344 def _run_command(self, job, config):
345 """
346 Run the shell command associated with the specified job.
347
348 Returns a tuple (return_code, io_log)
349 """
350 # Bail early if there is nothing do do
351 if job.command is None:
352 return None, ()
353 ui_io_delegate = self._command_io_delegate
354 # If there is no UI delegate specified create a simple
355 # delegate that logs all output to the console
356 if ui_io_delegate is None:
357 ui_io_delegate = FallbackCommandOutputPrinter(job.name)
358 # Create a delegate that writes all IO to disk
359 slug = slugify(job.name)
360 output_writer = CommandOutputWriter(
361 stdout_path=os.path.join(self._jobs_io_log_dir,
362 "{}.stdout".format(slug)),
363 stderr_path=os.path.join(self._jobs_io_log_dir,
364 "{}.stderr".format(slug)))
365 # Create a delegate that builds a log of all IO
366 io_log_builder = CommandIOLogBuilder()
367 # Create the delegate for routing IO
368 #
369 #
370 # Split the stream of data into three parts (each part is expressed as
371 # an element of extcmd.Chain()).
372 #
373 # Send the first copy of the data through bytes->text decoder and
374 # then to the UI delegate. This cold be something provided by the
375 # higher level caller or the default CommandOutputLogger.
376 #
377 # Send the second copy of the data to the _IOLogBuilder() instance that
378 # just concatenates subsequent bytes into neat time-stamped records.
379 #
380 # Send the third copy to the output writer that writes everything to
381 # disk.
382 delegate = extcmd.Chain([
383 ui_io_delegate,
384 io_log_builder,
385 output_writer])
386 logger.debug("job[%s] extcmd delegate: %r", job.name, delegate)
387 # Create a subprocess.Popen() like object that uses the delegate
388 # system to observe all IO as it occurs in real time.
389 logging_popen = extcmd.ExternalCommandWithDelegate(delegate)
390 # Start the process and wait for it to finish getting the
391 # result code. This will actually call a number of callbacks
392 # while the process is running. It will also spawn a few
393 # threads although all callbacks will be fired from a single
394 # thread (which is _not_ the main thread)
395 logger.debug("job[%s] starting command: %s", job.name, job.command)
396 if job.user is not None:
397 if job._checkbox._mode == 'src':
398 cmd = self._get_command_src(job, config)
399 else:
400 cmd = self._get_command_trusted(job, config)
401 cmd = ['pkexec', '--user', job.user] + cmd
402 logging.debug("job[%s] executing %r", job.name, cmd)
403 return_code = logging_popen.call(cmd)
404 else:
405 # XXX: sadly using /bin/sh results in broken output
406 # XXX: maybe run it both ways and raise exceptions on differences?
407 cmd = ['bash', '-c', job.command]
408 logging.debug("job[%s] executing %r", job.name, cmd)
409 return_code = logging_popen.call(
410 cmd, env=self._get_script_env(job, config))
411 logger.debug("job[%s] command return code: %r",
412 job.name, return_code)
413 # XXX: Perhaps handle process dying from signals here
414 # When the process is killed proc.returncode is not set
415 # and another (cannot remember now) attribute is set
416 fjson = os.path.join(self._jobs_io_log_dir, "{}.json".format(slug))
417 with open(fjson, "wt") as stream:
418 io_log_write(io_log_builder.io_log, stream)
419 stream.flush()
420 os.fsync(stream.fileno())
421 return return_code, fjson
0422
=== added directory 'plainbox/plainbox/impl/secure'
=== added file 'plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER'
--- plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,395 @@
1# This file is part of Checkbox.
2#
3# Copyright 2013 Canonical Ltd.
4# Written by:
5# Sylvain Pineau <sylvain.pineau@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21:mod:`plainbox.impl.secure.checkbox_trusted_launcher` -- command launcher
22=========================================================================
23
24.. warning::
25
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""
28
29import argparse
30import collections
31import glob
32import hashlib
33import json
34import os
35import re
36import subprocess
37from inspect import cleandoc
38
39
40class BaseJob:
41 """
42 Base Job definition class.
43 """
44
45 @property
46 def plugin(self):
47 return self.__getattr__('plugin')
48
49 @property
50 def command(self):
51 try:
52 return self.__getattr__('command')
53 except AttributeError:
54 return None
55
56 @property
57 def environ(self):
58 try:
59 return self.__getattr__('environ')
60 except AttributeError:
61 return None
62
63 @property
64 def user(self):
65 try:
66 return self.__getattr__('user')
67 except AttributeError:
68 return None
69
70 def __init__(self, data):
71 self._data = data
72
73 def __getattr__(self, attr):
74 if attr in self._data:
75 return self._data[attr]
76 raise AttributeError(attr)
77
78 def get_checksum(self):
79 """
80 Compute a checksum of the job definition.
81
82 This method can be used to compute the checksum of the canonical form
83 of the job definition. The canonical form is the UTF-8 encoded JSON
84 serialization of the data that makes up the full definition of the job
85 (all keys and values). The JSON serialization uses no indent and
86 minimal separators.
87
88 The checksum is defined as the SHA256 hash of the canonical form.
89 """
90 # Ideally we'd use simplejson.dumps() with sorted keys to get
91 # predictable serialization but that's another dependency. To get
92 # something simple that is equally reliable, just sort all the keys
93 # manually and ask standard json to serialize that..
94 sorted_data = collections.OrderedDict(sorted(self._data.items()))
95 # Compute the canonical form which is arbitrarily defined as sorted
96 # json text with default indent and separator settings.
97 canonical_form = json.dumps(
98 sorted_data, indent=None, separators=(',', ':'))
99 # Compute the sha256 hash of the UTF-8 encoding of the canonical form
100 # and return the hex digest as the checksum that can be displayed.
101 return hashlib.sha256(canonical_form.encode('UTF-8')).hexdigest()
102
103 def get_environ_settings(self):
104 """
105 Return a set of requested environment variables
106 """
107 if self.environ is not None:
108 return {variable for variable in re.split('[\s,]+', self.environ)}
109 else:
110 return set()
111
112 def modify_execution_environment(self, environ, packages):
113 """
114 Compute the environment the script will be executed in
115 """
116 # Get a proper environment
117 env = dict(os.environ)
118 # Use non-internationalized environment
119 env['LANG'] = 'C.UTF-8'
120 # Create CHECKBOX*_SHARE for every checkbox related packages
121 # Add their respective script directory to the PATH variable
122 # giving precedence to those located in /usr/lib/
123 for path in packages:
124 basename = os.path.basename(path)
125 env[basename.upper().replace('-', '_') + '_SHARE'] = path
126 # Update PATH so that scripts can be found
127 env['PATH'] = os.pathsep.join([
128 os.path.join('usr', 'lib', basename, 'bin'),
129 os.path.join(path, 'scripts')]
130 + env.get("PATH", "").split(os.pathsep))
131 if 'CHECKBOX_DATA' in env:
132 env['CHECKBOX_DATA'] = environ['CHECKBOX_DATA']
133 # Add new environment variables only if they are defined in the
134 # job environ property
135 for key in self.get_environ_settings():
136 if key in environ:
137 env[key] = environ[key]
138 return env
139
140
141class BaseRFC822Record:
142 """
143 Base class for tracking RFC822 records
144
145 This is a simple container for the dictionary of data.
146 """
147
148 def __init__(self, data):
149 self._data = data
150
151 @property
152 def data(self):
153 """
154 The data set (dictionary)
155 """
156 return self._data
157
158
159class RFC822SyntaxError(SyntaxError):
160 """
161 SyntaxError subclass for RFC822 parsing functions
162 """
163
164 def __init__(self, filename, lineno, msg):
165 self.filename = filename
166 self.lineno = lineno
167 self.msg = msg
168
169
170def load_rfc822_records(stream, data_cls=dict):
171 """
172 Load a sequence of rfc822-like records from a text stream.
173
174 Each record consists of any number of key-value pairs. Subsequent records
175 are separated by one blank line. A record key may have a multi-line value
176 if the line starts with whitespace character.
177
178 Returns a list of subsequent values as instances BaseRFC822Record class. If
179 the optional data_cls argument is collections.OrderedDict then the values
180 retain their original ordering.
181 """
182 return list(gen_rfc822_records(stream, data_cls))
183
184
185def gen_rfc822_records(stream, data_cls=dict):
186 """
187 Load a sequence of rfc822-like records from a text stream.
188
189 Each record consists of any number of key-value pairs. Subsequent records
190 are separated by one blank line. A record key may have a multi-line value
191 if the line starts with whitespace character.
192
193 Returns a list of subsequent values as instances BaseRFC822Record class. If
194 the optional data_cls argument is collections.OrderedDict then the values
195 retain their original ordering.
196 """
197 record = None
198 data = None
199 key = None
200 value_list = None
201
202 def _syntax_error(msg):
203 """
204 Report a syntax error in the current line
205 """
206 try:
207 filename = stream.name
208 except AttributeError:
209 filename = None
210 return RFC822SyntaxError(filename, lineno, msg)
211
212 def _new_record():
213 """
214 Reset local state to track new record
215 """
216 nonlocal key
217 nonlocal value_list
218 nonlocal record
219 nonlocal data
220 key = None
221 value_list = None
222 data = data_cls()
223 record = BaseRFC822Record(data)
224
225 def _commit_key_value_if_needed():
226 """
227 Finalize the most recently seen key: value pair
228 """
229 nonlocal key
230 if key is not None:
231 data[key] = cleandoc('\n'.join(value_list))
232 key = None
233
234 # Start with an empty record
235 _new_record()
236 # Iterate over subsequent lines of the stream
237 for lineno, line in enumerate(stream, start=1):
238 # Treat empty lines as record separators
239 if line.strip() == "":
240 # Commit the current record so that the multi-line value of the
241 # last key, if any, is saved as a string
242 _commit_key_value_if_needed()
243 # If data is non-empty, yield the record, this allows us to safely
244 # use newlines for formatting
245 if data:
246 yield record
247 # Reset local state so that we can build a new record
248 _new_record()
249 # Treat lines staring with whitespace as multi-line continuation of the
250 # most recently seen key-value
251 elif line.startswith(" "):
252 if key is None:
253 # If we have not seen any keys yet then this is a syntax error
254 raise _syntax_error("Unexpected multi-line value")
255 # Append the current line to the list of values of the most recent
256 # key. This prevents quadratic complexity of string concatenation
257 if line == " .\n":
258 value_list.append(" ")
259 elif line == " ..\n":
260 value_list.append(" .")
261 else:
262 value_list.append(line.rstrip())
263 # Treat lines with a colon as new key-value pairs
264 elif ":" in line:
265 # Since we have a new, key-value pair we need to commit any
266 # previous key that we may have (regardless of multi-line or
267 # single-line values).
268 _commit_key_value_if_needed()
269 # Parse the line by splitting on the colon, get rid of additional
270 # whitespace from both key and the value
271 key, value = line.split(":", 1)
272 key = key.strip()
273 value = value.strip()
274 # Check if the key already exist in this message
275 if key in record.data:
276 raise _syntax_error((
277 "Job has a duplicate key {!r} "
278 "with old value {!r} and new value {!r}").format(
279 key, record.data[key], value))
280 # Construct initial value list out of the (only) value that we have
281 # so far. Additional multi-line values will just append to
282 # value_list
283 value_list = [value]
284 # Treat all other lines as syntax errors
285 else:
286 raise _syntax_error("Unexpected non-empty line")
287 # Make sure to commit the last key from the record
288 _commit_key_value_if_needed()
289 # Once we've seen the whole file return the last record, if any
290 if data:
291 yield record
292
293
294class Runner:
295 """
296 Runner for jobs
297
298 Executes the command process and pipes back stdout/stderr
299 """
300
301 CHECKBOXES = "/usr/share/checkbox*"
302
303 def __init__(self, builtin_jobs=[], packages=[]):
304 # List of all available jobs in system-wide locations
305 self.builtin_jobs = builtin_jobs
306 # List of all checkbox variants, like checkbox-oem(-.*)?
307 self.packages = packages
308
309 def path_expand(self, path):
310 for p in glob.glob(path):
311 self.packages.append(p)
312 for dirpath, dirs, filenames in os.walk(os.path.join(p, 'jobs')):
313 for name in filenames:
314 if name.endswith(".txt"):
315 yield os.path.join(dirpath, name)
316
317 def main(self, argv=None):
318 parser = argparse.ArgumentParser(prog="checkbox-trusted-launcher")
319 group = parser.add_mutually_exclusive_group(required=True)
320 group.add_argument('--hash', metavar='HASH', help='job hash to match')
321 group.add_argument(
322 '--warmup',
323 action='store_true',
324 help='Return immediately, only useful when used with pkexec(1)')
325 parser.add_argument(
326 '--via',
327 metavar='LOCAL-JOB-HASH',
328 dest='via_hash',
329 help='Local job hash to use to match the generated job')
330 parser.add_argument(
331 'ENV', metavar='NAME=VALUE', nargs='*',
332 help='Set each NAME to VALUE in the string environment')
333 args = parser.parse_args(argv)
334
335 if args.warmup:
336 return 0
337
338 for filename in self.path_expand(self.CHECKBOXES):
339 stream = open(filename, "r", encoding="utf-8")
340 for message in load_rfc822_records(stream):
341 self.builtin_jobs.append(BaseJob(message.data))
342 stream.close()
343 lookup_list = [j for j in self.builtin_jobs if j.user]
344
345 args.ENV = dict(item.split('=') for item in args.ENV)
346
347 if args.via_hash is not None:
348 local_list = [j for j in self.builtin_jobs if j.plugin == 'local']
349 desired_job_list = [j for j in local_list
350 if j.get_checksum() == args.via_hash]
351 if desired_job_list:
352 via_job = desired_job_list.pop()
353 via_job_result = subprocess.Popen(
354 via_job.command,
355 shell=True,
356 universal_newlines=True,
357 stdout=subprocess.PIPE,
358 env=via_job.modify_execution_environment(
359 args.ENV,
360 self.packages)
361 )
362 try:
363 for message in load_rfc822_records(via_job_result.stdout):
364 lookup_list.append(BaseJob(message.data))
365 finally:
366 # Always call Popen.wait() in order to avoid zombies
367 via_job_result.stdout.close()
368 via_job_result.wait()
369
370 try:
371 target_job = [j for j in lookup_list
372 if j.get_checksum() == args.hash][0]
373 except IndexError:
374 return "Job not found"
375 try:
376 os.execve(
377 '/bin/bash',
378 ['bash', '-c', target_job.command],
379 target_job.modify_execution_environment(
380 args.ENV,
381 self.packages)
382 )
383 # if execve doesn't fail, it never returns...
384 except OSError:
385 return "Fatal error"
386 finally:
387 return "Fatal error"
388
389
390def main(argv=None):
391 """
392 Entry point for the checkbox trusted launcher
393 """
394 runner = Runner()
395 raise SystemExit(runner.main(argv))
0396
=== added file 'plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER'
--- plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER 1970-01-01 00:00:00 +0000
+++ plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER 2013-08-28 12:36:13 +0000
@@ -0,0 +1,280 @@
1# This file is part of Checkbox.
2#
3# Copyright 2013 Canonical Ltd.
4# Written by:
5# Sylvain Pineau <sylvain.pineau@canonical.com>
6#
7# Checkbox is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Checkbox is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
20"""
21plainbox.impl.secure.test_checkbox_trusted_launcher
22===================================================
23
24Test definitions for plainbox.impl.secure.checkbox_trusted_launcher module
25"""
26
27import os
28
29from inspect import cleandoc
30from io import StringIO
31from mock import Mock, patch
32from tempfile import NamedTemporaryFile, TemporaryDirectory
33from unittest import TestCase
34
35from plainbox.impl.secure.checkbox_trusted_launcher import BaseJob
36from plainbox.impl.secure.checkbox_trusted_launcher import load_rfc822_records
37from plainbox.impl.secure.checkbox_trusted_launcher import main
38from plainbox.impl.secure.checkbox_trusted_launcher import Runner
39from plainbox.impl.test_rfc822 import RFC822ParserTestsMixIn
40from plainbox.testing_utils.io import TestIO
41from plainbox.testing_utils.testcases import TestCaseWithParameters
42
43
44class TestJobDefinition(TestCase):
45
46 def setUp(self):
47 self._full_record = {
48 'plugin': 'plugin',
49 'command': 'command',
50 'environ': 'environ',
51 'user': 'user'
52 }
53 self._min_record = {
54 'plugin': 'plugin',
55 'name': 'name',
56 }
57
58 def test_smoke_full_record(self):
59 job = BaseJob(self._full_record)
60 self.assertEqual(job.plugin, "plugin")
61 self.assertEqual(job.command, "command")
62 self.assertEqual(job.environ, "environ")
63 self.assertEqual(job.user, "user")
64
65 def test_smoke_min_record(self):
66 job = BaseJob(self._min_record)
67 self.assertEqual(job.plugin, "plugin")
68 self.assertEqual(job.command, None)
69 self.assertEqual(job.environ, None)
70 self.assertEqual(job.user, None)
71
72 def test_checksum_smoke(self):
73 job1 = BaseJob({'plugin': 'plugin', 'user': 'root'})
74 identical_to_job1 = BaseJob({'plugin': 'plugin', 'user': 'root'})
75 # Two distinct but identical jobs have the same checksum
76 self.assertEqual(job1.get_checksum(), identical_to_job1.get_checksum())
77 job2 = BaseJob({'plugin': 'plugin', 'user': 'anonymous'})
78 # Two jobs with different definitions have different checksum
79 self.assertNotEqual(job1.get_checksum(), job2.get_checksum())
80 # The checksum is stable and does not change over time
81 self.assertEqual(
82 job1.get_checksum(),
83 "c47cc3719061e4df0010d061e6f20d3d046071fd467d02d093a03068d2f33400")
84
85
86class ParsingTests(TestCaseWithParameters):
87
88 parameter_names = ('glue',)
89 parameter_values = (
90 ('commas',),
91 ('spaces',),
92 ('tabs',),
93 ('newlines',),
94 ('spaces_and_commas',),
95 ('multiple_spaces',),
96 ('multiple_commas',)
97 )
98 parameters_keymap = {
99 'commas': ',',
100 'spaces': ' ',
101 'tabs': '\t',
102 'newlines': '\n',
103 'spaces_and_commas': ', ',
104 'multiple_spaces': ' ',
105 'multiple_commas': ',,,,'
106 }
107
108 def test_environ_parsing_with_various_separators(self):
109 job = BaseJob({
110 'name': 'name',
111 'plugin': 'plugin',
112 'environ': self.parameters_keymap[
113 self.parameters.glue].join(['foo', 'bar', 'froz'])})
114 expected = set({'foo', 'bar', 'froz'})
115 observed = job.get_environ_settings()
116 self.assertEqual(expected, observed)
117
118 def test_environ_parsing_empty(self):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: