Merge lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-201308281227 into lp:ubuntu/saucy/checkbox

Proposed by Ubuntu Package Importer
Status: Needs review
Proposed branch: lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-201308281227
Merge into: lp:ubuntu/saucy/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-201308281227
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+182623@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/checkbox reflect what is in the archive and the old bzr branch has been pushed to lp:~ubuntu-branches/ubuntu/saucy/checkbox/saucy-201308281227. 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
* 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)
* 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
* Ensured that button strings from the "continue" dialog are translatable
  (LP: #1176695)

* 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)
* 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
1=== modified file 'checkbox/parsers/cpuinfo.py'
2=== modified file 'debian/changelog'
3--- debian/changelog 2013-08-02 10:56:56 +0000
4+++ debian/changelog 2013-08-28 12:37:27 +0000
5@@ -1,3 +1,4 @@
6+<<<<<<< TREE
7 checkbox (0.16.4) saucy; urgency=low
8
9 [ Jeff Lane ]
10@@ -224,6 +225,69 @@
11
12 -- Daniel Manrique <roadmr@ubuntu.com> Wed, 15 May 2013 16:39:12 -0400
13
14+=======
15+checkbox (0.16.1) saucy; urgency=low
16+
17+ * New upstream release (LP: #1180545):
18+
19+ * Launchpad automated translation updates
20+
21+ [ Alberto Milone ]
22+ * scripts/graphics_stress_test, scripts/rotation_test: make sure to
23+ always reset the "screen" variable. Somehow the NVIDIA driver manages
24+ to make it unusable after the first time. (LP: #1172667)
25+
26+ [ Brendan Donegan ]
27+ * checkbox/parsers/submission.py - publish kernel-release information to
28+ interested parties.
29+ * scripts/rendercheck_test - change nargs='+' to action='append' for blacklist
30+ option so it works as expected.
31+ jobs/rendercheck.txt.in - blacklist gradients test as it is known to produce
32+ false positives. (LP: #1093718)
33+ * plugins/hexr_transport.py - added plugin for submitting to HEXR and
34+ certification based on certify_new_transport from checkbox-certification.
35+ examples/checkbox-qt.ini - blacklisted hexr_transport as we won't use it
36+ examples/checkbox-cli.ini - blacklisted hexr_transport as we won't use it
37+ examples/checkbox-urwid.ini - blacklisted hexr_transport as we won't use it
38+
39+ [ Daniel Manrique ]
40+ * Ensured that button strings from the "continue" dialog are translatable
41+ (LP: #1176695)
42+
43+ [ Jeff Lane ]
44+ * checkbox/parsers/cpuinfo.py - split on first instance of ':' in cpuinfo
45+ output lines to avoid splitting into more than 2 items. Also fixed a pep8
46+ issue discovered while working on this. (LP: #1180496)
47+ * scripts/cpu_offlining: Modified script to no longer offline cpu0 to resolve
48+ a bug on ARM. Modified output so most of it is redirected to stderr for
49+ fail cases, we don't need that much for success cases. (LP: #1078897)
50+ * jobs/mediacard.txt.in: Modified test instructions to be less confusing
51+ (LP: #970857)
52+ * scripts/cpu_topology: define the cpuinfo nested dicts on creation rather
53+ than define elements during parsing of /proc/cpuinfo (LP: #1111878)
54+ * scripts/lsmod_info: Corrected error handling for the check_output() call to
55+ trap the correct error. (LP: #1103647)
56+ * jobs/camera.txt.in: removed an extraneous requres line for gir1.2
57+ scripts/camera_test: added code to determine what version of gst we're
58+ using and set video type and plugin accordingly. (LP: #1100594)
59+ * scripts/network_check: added ability to specify custom target URL for
60+ debugging failures (LP: #1128017)
61+ * scripts/removable_storage_test: Added error handling to trap OSError on
62+ non-writable media and modified output to handle subsequent
63+ ZeroDivisionError issues when summarizing test results.
64+ jobs/media.txt.in: Modified instructions for SD/SDHC to specify using
65+ UNLOCKED cards to avoid issues when testing read-only media (LP: #1153894)
66+
67+ [ Sylvain Pineau ]
68+ * jobs/suspend.txt.in, scripts/gpu_test: Remove the need of running the script
69+ with the root user, restore the workspaces switch and the HTML5 video
70+ playback ; remove the extra suspend/resume (LP: #1172851)
71+ * checkbox/parsers/udevadm.py: Only filter devices without product AND vendor
72+ information (LP: #1167733)
73+
74+ -- Daniel Manrique <roadmr@ubuntu.com> Wed, 15 May 2013 16:39:12 -0400
75+
76+>>>>>>> MERGE-SOURCE
77 checkbox (0.16) saucy; urgency=low
78
79 * New upstream release (LP: #1178403):
80
81=== modified file 'debian/po/ast.po'
82--- debian/po/ast.po 2013-08-02 10:56:56 +0000
83+++ debian/po/ast.po 2013-08-28 12:37:27 +0000
84@@ -15,8 +15,13 @@
85 "MIME-Version: 1.0\n"
86 "Content-Type: text/plain; charset=UTF-8\n"
87 "Content-Transfer-Encoding: 8bit\n"
88+<<<<<<< TREE
89 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
90 "X-Generator: Launchpad (build 16700)\n"
91+=======
92+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
93+"X-Generator: Launchpad (build 16598)\n"
94+>>>>>>> MERGE-SOURCE
95
96 #. Type: string
97 #. Description
98
99=== modified file 'debian/po/cs.po'
100--- debian/po/cs.po 2013-08-02 10:56:56 +0000
101+++ debian/po/cs.po 2013-08-28 12:37:27 +0000
102@@ -15,8 +15,13 @@
103 "MIME-Version: 1.0\n"
104 "Content-Type: text/plain; charset=UTF-8\n"
105 "Content-Transfer-Encoding: 8bit\n"
106+<<<<<<< TREE
107 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
108 "X-Generator: Launchpad (build 16700)\n"
109+=======
110+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
111+"X-Generator: Launchpad (build 16598)\n"
112+>>>>>>> MERGE-SOURCE
113
114 #. Type: string
115 #. Description
116
117=== modified file 'debian/po/de.po'
118--- debian/po/de.po 2013-08-02 10:56:56 +0000
119+++ debian/po/de.po 2013-08-28 12:37:27 +0000
120@@ -15,8 +15,13 @@
121 "MIME-Version: 1.0\n"
122 "Content-Type: text/plain; charset=UTF-8\n"
123 "Content-Transfer-Encoding: 8bit\n"
124+<<<<<<< TREE
125 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
126 "X-Generator: Launchpad (build 16700)\n"
127+=======
128+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
129+"X-Generator: Launchpad (build 16598)\n"
130+>>>>>>> MERGE-SOURCE
131
132 #. Type: string
133 #. Description
134
135=== modified file 'debian/po/en_AU.po'
136--- debian/po/en_AU.po 2013-08-02 10:56:56 +0000
137+++ debian/po/en_AU.po 2013-08-28 12:37:27 +0000
138@@ -15,8 +15,13 @@
139 "MIME-Version: 1.0\n"
140 "Content-Type: text/plain; charset=UTF-8\n"
141 "Content-Transfer-Encoding: 8bit\n"
142+<<<<<<< TREE
143 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
144 "X-Generator: Launchpad (build 16700)\n"
145+=======
146+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
147+"X-Generator: Launchpad (build 16598)\n"
148+>>>>>>> MERGE-SOURCE
149
150 #. Type: string
151 #. Description
152
153=== modified file 'debian/po/en_GB.po'
154--- debian/po/en_GB.po 2013-08-02 10:56:56 +0000
155+++ debian/po/en_GB.po 2013-08-28 12:37:27 +0000
156@@ -15,8 +15,13 @@
157 "MIME-Version: 1.0\n"
158 "Content-Type: text/plain; charset=UTF-8\n"
159 "Content-Transfer-Encoding: 8bit\n"
160+<<<<<<< TREE
161 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
162 "X-Generator: Launchpad (build 16700)\n"
163+=======
164+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
165+"X-Generator: Launchpad (build 16598)\n"
166+>>>>>>> MERGE-SOURCE
167
168 #. Type: string
169 #. Description
170
171=== modified file 'debian/po/es.po'
172--- debian/po/es.po 2013-08-02 10:56:56 +0000
173+++ debian/po/es.po 2013-08-28 12:37:27 +0000
174@@ -15,8 +15,13 @@
175 "MIME-Version: 1.0\n"
176 "Content-Type: text/plain; charset=UTF-8\n"
177 "Content-Transfer-Encoding: 8bit\n"
178+<<<<<<< TREE
179 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
180 "X-Generator: Launchpad (build 16700)\n"
181+=======
182+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
183+"X-Generator: Launchpad (build 16598)\n"
184+>>>>>>> MERGE-SOURCE
185
186 #. Type: string
187 #. Description
188
189=== modified file 'debian/po/fr.po'
190--- debian/po/fr.po 2013-08-02 10:56:56 +0000
191+++ debian/po/fr.po 2013-08-28 12:37:27 +0000
192@@ -15,8 +15,13 @@
193 "MIME-Version: 1.0\n"
194 "Content-Type: text/plain; charset=UTF-8\n"
195 "Content-Transfer-Encoding: 8bit\n"
196+<<<<<<< TREE
197 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
198 "X-Generator: Launchpad (build 16700)\n"
199+=======
200+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
201+"X-Generator: Launchpad (build 16598)\n"
202+>>>>>>> MERGE-SOURCE
203
204 #. Type: string
205 #. Description
206
207=== modified file 'debian/po/gl.po'
208--- debian/po/gl.po 2013-08-02 10:56:56 +0000
209+++ debian/po/gl.po 2013-08-28 12:37:27 +0000
210@@ -15,8 +15,13 @@
211 "MIME-Version: 1.0\n"
212 "Content-Type: text/plain; charset=UTF-8\n"
213 "Content-Transfer-Encoding: 8bit\n"
214+<<<<<<< TREE
215 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
216 "X-Generator: Launchpad (build 16700)\n"
217+=======
218+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
219+"X-Generator: Launchpad (build 16598)\n"
220+>>>>>>> MERGE-SOURCE
221
222 #. Type: string
223 #. Description
224
225=== modified file 'debian/po/he.po'
226--- debian/po/he.po 2013-08-02 10:56:56 +0000
227+++ debian/po/he.po 2013-08-28 12:37:27 +0000
228@@ -15,8 +15,13 @@
229 "MIME-Version: 1.0\n"
230 "Content-Type: text/plain; charset=UTF-8\n"
231 "Content-Transfer-Encoding: 8bit\n"
232+<<<<<<< TREE
233 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
234 "X-Generator: Launchpad (build 16700)\n"
235+=======
236+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
237+"X-Generator: Launchpad (build 16598)\n"
238+>>>>>>> MERGE-SOURCE
239
240 #. Type: string
241 #. Description
242
243=== modified file 'debian/po/hu.po'
244--- debian/po/hu.po 2013-08-02 10:56:56 +0000
245+++ debian/po/hu.po 2013-08-28 12:37:27 +0000
246@@ -15,8 +15,13 @@
247 "MIME-Version: 1.0\n"
248 "Content-Type: text/plain; charset=UTF-8\n"
249 "Content-Transfer-Encoding: 8bit\n"
250+<<<<<<< TREE
251 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
252 "X-Generator: Launchpad (build 16700)\n"
253+=======
254+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
255+"X-Generator: Launchpad (build 16598)\n"
256+>>>>>>> MERGE-SOURCE
257
258 #. Type: string
259 #. Description
260
261=== modified file 'debian/po/id.po'
262--- debian/po/id.po 2013-08-02 10:56:56 +0000
263+++ debian/po/id.po 2013-08-28 12:37:27 +0000
264@@ -15,8 +15,13 @@
265 "MIME-Version: 1.0\n"
266 "Content-Type: text/plain; charset=UTF-8\n"
267 "Content-Transfer-Encoding: 8bit\n"
268+<<<<<<< TREE
269 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
270 "X-Generator: Launchpad (build 16700)\n"
271+=======
272+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
273+"X-Generator: Launchpad (build 16598)\n"
274+>>>>>>> MERGE-SOURCE
275
276 #. Type: string
277 #. Description
278
279=== modified file 'debian/po/it.po'
280--- debian/po/it.po 2013-08-02 10:56:56 +0000
281+++ debian/po/it.po 2013-08-28 12:37:27 +0000
282@@ -15,8 +15,13 @@
283 "MIME-Version: 1.0\n"
284 "Content-Type: text/plain; charset=UTF-8\n"
285 "Content-Transfer-Encoding: 8bit\n"
286+<<<<<<< TREE
287 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
288 "X-Generator: Launchpad (build 16700)\n"
289+=======
290+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
291+"X-Generator: Launchpad (build 16598)\n"
292+>>>>>>> MERGE-SOURCE
293
294 #. Type: string
295 #. Description
296
297=== modified file 'debian/po/ja.po'
298--- debian/po/ja.po 2013-08-02 10:56:56 +0000
299+++ debian/po/ja.po 2013-08-28 12:37:27 +0000
300@@ -15,8 +15,13 @@
301 "MIME-Version: 1.0\n"
302 "Content-Type: text/plain; charset=UTF-8\n"
303 "Content-Transfer-Encoding: 8bit\n"
304+<<<<<<< TREE
305 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
306 "X-Generator: Launchpad (build 16700)\n"
307+=======
308+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
309+"X-Generator: Launchpad (build 16598)\n"
310+>>>>>>> MERGE-SOURCE
311
312 #. Type: string
313 #. Description
314
315=== modified file 'debian/po/nl.po'
316--- debian/po/nl.po 2013-08-02 10:56:56 +0000
317+++ debian/po/nl.po 2013-08-28 12:37:27 +0000
318@@ -15,8 +15,13 @@
319 "MIME-Version: 1.0\n"
320 "Content-Type: text/plain; charset=UTF-8\n"
321 "Content-Transfer-Encoding: 8bit\n"
322+<<<<<<< TREE
323 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
324 "X-Generator: Launchpad (build 16700)\n"
325+=======
326+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
327+"X-Generator: Launchpad (build 16598)\n"
328+>>>>>>> MERGE-SOURCE
329
330 #. Type: string
331 #. Description
332
333=== modified file 'debian/po/oc.po'
334--- debian/po/oc.po 2013-08-02 10:56:56 +0000
335+++ debian/po/oc.po 2013-08-28 12:37:27 +0000
336@@ -15,8 +15,13 @@
337 "MIME-Version: 1.0\n"
338 "Content-Type: text/plain; charset=UTF-8\n"
339 "Content-Transfer-Encoding: 8bit\n"
340+<<<<<<< TREE
341 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
342 "X-Generator: Launchpad (build 16700)\n"
343+=======
344+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
345+"X-Generator: Launchpad (build 16598)\n"
346+>>>>>>> MERGE-SOURCE
347
348 #. Type: string
349 #. Description
350
351=== modified file 'debian/po/pl.po'
352--- debian/po/pl.po 2013-08-02 10:56:56 +0000
353+++ debian/po/pl.po 2013-08-28 12:37:27 +0000
354@@ -15,8 +15,13 @@
355 "MIME-Version: 1.0\n"
356 "Content-Type: text/plain; charset=UTF-8\n"
357 "Content-Transfer-Encoding: 8bit\n"
358+<<<<<<< TREE
359 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
360 "X-Generator: Launchpad (build 16700)\n"
361+=======
362+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
363+"X-Generator: Launchpad (build 16598)\n"
364+>>>>>>> MERGE-SOURCE
365
366 #. Type: string
367 #. Description
368
369=== modified file 'debian/po/pt_BR.po'
370--- debian/po/pt_BR.po 2013-08-02 10:56:56 +0000
371+++ debian/po/pt_BR.po 2013-08-28 12:37:27 +0000
372@@ -15,8 +15,13 @@
373 "MIME-Version: 1.0\n"
374 "Content-Type: text/plain; charset=UTF-8\n"
375 "Content-Transfer-Encoding: 8bit\n"
376+<<<<<<< TREE
377 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
378 "X-Generator: Launchpad (build 16700)\n"
379+=======
380+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
381+"X-Generator: Launchpad (build 16598)\n"
382+>>>>>>> MERGE-SOURCE
383
384 #. Type: string
385 #. Description
386
387=== modified file 'debian/po/ro.po'
388--- debian/po/ro.po 2013-08-02 10:56:56 +0000
389+++ debian/po/ro.po 2013-08-28 12:37:27 +0000
390@@ -15,8 +15,13 @@
391 "MIME-Version: 1.0\n"
392 "Content-Type: text/plain; charset=UTF-8\n"
393 "Content-Transfer-Encoding: 8bit\n"
394+<<<<<<< TREE
395 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
396 "X-Generator: Launchpad (build 16700)\n"
397+=======
398+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
399+"X-Generator: Launchpad (build 16598)\n"
400+>>>>>>> MERGE-SOURCE
401
402 #. Type: string
403 #. Description
404
405=== modified file 'debian/po/ru.po'
406--- debian/po/ru.po 2013-08-02 10:56:56 +0000
407+++ debian/po/ru.po 2013-08-28 12:37:27 +0000
408@@ -15,8 +15,13 @@
409 "MIME-Version: 1.0\n"
410 "Content-Type: text/plain; charset=UTF-8\n"
411 "Content-Transfer-Encoding: 8bit\n"
412+<<<<<<< TREE
413 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
414 "X-Generator: Launchpad (build 16700)\n"
415+=======
416+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
417+"X-Generator: Launchpad (build 16598)\n"
418+>>>>>>> MERGE-SOURCE
419
420 #. Type: string
421 #. Description
422
423=== modified file 'debian/po/tr.po'
424--- debian/po/tr.po 2013-08-02 10:56:56 +0000
425+++ debian/po/tr.po 2013-08-28 12:37:27 +0000
426@@ -15,8 +15,13 @@
427 "MIME-Version: 1.0\n"
428 "Content-Type: text/plain; charset=UTF-8\n"
429 "Content-Transfer-Encoding: 8bit\n"
430+<<<<<<< TREE
431 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
432 "X-Generator: Launchpad (build 16700)\n"
433+=======
434+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
435+"X-Generator: Launchpad (build 16598)\n"
436+>>>>>>> MERGE-SOURCE
437
438 #. Type: string
439 #. Description
440
441=== modified file 'debian/po/uk.po'
442--- debian/po/uk.po 2013-08-02 10:56:56 +0000
443+++ debian/po/uk.po 2013-08-28 12:37:27 +0000
444@@ -15,8 +15,13 @@
445 "MIME-Version: 1.0\n"
446 "Content-Type: text/plain; charset=UTF-8\n"
447 "Content-Transfer-Encoding: 8bit\n"
448+<<<<<<< TREE
449 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
450 "X-Generator: Launchpad (build 16700)\n"
451+=======
452+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
453+"X-Generator: Launchpad (build 16598)\n"
454+>>>>>>> MERGE-SOURCE
455
456 #. Type: string
457 #. Description
458
459=== modified file 'debian/po/zh_CN.po'
460--- debian/po/zh_CN.po 2013-08-02 10:56:56 +0000
461+++ debian/po/zh_CN.po 2013-08-28 12:37:27 +0000
462@@ -15,8 +15,13 @@
463 "MIME-Version: 1.0\n"
464 "Content-Type: text/plain; charset=UTF-8\n"
465 "Content-Transfer-Encoding: 8bit\n"
466+<<<<<<< TREE
467 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
468 "X-Generator: Launchpad (build 16700)\n"
469+=======
470+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
471+"X-Generator: Launchpad (build 16598)\n"
472+>>>>>>> MERGE-SOURCE
473
474 #. Type: string
475 #. Description
476
477=== modified file 'debian/po/zh_TW.po'
478--- debian/po/zh_TW.po 2013-08-02 10:56:56 +0000
479+++ debian/po/zh_TW.po 2013-08-28 12:37:27 +0000
480@@ -15,8 +15,13 @@
481 "MIME-Version: 1.0\n"
482 "Content-Type: text/plain; charset=UTF-8\n"
483 "Content-Transfer-Encoding: 8bit\n"
484+<<<<<<< TREE
485 "X-Launchpad-Export-Date: 2013-07-24 05:13+0000\n"
486 "X-Generator: Launchpad (build 16700)\n"
487+=======
488+"X-Launchpad-Export-Date: 2013-05-09 05:18+0000\n"
489+"X-Generator: Launchpad (build 16598)\n"
490+>>>>>>> MERGE-SOURCE
491
492 #. Type: string
493 #. Description
494
495=== modified file 'jobs/graphics.txt.in'
496=== modified file 'jobs/mediacard.txt.in'
497--- jobs/mediacard.txt.in 2013-08-02 10:56:56 +0000
498+++ jobs/mediacard.txt.in 2013-08-28 12:37:27 +0000
499@@ -78,6 +78,52 @@
500 The verification of this test is automated. Do not change the
501 automatically selected result.
502
503+<<<<<<< TREE
504+=======
505+plugin: user-interact
506+name: mediacard/sd-insert-after-suspend
507+depends: suspend/suspend_advanced
508+command: removable_storage_watcher --memorycard insert sdio usb scsi
509+_description:
510+ PURPOSE:
511+ This test will check that the systems media card reader can
512+ detect the insertion of an UNLOCKED SD card after the system
513+ has been suspended
514+ STEPS:
515+ 1. Click "Test" and insert an UNLOCKED SD card into the reader.
516+ If a file browser opens up, you can safely close it.
517+ (Note: this test will time-out after 20 seconds.)
518+ 2. Do not remove the device after this test.
519+ VERIFICATION:
520+ The verification of this test is automated. Do not change the
521+ automatically selected result.
522+
523+plugin: shell
524+name: mediacard/sd-storage-after-suspend
525+depends: mediacard/sd-insert-after-suspend
526+user: root
527+command: removable_storage_test -s 268400000 --memorycard sdio usb scsi
528+_description:
529+ This test is automated and executes after the mediacard/sd-insert-after-suspend test
530+ is run. It tests reading and writing to the SD card after the system has been suspended.
531+
532+plugin: user-interact
533+name: mediacard/sd-remove-after-suspend
534+depends: mediacard/sd-insert-after-suspend
535+command: removable_storage_watcher --memorycard remove sdio usb scsi
536+_description:
537+ PURPOSE:
538+ This test will check that the system correctly detects
539+ the removal of an SD card from the systems card reader
540+ after the system has been suspended.
541+ STEPS:
542+ 1. Click "Test" and remove the SD card from the reader.
543+ (Note: this test will time-out after 20 seconds.)
544+ VERIFICATION:
545+ The verification of this test is automated. Do not change the
546+ automatically selected result.
547+
548+>>>>>>> MERGE-SOURCE
549 plugin: shell
550 name: mediacard/sd-preinserted
551 user: root
552@@ -130,6 +176,51 @@
553 automatically selected result.
554
555 plugin: user-interact
556+<<<<<<< TREE
557+=======
558+name: mediacard/sdhc-insert-after-suspend
559+depends: suspend/suspend_advanced
560+command: removable_storage_watcher --memorycard insert sdio usb scsi
561+_description:
562+ PURPOSE:
563+ This test will check that the systems media card reader can
564+ detect the insertion of an UNLOCKED SDHC media card after the
565+ system has been suspended
566+ STEPS:
567+ 1. Click "Test" and insert an UNLOCKED SDHC card into the reader.
568+ If a file browser opens up, you can safely close it.
569+ (Note: this test will time-out after 20 seconds.)
570+ 2. Do not remove the device after this test.
571+ VERIFICATION:
572+ The verification of this test is automated. Do not change the
573+ automatically selected result.
574+
575+plugin: shell
576+name: mediacard/sdhc-storage-after-suspend
577+depends: mediacard/sdhc-insert-after-suspend
578+user: root
579+command: removable_storage_test -s 268400000 --memorycard sdio usb scsi
580+_description:
581+ This test is automated and executes after the mediacard/sdhc-insert-after-suspend test
582+ is run. It tests reading and writing to the SDHC card after the system has been suspended.
583+
584+plugin: user-interact
585+name: mediacard/sdhc-remove-after-suspend
586+depends: mediacard/sdhc-insert-after-suspend
587+command: removable_storage_watcher --memorycard remove sdio usb scsi
588+_description:
589+ PURPOSE:
590+ This test will check that the system correctly detects the removal
591+ of an SDHC card from the systems card reader after the system has been suspended.
592+ STEPS:
593+ 1. Click "Test" and remove the SDHC card from the reader.
594+ (Note: this test will time-out after 20 seconds.)
595+ VERIFICATION:
596+ The verification of this test is automated. Do not change the
597+ automatically selected result.
598+
599+plugin: user-interact
600+>>>>>>> MERGE-SOURCE
601 name: mediacard/cf-insert
602 command: removable_storage_watcher --memorycard insert sdio usb scsi
603 _description:
604
605=== modified file 'jobs/resource.txt.in'
606--- jobs/resource.txt.in 2013-07-12 14:51:00 +0000
607+++ jobs/resource.txt.in 2013-08-28 12:37:27 +0000
608@@ -56,6 +56,12 @@
609 command:
610 find -H $(echo "$PATH" | sed -e 's/:/ /g') -maxdepth 1 -type f -executable -printf "name: %f\n\n"
611
612+name: executable
613+plugin: resource
614+description: Generates a resource for all available executables
615+command:
616+ find -H $(echo "$PATH" | sed -e 's/:/ /g') -maxdepth 1 -type f -executable -printf "name: %f\n\n"
617+
618 name: device
619 plugin: resource
620 command: udev_resource
621
622=== added directory 'plainbox'
623=== added file 'plainbox/MANIFEST.in.OTHER'
624--- plainbox/MANIFEST.in.OTHER 1970-01-01 00:00:00 +0000
625+++ plainbox/MANIFEST.in.OTHER 2013-08-28 12:37:27 +0000
626@@ -0,0 +1,9 @@
627+include README.md
628+include COPYING
629+include mk-interesting-graphs.sh
630+recursive-include plainbox/test-data/ *.json *.xml *.txt
631+recursive-include docs *.rst
632+include docs/conf.py
633+include plainbox/data/report/hardware-1_0.rng
634+include contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy
635+include contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy
636
637=== added directory 'plainbox/contrib'
638=== added directory 'plainbox/contrib/policykit_auth_admin_keep'
639=== added file 'plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy'
640--- plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy 1970-01-01 00:00:00 +0000
641+++ plainbox/contrib/policykit_auth_admin_keep/org.freedesktop.policykit.pkexec.policy 2013-08-28 12:37:27 +0000
642@@ -0,0 +1,30 @@
643+<?xml version="1.0" encoding="UTF-8"?>
644+<!DOCTYPE policyconfig PUBLIC
645+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
646+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
647+<policyconfig>
648+
649+ <!--
650+ Policy definitions for PlainBox system actions.
651+ (C) 2013 Canonical Ltd.
652+ Author: Sylvain Pineau <sylvain.pineau@canonical.com>
653+ -->
654+
655+ <vendor>PlainBox</vendor>
656+ <vendor_url>https://launchpad.net/checkbox</vendor_url>
657+ <icon_name>checkbox</icon_name>
658+
659+ <action id="org.freedesktop.policykit.pkexec.run-plainbox-job">
660+ <description>Run Job command</description>
661+ <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>
662+ <defaults>
663+ <allow_any>no</allow_any>
664+ <allow_inactive>no</allow_inactive>
665+ <allow_active>auth_admin_keep</allow_active>
666+ </defaults>
667+ <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/checkbox-trusted-launcher</annotate>
668+ <annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
669+ </action>
670+
671+</policyconfig>
672+
673
674=== added directory 'plainbox/contrib/policykit_yes'
675=== added file 'plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER'
676--- plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER 1970-01-01 00:00:00 +0000
677+++ plainbox/contrib/policykit_yes/org.freedesktop.policykit.pkexec.policy.OTHER 2013-08-28 12:37:27 +0000
678@@ -0,0 +1,29 @@
679+<?xml version="1.0" encoding="UTF-8"?>
680+<!DOCTYPE policyconfig PUBLIC
681+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
682+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
683+<policyconfig>
684+
685+ <!--
686+ Policy definitions for PlainBox system actions.
687+ (C) 2013 Canonical Ltd.
688+ Author: Sylvain Pineau <sylvain.pineau@canonical.com>
689+ -->
690+
691+ <vendor>PlainBox</vendor>
692+ <vendor_url>https://launchpad.net/checkbox</vendor_url>
693+ <icon_name>checkbox</icon_name>
694+
695+ <action id="org.freedesktop.policykit.pkexec.run-plainbox-job">
696+ <description>Run Job command</description>
697+ <defaults>
698+ <allow_any>no</allow_any>
699+ <allow_inactive>no</allow_inactive>
700+ <allow_active>yes</allow_active>
701+ </defaults>
702+ <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/checkbox-trusted-launcher</annotate>
703+ <annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
704+ </action>
705+
706+</policyconfig>
707+
708
709=== added directory 'plainbox/docs'
710=== added directory 'plainbox/docs/dev'
711=== added file 'plainbox/docs/dev/architecture.rst.OTHER'
712--- plainbox/docs/dev/architecture.rst.OTHER 1970-01-01 00:00:00 +0000
713+++ plainbox/docs/dev/architecture.rst.OTHER 2013-08-28 12:37:27 +0000
714@@ -0,0 +1,40 @@
715+PlainBox Architecture
716+=====================
717+
718+This document explains the architecture of PlainBox internals. It should be
719+always up-to-date and accurate to the extent of the scope of this overview.
720+
721+.. toctree::
722+ :maxdepth: 3
723+
724+ trusted-launcher.rst
725+ config.rst
726+ resources.rst
727+ old.rst
728+
729+General design considerations
730+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
731+
732+PlainBox is a reimplementation of CheckBox that replaces a reactor / event /
733+plugin architecture with a monolithic core and tightly integrated components.
734+
735+The implementation models a few of the externally-visible concepts such as
736+jobs, resources and resource programs but also has some additional design that
737+was not present in CheckBox before.
738+
739+The goal of the rewrite is to provide the right model and APIs for user
740+interfaces in order to build the kind of end-user solution that we could not
741+build with CheckBox.
742+
743+This is expressed by additional functionality that is there only to provide the
744+higher layers with the right data (failure reason, descriptions, etc.). The
745+code is also intended to be highly testable. Test coverage at the time of
746+writing this document was exceeding 80%
747+
748+The core requirement for the current phase of PlainBox development is feature
749+parity with CheckBox and gradual shift from one to another in the daily
750+responsibilities of the Hardware Certification team. Currently PlainBox
751+implements a large chunk of core / essential features from CheckBox. While not
752+all features are present the core is considered almost feature complete at this
753+stage.
754+
755
756=== added file 'plainbox/docs/dev/old.rst.OTHER'
757--- plainbox/docs/dev/old.rst.OTHER 1970-01-01 00:00:00 +0000
758+++ plainbox/docs/dev/old.rst.OTHER 2013-08-28 12:37:27 +0000
759@@ -0,0 +1,343 @@
760+Old Architecture Notes
761+======================
762+
763+.. warning::
764+
765+ This section needs maintenance
766+
767+Application Skeleton
768+^^^^^^^^^^^^^^^^^^^^
769+
770+This skeleton represents a typical application based on PlainBox. It enumerates
771+the essential parts of the APIs from the point of view of an application
772+developer.
773+
774+1. Instantiate :class:`plainbox.impl.checkbox.CheckBox` then call
775+ :meth:`plainbox.impl.checkbox.CheckBox.get_builtin_jobs()` to discover all
776+ known jobs. In the future this might be replaced by a step that obtains jobs
777+ from a named provider.
778+
779+3. Instantiate :class:`plainbox.impl.runner.JobRunner` so that we can run jobs
780+
781+4. Instantiate :class:`plainbox.impl.session.SessionState` so that we can keep
782+ track of application state.
783+
784+ - Potentially restore an earlier, interrupted, testing session by calling
785+ :meth:`plainbox.impl.session.SessionState.restore()`
786+
787+ - Potentially remove an earlier, interrupted, testing session by calling
788+ :meth:`plainbox.impl.session.SessionState.discard()`
789+
790+ - Potentially start a new test session by calling
791+ :meth:`plainbox.impl.session.SessionState.open()`
792+
793+5. Allow the user to select jobs that should be executed and update session
794+ state by calling
795+ :meth:`plainbox.impl.session.SessionState.update_desired_job_list()`
796+
797+6. For each job in :attr:`plainbox.impl.SessionState.run_list`:
798+
799+ 1. Check if we want to run the job (if we have a result for it from previous
800+ runs) or if we must run it (for jobs that cannot be persisted across
801+ suspend)
802+
803+ 2. Check if the job can be started by looking at
804+ :meth:`plainbox.impl.session.JobState.can_start()`
805+
806+ - optionally query for additional data on why a job cannot be started and
807+ present that to the user.
808+
809+ - optionally abort the sequence and go to step 5 or the outer loop.
810+
811+ 3. Call :meth:`plainbox.impl.runner.JobRunner.run_job()` with the current
812+ job and store the result.
813+
814+ - optionally ask the user to perform some manipulation
815+
816+ - optionally ask the user to qualify the outcome
817+
818+ - optionally ask the user for additional comments
819+
820+ 4. Call :meth:`plainbox.impl.session.SessionState.update_job_result()` to
821+ update readiness of jobs that depend on the outcome or output of current
822+ job.
823+
824+ 5. Call :meth:`plainbox.impl.session.SessionState.checkpoint()` to ensure
825+ that testing can resume after system crash or shutdown.
826+
827+7. Instantiate the selected state exporter, for example
828+ :class:`plainbox.impl.exporters.json.JSONSessionStateExporter` so that we
829+ can use it to save test results.
830+
831+ - optionally pass configuration options to customize the subset and the
832+ presentation of the session state
833+
834+8. Call
835+ :meth:`plainbox.impl.exporters.SessionStateExporterBase.get_session_data_subset()`
836+ followed by :meth:`plainbox.impl.exporters.SessionStateExporterBase.dump()`
837+ to save results to a file.
838+
839+9. Call :meth:`plainbox.impl.session.SessionState.close()` to remove any
840+ nonvolatile temporary storage that was needed for the session.
841+
842+Essential classes
843+=================
844+
845+:class:`~plainbox.impl.session.SessionState`
846+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
847+
848+Class representing all state needed during a single program session.
849+
850+Usage
851+-----
852+
853+The general idea is that you feed the session with a list of known jobs and
854+a subset of jobs that you want to run and in return get an ordered list of
855+jobs to run.
856+
857+It is expected that the user will select / deselect and run jobs. This
858+class can react to both actions by recomputing the dependency graph and
859+updating the read states accordingly.
860+
861+As the user runs subsequent jobs the results of those jobs are exposed to
862+the session with :meth:`update_job_result()`. This can cause subsequent
863+jobs to become available (not inhibited by anything). Note that there is no
864+notification of changes at this time.
865+
866+The session does almost nothing by itself, it learns about everything by
867+observing job results coming from the job runner
868+(:class:`plainbox.impl.runner.JobRunner`) that applications need to
869+instantiate.
870+
871+Suspend and resume
872+------------------
873+
874+The session can save check-point data after each job is executed. This
875+allows the system to survive and continue after a catastrophic failure
876+(broken suspend, power failure) or continue across tests that require the
877+machine to reboot.
878+
879+.. todo::
880+
881+ Create a section on suspend/resume design
882+
883+Implementation notes
884+--------------------
885+
886+Internally it ties into :class:`plainbox.impl.depmgr.DependencySolver` for
887+resolving dependencies. The way the session objects are used allows them to
888+return various problems back to the UI level - those are all the error
889+classes from :mod:`plainbox.impl.depmgr`:
890+
891+ - :class:`plainbox.impl.depmgr.DependencyCycleError`
892+
893+ - :class:`plainbox.impl.depmgr.DependencyDuplicateError`
894+
895+ - :class:`plainbox.impl.depmgr.DependencyMissingError`
896+
897+Normally *none* of those errors should ever happen, they are only provided
898+so that we don't choke when a problem really happens. Everything is checked
899+and verified early before starting a job so typical unit and integration
900+testing should capture broken job definitions (for example, with cyclic
901+dependencies) being added to the repository.
902+
903+Implementation issues
904+---------------------
905+
906+There are two issues that are known at this time:
907+
908+* There is too much checkbox-specific knowledge which really belongs
909+ elsewhere. We are working to remove that so that non-checkbox jobs
910+ can be introduced later. There is a branch in progress that entirely
911+ removes that and moves it to a new concept called SessionController.
912+ In that design the session delegates understanding of results to a
913+ per-job session controller and exposes some APIs to alter the state
914+ that was previously internal (most notably a way to add new jobs and
915+ resources).
916+
917+* The way jobs are currently selected is unfortunate because of local jobs
918+ that can add new jobs to the system. This causes considerable complexity
919+ at the application level where the application must check if each
920+ executed job is a 'local' job and re-compute the desired_job_list. This
921+ should be replaced by a matcher function that can be passed to
922+ SessionState once so that desired_job_list is re-evaluated internally
923+ whenever job_list changes.
924+
925+
926+:class:`~plainbox.impl.job.JobDefinition`
927+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
928+
929+:term:`CheckBox` has a concept of a :term:`job`. Jobs are named units of
930+testing work that can be executed. Typical jobs range from automated CPU power
931+management checks, BIOS tests, semi-automated peripherals testing to all manual
932+validation by following a script (intended for humans).
933+
934+Jobs are distributed in plain text files, formated as a loose RFC822 documents
935+where typically a single text file contains a few dozen different jobs that
936+belong to one topic, for example, all bluetooth tests.
937+
938+Tests have a number of properties that will not be discussed in detail here,
939+they are all documented in :class:`plainbox.impl.job.JobDefinition`. From the
940+architecture point of view the four essential properties of a job are *name*,
941+*plugin* and *requires* and *depends*. Those are discussed in detail below.
942+
943+JobDefinition.name
944+------------------
945+
946+The *name* field must be unique and is referred to by other parts of the system
947+(such as whitelists). Typically jobs follow a simple naming pattern
948+'category/detail', eg, 'networking/modem_connection'. The name must be _unique_
949+and this is enforced by the core.
950+
951+JobDefinition.plugin
952+--------------------
953+
954+The *plugin* field is an archaism from CheckBox and a misnomer (as PlainBox
955+does not have any plugins). In the CheckBox architecture it would instruct the
956+core which plugin should process that job. In PlainBox it is a way to encode
957+what type of a job is being processed. There is a finite set of types that are
958+documented below.
959+
960+plugin == "shell"
961+#################
962+
963+This value is used for fully automated jobs. Everything the job needs to do is
964+automated (preparation, execution, verification) and fully handled by the
965+command that is associated with a job.
966+
967+plugin == "manual"
968+##################
969+
970+This value is used for fully manual jobs. It has no special handling in the core
971+apart from requiring a human-provided outcome (pass/fail classification)
972+
973+.. _local:
974+
975+plugin == "local"
976+#################
977+
978+This value is used for special job generator jobs. The output of such jobs is
979+interpreted as additional jobs and is identical in effect to loading such jobs
980+from a job definition file.
981+
982+There are two practical uses for such jobs:
983+
984+* Some local jobs are used to generate a number of jobs for each object.
985+ This is needed where the tested machine may have a number of such objects
986+ and each requires unique testing. A good example is a computer where all
987+ network tests are explicitly "instantiated" for each network card
988+ present.
989+
990+ This is a valid use case but is rather unfortunate for architecture of
991+ PlainBox and there is a desire to replace it with equally-expressive
992+ pattern jobs. The advantage is that unlike local jobs (which cannot be
993+ "discovered" without enduring any potential side effects that may be
994+ caused by the job script command) pattern jobs would allow the core to
995+ determine the names of jobs that can be generated and, for example,
996+ automatically determine that a pattern job needs to be executed as a
997+ dependency of a phantom (yet undetermined) job with a given name.
998+
999+ The solution with "pattern" jobs may be executed in future phases of
1000+ PlainBox development. Currently there is no support for that at all.
1001+
1002+ Currently PlainBox cannot determine job dependencies across local jobs.
1003+ That is, unless a local job is explicitly requested (in the desired job
1004+ list) PlainBox will not be able to run a job that is generated by a local
1005+ job at all and will treat it as if that job never existed.
1006+
1007+* Some local jobs are used to create a form of informal "category".
1008+ Typically all such jobs have a leading and trailing double underscore,
1009+ for example '__audio__'. This is currently being used by CheckBox for
1010+ building a hierarchical tree of tests that the user may select.
1011+
1012+ Since this has the same flaws as described above (for pattern jobs) it
1013+ will likely be replaced by an explicit category field that can be
1014+ specified each job.
1015+
1016+plugin == "resource"
1017+####################
1018+
1019+This value is used for special "data" or "environment" jobs. Their output is
1020+parsed as a list of RFC822 records and is kept by the core during a testing session.
1021+
1022+They are primarily used to determine if a given job can be started. For
1023+example, a particular bluetooth test may use the _requires_ field to indicate
1024+that it depends (via a resource dependency) on a job that enumerates devices
1025+and that one of those devices must be a bluetooth device.
1026+
1027+plugin == "user-interact"
1028+#########################
1029+
1030+For all intents and purposes it is equivalent to "manual". The actual
1031+difference is that a user is expected to perform some physical manipulation
1032+before an automated test.
1033+
1034+plugin == "user-verify"
1035+#######################
1036+
1037+For all intents and purposes it is equivalent to "manual". The actual
1038+difference is that a user is expected to perform manual verification after an
1039+automated test.
1040+
1041+JobDefinition.depends
1042+---------------------
1043+
1044+The *depends* field is used to express dependencies between two jobs. If job A
1045+has depends on job B then A cannot start if B is not both finished and
1046+successful. PlainBox understands this dependency and can automatically sort and
1047+execute jobs in proper order. In many places of the code this is referred to as
1048+a "direct dependency" (in contrast to "resource dependency")
1049+
1050+The actual syntax is not strictly specified, PlainBox interprets this field as
1051+a list of tokens delimited by comma or any whitespace (including newlines).
1052+
1053+A job may depend on any number of other jobs. There are a number of failure
1054+modes associated with this feature, all of which are detected and handled by
1055+PlainBox. Typically they only arise when during CheckBox job development
1056+(editing actual job files) and are always a sign of a human error. No released
1057+version of CheckBox or PlainBox should ever encounter any of those issues.
1058+
1059+The actual problems are:
1060+
1061+* dependency cycles, where job either directly or indirectly depends on
1062+ itself
1063+
1064+* missing dependencies where some job refers to a job that is not defined
1065+ anywhere.
1066+
1067+* duplicate jobs where two jobs with the same name (but different
1068+ definition) are being introduced to the system.
1069+
1070+In all of those cases the core removes the offending job and tries to work
1071+regardless of the problem. This is intended more as a development aid rather
1072+than a reliability feature as no released versions of either project should
1073+cause this problem.
1074+
1075+JobDefinition.command
1076+---------------------
1077+
1078+The *command* field is used when the job needs to call an external command.
1079+Typically all shell jobs define a command to run.
1080+
1081+"Manual" jobs can also define a command to run as part of the test procedure.
1082+
1083+JobDefinition.user
1084+------------------
1085+
1086+The *user* field is used when the job requires to run as a specific user
1087+(e.g. root).
1088+
1089+The job command will be run via pkexec to get the necessary
1090+permissions.
1091+
1092+.. _environ:
1093+
1094+JobDefinition.environ
1095+---------------------
1096+
1097+The *environ* field is used to pass additional environmental keys from the user
1098+session to the new environment set up when the job command is run by another
1099+user (root, most of the time).
1100+
1101+The actual syntax is not strictly specified, PlainBox interprets this field as
1102+a list of tokens delimited by comma or any whitespace (including newlines).
1103
1104=== added file 'plainbox/docs/dev/reference.rst.OTHER'
1105--- plainbox/docs/dev/reference.rst.OTHER 1970-01-01 00:00:00 +0000
1106+++ plainbox/docs/dev/reference.rst.OTHER 2013-08-28 12:37:27 +0000
1107@@ -0,0 +1,179 @@
1108+.. _code_reference:
1109+
1110+.. toctree::
1111+ :maxdepth: 2
1112+
1113+Code reference
1114+==============
1115+
1116+.. note::
1117+
1118+ Unless stated otherwise all API is unstable. PlainBox does not offer
1119+ general API stability at this time.
1120+
1121+.. automodule:: plainbox
1122+ :members:
1123+ :undoc-members:
1124+ :show-inheritance:
1125+
1126+.. automodule:: plainbox.public
1127+ :members:
1128+ :undoc-members:
1129+ :show-inheritance:
1130+
1131+.. automodule:: plainbox.abc
1132+ :members:
1133+ :undoc-members:
1134+ :show-inheritance:
1135+
1136+.. automodule:: plainbox.tests
1137+ :members:
1138+ :undoc-members:
1139+ :show-inheritance:
1140+
1141+.. automodule:: plainbox.testing_utils
1142+ :members:
1143+ :undoc-members:
1144+ :show-inheritance:
1145+
1146+.. automodule:: plainbox.testing_utils.cwd
1147+ :members:
1148+ :undoc-members:
1149+ :show-inheritance:
1150+
1151+.. automodule:: plainbox.testing_utils.io
1152+ :members:
1153+ :undoc-members:
1154+ :show-inheritance:
1155+
1156+.. automodule:: plainbox.testing_utils.testcases
1157+ :members:
1158+ :undoc-members:
1159+ :show-inheritance:
1160+
1161+.. automodule:: plainbox.vendor
1162+ :members:
1163+
1164+.. automodule:: plainbox.vendor.extcmd
1165+ :members:
1166+ :undoc-members:
1167+ :show-inheritance:
1168+
1169+.. automodule:: plainbox.impl
1170+ :members:
1171+ :undoc-members:
1172+ :show-inheritance:
1173+
1174+.. automodule:: plainbox.impl.commands
1175+ :members:
1176+ :undoc-members:
1177+ :show-inheritance:
1178+
1179+.. automodule:: plainbox.impl.commands.selftest
1180+ :members:
1181+ :undoc-members:
1182+ :show-inheritance:
1183+
1184+.. automodule:: plainbox.impl.exporter
1185+ :members:
1186+ :undoc-members:
1187+ :show-inheritance:
1188+
1189+.. automodule:: plainbox.impl.exporter.json
1190+ :members:
1191+ :undoc-members:
1192+ :show-inheritance:
1193+
1194+.. automodule:: plainbox.impl.exporter.rfc822
1195+ :members:
1196+ :undoc-members:
1197+ :show-inheritance:
1198+
1199+.. automodule:: plainbox.impl.exporter.text
1200+ :members:
1201+ :undoc-members:
1202+ :show-inheritance:
1203+
1204+.. automodule:: plainbox.impl.secure
1205+ :members:
1206+ :undoc-members:
1207+ :show-inheritance:
1208+
1209+.. automodule:: plainbox.impl.secure.checkbox_trusted_launcher
1210+ :members:
1211+ :undoc-members:
1212+ :show-inheritance:
1213+
1214+.. automodule:: plainbox.impl.transport
1215+ :members:
1216+ :undoc-members:
1217+ :show-inheritance:
1218+
1219+.. automodule:: plainbox.impl.transport.certification
1220+ :members:
1221+ :undoc-members:
1222+ :show-inheritance:
1223+
1224+.. automodule:: plainbox.impl.box
1225+ :members:
1226+ :undoc-members:
1227+ :show-inheritance:
1228+
1229+.. automodule:: plainbox.impl.checkbox
1230+ :members:
1231+ :undoc-members:
1232+ :show-inheritance:
1233+
1234+.. automodule:: plainbox.impl.config
1235+ :members:
1236+ :undoc-members:
1237+ :show-inheritance:
1238+
1239+.. automodule:: plainbox.impl.depmgr
1240+ :members:
1241+ :undoc-members:
1242+ :show-inheritance:
1243+
1244+.. automodule:: plainbox.impl.integration_tests
1245+ :members:
1246+ :undoc-members:
1247+ :show-inheritance:
1248+
1249+.. automodule:: plainbox.impl.job
1250+ :members:
1251+ :undoc-members:
1252+ :show-inheritance:
1253+
1254+.. automodule:: plainbox.impl.mock_job
1255+ :members:
1256+ :undoc-members:
1257+ :show-inheritance:
1258+
1259+.. automodule:: plainbox.impl.resource
1260+ :members:
1261+ :undoc-members:
1262+ :show-inheritance:
1263+
1264+.. automodule:: plainbox.impl.result
1265+ :members:
1266+ :undoc-members:
1267+ :show-inheritance:
1268+
1269+.. automodule:: plainbox.impl.rfc822
1270+ :members:
1271+ :show-inheritance:
1272+
1273+.. automodule:: plainbox.impl.runner
1274+ :members:
1275+ :undoc-members:
1276+ :show-inheritance:
1277+
1278+.. automodule:: plainbox.impl.session
1279+ :members:
1280+ :undoc-members:
1281+ :show-inheritance:
1282+
1283+.. automodule:: plainbox.impl.testing_utils
1284+ :members:
1285+ :undoc-members:
1286+ :show-inheritance:
1287
1288=== added file 'plainbox/docs/dev/resources.rst.OTHER'
1289--- plainbox/docs/dev/resources.rst.OTHER 1970-01-01 00:00:00 +0000
1290+++ plainbox/docs/dev/resources.rst.OTHER 2013-08-28 12:37:27 +0000
1291@@ -0,0 +1,259 @@
1292+Resources
1293+=========
1294+
1295+Resources are a mechanism that allows to constrain certain :term:`job` to
1296+execute only on devices with appropriate hardware or software dependencies.
1297+This mechanism allows some types of jobs to publish resource objects to an
1298+abstract namespace and to a way to evaluate a resource program to determine if
1299+a job can be started.
1300+
1301+Resources in PlainBox
1302+=====================
1303+
1304+The following chapters explain how resources actually work in :term:`PlainBox`.
1305+Currently there *is* a subtle difference between this and the original
1306+:term:`CheckBox` implementation.
1307+
1308+Resource programs
1309+-----------------
1310+
1311+Resource programs are multi-line statements that can be embedded in job
1312+definitions. By far, the most common use case is to check if a required package
1313+is installed, and thus, the job can use it as a part of a test. A check like
1314+this looks like this::
1315+
1316+ package.name == "fwts"
1317+
1318+This resource program codifies that the job needs the ``fwts`` package to run.
1319+There is a companion job with the same name that interrogates the local package
1320+database and publishes a set of resource objects. Each such object is a
1321+collection of arbitrary key-value pairs. The ``package`` job simply publishes
1322+the ``name`` and ``version`` of each installed package but the mechanism is
1323+generic and applies to all resources.
1324+
1325+As stated, resource programs can be multi-line, a real world example of that is
1326+presented below::
1327+
1328+ device.category == 'CDROM'
1329+ optical_drive.cd == 'writable'
1330+
1331+This example is much like the one above, referring to some resources, here
1332+coming from jobs ``device`` and ``optical_drive``. What is important to point
1333+out is that, as a rule of a thumb, multi line programs have an implicit ``and``
1334+operator between each line. This program would only evaluate to True if there
1335+is a writable CD-ROM available.
1336+
1337+Each resource program is composed of resource expressions. Each line maps
1338+directly onto one expression so the example program above uses two resource
1339+expressions.
1340+
1341+Resource expressions
1342+--------------------
1343+
1344+Resource expressions are evaluated like normal python programs. They use all of
1345+the same syntax, semantics and behavior. None of the operators are overridden
1346+to do anything unexpected. The evaluator tries to follow the principle of least
1347+surprise but this is not always possible.
1348+
1349+Resource expressions cannot execute arbitrary python code. In general almost
1350+everything is disallowed, except as noted below:
1351+
1352+* Expressions can use any literals (strings, numbers, True, False, lists and tuples)
1353+* Expressions can use boolean operators (``and``, ``or``, ``not``)
1354+* Expressions can use all comparison operators
1355+* Expressions can use all binary and unary operators
1356+* Expressions can use the set membership operator (``in``)
1357+* Expressions can use read-only attribute access
1358+
1359+Anything else is rejected as an invalid resource expression.
1360+
1361+In addition to that, each resource expression must use exactly one variable,
1362+which must be used like an object with attributes. The name of that variable
1363+must correspond to the name of the job that generates resources. Attempts to
1364+use more than one variable or to not use any variables are detected early and
1365+rejected as invalid resource expressions.
1366+
1367+The name of the variable determines which resource group to use. It must match
1368+the name of the job that generates such resources.
1369+
1370+In the examples elsewhere in this page the ``package`` resources are generated
1371+by the ``package`` job. PlainBox uses this to know which resources to try but
1372+also to implicitly to express dependencies so that the ``package`` job does not
1373+have to be explicitly selected and marked for execution prior to the job that
1374+in fact depends on it. This is all done automatically.
1375+
1376+Evaluation
1377+----------
1378+
1379+Due to mandatory compatibility with existing :term:`CheckBox` jobs there are
1380+some unexpected aspects of how evaluation is performed. Those are marked as
1381+**unexpected** below:
1382+
1383+1. First PlainBox looks at the resource program and splits it into lines. Each
1384+ non-empty line is parsed and converted to a resource expression.
1385+
1386+2. **unexpected** Each resource expression is repeatedly evaluated, once for
1387+ each resource from the group determined by the variable name. All exceptions
1388+ are silently ignored and treated as if the iteration had evaluated to False.
1389+ The whole resource expression evaluates to ``True`` if any of the iterations
1390+ evaluated to ``True``. In other words, there is an implicit ``any()`` around
1391+ each resource expression, iterating over all resources.
1392+
1393+3. **unexpected** The resource program evaluates to ``True`` only if all
1394+ resource expressions evaluated to ``True``. In other words, there is an
1395+ implicit ``and`` between each line.
1396+
1397+Limitations
1398+-----------
1399+
1400+The design of resource programs has the following shortcomings. The list is
1401+non-exhaustive, it only contains issues that we came across found not to work
1402+in practice.
1403+
1404+Exactly one variable per expression
1405+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1406+
1407+Each resource expression must refer to exactly one variable. This is a side
1408+effect of the way the evaluator works. It basically bind one object (a
1409+particular resource) to that variable and evaluates the expression.
1410+
1411+The expression parser / syntax analyzer identifies expressions with this
1412+problem early and rejects them with an appropriate error message. Here are
1413+some examples of hypothetical expressions that exhibit this problem.
1414+
1415+"I want to have mplayer and an audio device so that I can play some sounds"::
1416+
1417+ device.category == "AUDIO" and package.name == "mplayer"
1418+
1419+To work around this, split the expression to two separate expressions. The
1420+evaluator will put an implicit ``and`` between them and it will do exactly what
1421+you intended::
1422+
1423+ device.category == "AUDIO"
1424+ package.name == "mplayer"
1425+
1426+"I want to always run this test"::
1427+
1428+ True
1429+
1430+To work around this, simply remove the requirement program entirely!
1431+
1432+"I want to never run this test"::
1433+
1434+ False
1435+
1436+To work around this remove this job from the selection. You may also use a
1437+special resource that produces one constant value, and check that it is equal
1438+to something different.
1439+
1440+Exactly one resource bound to a variable at once
1441+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1442+
1443+It's not possible to refer to two different resources, from the same resource
1444+group, in one resource expression. In other terms, the variable always points
1445+to one object, it is not a collection of objects.
1446+
1447+For example, let's consider this program::
1448+
1449+ package.name == 'xorg' and package.name == 'procps'
1450+
1451+Seemingly the intent was to ensure that both ``xorg`` and ``procps`` are
1452+installed. The reason why this does not work is that at each iteration of the
1453+the expression evaluator, the name ``package`` refers to exactly one resource
1454+object. In other words, that expression is equivalent to this one::
1455+
1456+ A == True and A == False
1457+
1458+This type of error is not captured by our limited semantic analyzer. It will
1459+silently evaluate to False and inhibit the job from being stated.
1460+
1461+To work around this, split the expression to two consecutive lines. As stated
1462+in rule 3 in the list above, there is an implicit ``and`` operator between all
1463+expressions. A working example that expresses the same intent looks like this::
1464+
1465+ package.name == 'xorg'
1466+ package.name == 'procps'
1467+
1468+Operator != is useless
1469+^^^^^^^^^^^^^^^^^^^^^^
1470+
1471+This is strange at first but quickly becomes obvious once you recall rule 2
1472+from the list above. That rule states that the expression is evaluated
1473+repeatedly for each resource from a particular group and that any ``True``
1474+iteration marks the whole expression as ``True``).
1475+
1476+Let's look at a real-world example::
1477+
1478+ xinput.device_class == 'XITouchClass' and xinput.touch_mode != 'dependent'
1479+
1480+So seemingly, the intent here was to have at least ``xinput`` resource with a
1481+``device_class`` attribute equal to ``XITouchClass`` that has ``touch_mode``
1482+attribute equal to anything but ``dependent``.
1483+
1484+Now let's assume that we have exactly two resources in the ``xinput`` group::
1485+
1486+ device_class: XITouchClass
1487+ touch_mode: dependant
1488+
1489+ device_class: XITouchClass
1490+ touch_mode: something else
1491+
1492+Now, this expression will evaluate to ``True``, as the second resource fulfils
1493+the requirements. Is this what the test designer had expected? That's hard to
1494+say. The problem here is that this expression can be understood as *at least
1495+one resource isn't something* **or** *all resources weren't something*. Both
1496+are equally valid desires and, depending on how the test is implemented, may or
1497+many not work correctly in practice.
1498+
1499+Currently there is no workaround. We are considering adding a new syntax that
1500+would allow to specify this explicitly. The proposal is documented below as
1501+"implicit any(), explicit all()"
1502+
1503+Everything is a string
1504+^^^^^^^^^^^^^^^^^^^^^^
1505+
1506+Resource programs are regular python programs evaluated in unusual ways but
1507+all of the variables that are exposed through the resource object are strings.
1508+
1509+This has considerable impact on comparison, unless you are comparing to a
1510+string the comparison will always silently fail as python has dynamic but
1511+strict, not loose types (there is no implicit type conversion). To alleviate
1512+this problem several type names / conversion functions are allowed in
1513+requirement programs. Those are:
1514+
1515+* :py:class:`int`, to convert to integer numbers
1516+* :py:class:`float`, to convert to floating point numbers
1517+* :py:class:`bool`, to convert to a boolean context
1518+
1519+Considered enhancements
1520+-----------------------
1521+
1522+We are currently considering one improvement to resource programs. This would
1523+allow us to introduce a fix that resolves some issues in a backwards compatible
1524+way. Technical aspects are not yet resolved as that extension would not be
1525+available in :term:`CheckBox` until CheckBox can be built on top of
1526+:term:`PlainBox`
1527+
1528+Implicit any(), explicit all()
1529+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1530+
1531+This proposal changes the way resource expressions are evaluated.
1532+
1533+The implicit ``any()`` implemented as a loop over all resources from the
1534+resource group designated by variable name would be configurable.
1535+
1536+A developer may choose to wrap the whole expression in the ``all()`` function
1537+to indicate that the expression inside ``all()`` must evaluate to ``True`` for
1538+**all** iterations (all resources).
1539+
1540+This would allow solving the case where a job can only run, for example, when a
1541+certain package is **not** installed. This could be expressed as::
1542+
1543+ all(package.name != 'ubuntu-desktop')
1544+
1545+Resources in CheckBox
1546+=====================
1547+
1548+The following chapters explain how resources originally worked in
1549+:term:`CheckBox`. Only notable differences from :term:`PlainBox` implementation
1550+are listed.
1551
1552=== added file 'plainbox/docs/dev/trusted-launcher.rst'
1553--- plainbox/docs/dev/trusted-launcher.rst 1970-01-01 00:00:00 +0000
1554+++ plainbox/docs/dev/trusted-launcher.rst 2013-08-28 12:37:27 +0000
1555@@ -0,0 +1,209 @@
1556+Running jobs as root
1557+====================
1558+
1559+:term:`PlainBox` is started without any privilege.
1560+But several tests need to start commands requiring privileges.
1561+
1562+Such tests will call a trusted launcher, a standalone script
1563+which does not depend on the :term:`PlainBox` core modules.
1564+`polkit <http://www.freedesktop.org/wiki/Software/polkit>`_
1565+will control access to system resources.
1566+The trusted launcher has to be started using
1567+`pkexec <http://www.freedesktop.org/software/polkit/docs/0.105/pkexec.1.html>`_
1568+so that the related policy file works as expected.
1569+
1570+To avoid a security hole that allows anyone to run anything as root,
1571+the launcher can only run jobs installed in a system-wide directory.
1572+This way we are not weaken the trust system as root access is required
1573+to install both components (the trusted runner and jobs).
1574+The :term:`PlainBox` process will send an identifier which is matched by a well-known
1575+list in the trusted launcher. This identifier is the job hash:
1576+
1577+.. code-block:: bash
1578+
1579+ $ pkexec trusted-launcher JOB-HASH
1580+
1581+See :meth:`plainbox.impl.secure.checkbox_trusted_launcher.BaseJob.get_checksum()` for details about job hashes.
1582+
1583+Using Polkit
1584+^^^^^^^^^^^^
1585+
1586+Available authentication methods
1587+--------------------------------
1588+
1589+.. note::
1590+
1591+ Only applicable to the package version of PlainBox
1592+
1593+PlainBox comes with two authentication methods but both aim to retain the
1594+granted privileges for the life of the :term:`PlainBox` process.
1595+
1596+* The first method will ask the password only once and show the following
1597+ agent on desktop systems (a text-based agent is available for servers):
1598+
1599+ .. code-block:: text
1600+
1601+ +-----------------------------------------------------------------------------+
1602+ | [X] Authenticate |
1603+ +-----------------------------------------------------------------------------+
1604+ | |
1605+ | [Icon] Please enter your password. Some tests require root access to run |
1606+ | properly. Your password will never be stored and will never be |
1607+ | submitted with test results. |
1608+ | |
1609+ | An application is attempting to perform an action that requires |
1610+ | privileges. |
1611+ | Authentication as the super user is required to perform this action. |
1612+ | |
1613+ | Password: [________________________________________________________] |
1614+ | |
1615+ | [V] Details: |
1616+ | Action: org.freedesktop.policykit.pkexec.run-plainbox-job |
1617+ | Vendor: PlainBox |
1618+ | |
1619+ | [Cancel] [Authenticate] |
1620+ +-----------------------------------------------------------------------------+
1621+
1622+ The following policy file has to be installed in :file:`/usr/share/polkit-1/actions/`
1623+ on Ubuntu systems.
1624+ Asking the password just one time and keeps the authentication for forthcoming
1625+ calls is provided by the **allow_active** element and the **auth_admin_keep** value.
1626+
1627+ Check the `polkit actions <http://www.freedesktop.org/software/polkit/docs/0.105/polkit.8.html#polkit-declaring-actions>`_
1628+ documentation for details about the other parameters.
1629+
1630+ .. code-block:: xml
1631+
1632+ <?xml version="1.0" encoding="UTF-8"?>
1633+ <!DOCTYPE policyconfig PUBLIC
1634+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
1635+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
1636+ <policyconfig>
1637+
1638+ <vendor>PlainBox</vendor>
1639+ <vendor_url>https://launchpad.net/checkbox</vendor_url>
1640+ <icon_name>checkbox</icon_name>
1641+
1642+ <action id="org.freedesktop.policykit.pkexec.run-plainbox-job">
1643+ <description>Run Job command</description>
1644+ <message>Authentication is required to run a job command.</message>
1645+ <defaults>
1646+ <allow_any>no</allow_any>
1647+ <allow_inactive>no</allow_inactive>
1648+ <allow_active>auth_admin_keep</allow_active>
1649+ </defaults>
1650+ <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/checkbox-trusted-launcher</annotate>
1651+ <annotate key="org.freedesktop.policykit.exec.allow_gui">TRUE</annotate>
1652+ </action>
1653+
1654+ </policyconfig>
1655+
1656+* The second method is only intended to be used in headless mode (like `SRU`).
1657+ The only difference with the above method is that **allow_active** will be set to **yes**.
1658+
1659+.. note::
1660+
1661+ The two policy files are available in the PlainBox :file:`contrib/` directory.
1662+
1663+Environment settings with pkexec
1664+--------------------------------
1665+
1666+`pkexec <http://www.freedesktop.org/software/polkit/docs/0.105/pkexec.1.html>`_
1667+allows an authorized user to execute a command as another user.
1668+But the environment that ``command`` will run it, will be set to a minimal known
1669+and safe environment in order to avoid injecting code through ``LD_LIBRARY_PATH``
1670+or similar mechanisms.
1671+
1672+However, some jobs commands require specific enviroment variables such as the
1673+name of an access point for a wireless test. Those kind of variables must be
1674+available to the trusted launcher.
1675+To do so, the enviromment mapping is sent to the launcher like key/value pairs
1676+are sent to the env(1) command:
1677+
1678+.. code-block:: bash
1679+
1680+ $ pkexec trusted-launcher JOB-HASH [NAME=VALUE [NAME=VALUE ...]]
1681+
1682+Each NAME will be set to VALUE in the environment given that they are known
1683+and defined in the :ref:`JobDefinition.environ <environ>` parameter.
1684+
1685+Checkbox trusted launcher
1686+^^^^^^^^^^^^^^^^^^^^^^^^^
1687+
1688+The checkbox trusted launcher is the minimal code needed to be able to run a
1689+:term:`CheckBox` job command.
1690+
1691+It offers base classes for the following core subclasses:
1692+
1693+* :class:`plainbox.impl.rfc822.RFC822Record`
1694+* :class:`plainbox.impl.job.JobDefinition`
1695+
1696+The only duplicated code is the RFC822 parser, where all logging features have
1697+been removed.
1698+
1699+The :class:`plainbox.impl.secure.checkbox_trusted_launcher.Runner` class just
1700+executes the command process with :py:func:`os.execve`.
1701+
1702+Internally the checkbox trusted launcher looks for jobs in the system locations defined in
1703+:attr:`plainbox.impl.secure.checkbox_trusted_launcher.Runner.CHECKBOXES` which defaults to :file:`/usr/share/checkbox*`.
1704+This way the launcher can match all :term:`CheckBox` variants, like ``checkbox-oem(-.*)?``
1705+
1706+Usage
1707+-----
1708+
1709+.. code-block:: text
1710+
1711+ checkbox-trusted-launcher [-h] (--hash HASH | --warmup)
1712+ [--via LOCAL-JOB-HASH]
1713+ [NAME=VALUE [NAME=VALUE ...]]
1714+
1715+ positional arguments:
1716+ NAME=VALUE Set each NAME to VALUE in the string environment
1717+
1718+ optional arguments:
1719+ -h, --help show this help message and exit
1720+ --hash HASH job hash to match
1721+ --warmup Return immediately, only useful when used with
1722+ pkexec(1)
1723+ --via LOCAL-JOB-HASH Local job hash to use to match the generated job
1724+
1725+.. note::
1726+
1727+ Check all job hashes with ``plainbox special -J``
1728+
1729+As stated in the polkit chapter, only a trusted subset of the environment mapping
1730+will be set using :py:func:`os.execve` to run the command.
1731+Only the variables defined in the job environ property are allowed to avoid
1732+compromising the root environment.
1733+Needed modifications like adding ``CHECKBOX_SHARE`` and new paths to scripts are
1734+managed by the checkbox-trusted-launcher.
1735+
1736+Authentication on PlainBox startup
1737+----------------------------------
1738+
1739+To avoid prompting the password at the first test requiring privileges, :term:`PlainBox`
1740+will call the ``checkbox-trusted-launcher`` with the ``--warmup`` option.
1741+It's like a NOOP and it will return immediately, but thanks to the installed policy file
1742+the authentication will be kept.
1743+
1744+.. note::
1745+
1746+ When running the development version from a branch, the usual polkit
1747+ authentication agent will pop up to ask the password each and every time.
1748+ This is the only difference.
1749+
1750+Special case of jobs using the CheckBox local plugin
1751+----------------------------------------------------
1752+
1753+For jobs generated from :ref:`local <local>` jobs (e.g. disk/read_performance.*)
1754+the trusted launcher is started with ``--via`` meaning that we have to first
1755+eval a local job to find a hash match.
1756+Once a match is found, the job command is executed using :py:func:`os.execve`.
1757+
1758+.. code-block:: bash
1759+
1760+ $ pkexec checkbox-trusted-launcher --hash JOB-HASH --via LOCAL-JOB-HASH
1761+
1762+.. note::
1763+
1764+ it will obviously fail if any local job can ever generate another local job.
1765
1766=== added file 'plainbox/docs/usage.rst.OTHER'
1767--- plainbox/docs/usage.rst.OTHER 1970-01-01 00:00:00 +0000
1768+++ plainbox/docs/usage.rst.OTHER 2013-08-28 12:37:27 +0000
1769@@ -0,0 +1,93 @@
1770+.. _usage:
1771+
1772+Usage
1773+=====
1774+
1775+Currently :term:`PlainBox` has no graphical user interface. To use it you need
1776+to use the command line.
1777+
1778+Basically there is just one command that does everything we can do so far, that
1779+is :command:`plainbox run`. It has a number of options that tell it which
1780+:term:`job` to run and what to do with results.
1781+
1782+PlainBox has built-in help system so running :command:`plainbox run --help`
1783+will give you instant information about all the various arguments and options
1784+that are available. This document is not intended to replace that.
1785+
1786+Running a specific job
1787+^^^^^^^^^^^^^^^^^^^^^^
1788+
1789+To run a specific :term:`job` pass it to the ``--include-pattern`` or ``-i``
1790+option.
1791+
1792+For example, to run the ``cpu/scaling_test`` job:
1793+
1794+.. code-block:: bash
1795+
1796+ $ plainbox run -i cpu/scaling_test
1797+
1798+.. note::
1799+
1800+ The option ``-i`` can be provided any number of times.
1801+
1802+Running jobs related to a specific area
1803+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1804+
1805+PlainBox has no concept of job categories but you can simulate that by
1806+running all jobs that follow a specific naming pattern. For example, to run
1807+all of the USB tests you can run the following command:
1808+
1809+.. code-block:: bash
1810+
1811+ $ plainbox run -i 'usb/.*'
1812+
1813+To list all known jobs run:
1814+
1815+.. code-block:: bash
1816+
1817+ plainbox special --list-jobs
1818+
1819+Running a white list
1820+^^^^^^^^^^^^^^^^^^^^
1821+
1822+To run a :term:`white list` pass the ``--whitelist`` or ``-w`` option.
1823+
1824+For example, to run the default white list run:
1825+
1826+.. code-block:: bash
1827+
1828+ $ plainbox run -w /usr/share/checkbox/data/whitelists/default.whitelist
1829+
1830+Saving test results as XML
1831+^^^^^^^^^^^^^^^^^^^^^^^^^^
1832+
1833+To generate an XML file that can be sent to the :term:`certification website`
1834+you need to pass two additional options:
1835+
1836+1. ``--output-format=xml``
1837+2. ``--output-file=NAME`` where *NAME* is a file name
1838+
1839+For example, to get the default certification tests ready to be submitted
1840+run this command:
1841+
1842+.. code-block:: bash
1843+
1844+ $ plainbox run --whitelist=/usr/share/checkbox/data/whitelists/default.whitelist --output-format=xml --output-file=submission.xml
1845+
1846+Running stable release update tests
1847+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1848+
1849+PlainBox has special support for running stable release updates tests in an
1850+automated manner. This runs all the jobs from the *sru.whitelist* and sends the
1851+results to the certification website.
1852+
1853+To run SRU tests you will need to know the so-called :term:`Secure ID` of the
1854+device you are testing. Once you know that all you need to do is run:
1855+
1856+.. code-block:: bash
1857+
1858+ $ plainbox sru $secure_id submission.xml
1859+
1860+The second argument, submission.xml, is a name of the fallback file that is
1861+only created when sending the data to the certification website fails to work
1862+for any reason.
1863
1864=== added file 'plainbox/mk-venv.sh.OTHER'
1865--- plainbox/mk-venv.sh.OTHER 1970-01-01 00:00:00 +0000
1866+++ plainbox/mk-venv.sh.OTHER 2013-08-28 12:37:27 +0000
1867@@ -0,0 +1,195 @@
1868+#!/bin/sh
1869+# Create a virtualenv for working with plainbox.
1870+#
1871+# This ensures that 'plainbox' command exists and is in PATH and that the
1872+# plainbox module is correctly located can be imported.
1873+#
1874+# This is how Zygmunt Krynicki works, feel free to use or adjust to your needs
1875+
1876+VENV_PATH=
1877+install_missing=0
1878+# Parse arguments:
1879+while [ -n "$1" ]; do
1880+ case "$1" in
1881+ --help|-h)
1882+ echo "Usage: mk-venv.sh [LOCATION]"
1883+ echo ""
1884+ echo "Create a virtualenv for working with plainbox in LOCATION"
1885+ exit 0
1886+ ;;
1887+ --install-missing)
1888+ install_missing=1
1889+ shift
1890+ ;;
1891+ *)
1892+ if [ -z "$VENV_PATH" ]; then
1893+ VENV_PATH="$1"
1894+ shift
1895+ else
1896+ echo "Error: too many arguments: '$1'"
1897+ exit 1
1898+ fi
1899+ ;;
1900+ esac
1901+done
1902+
1903+# Apply defaults to arguments without values
1904+if [ -z "$VENV_PATH" ]; then
1905+ # Use sensible defaults for vagrant
1906+ if [ "$LOGNAME" = "vagrant" ]; then
1907+ VENV_PATH=/tmp/venv
1908+ else
1909+ VENV_PATH=/ramdisk/venv
1910+ fi
1911+fi
1912+
1913+# Do a sanity check on lsb_release that is missing on Fedora the last time I
1914+# had a look at it.
1915+if [ "x$(which lsb_release)" = "x" ]; then
1916+ echo "This script requires the 'lsb_release' command"
1917+ exit 1
1918+fi
1919+
1920+# The code below is a mixture of Debian/Ubuntu packages and pypi packages.
1921+# It is designed to work on Ubuntu 12.04 or later.
1922+# There are _some_ differences between how each release is handled.
1923+#
1924+# Non Ubuntu systems are not tested as they don't have the required checkbox
1925+# package. Debian might be supported once we have JobBox and stuff like Fedora
1926+# would need a whole new approach but patches are welcome [CLA required]
1927+if [ "$(lsb_release --short --id)" != "Ubuntu" ] && [ $(lsb_release --short --id --upstream) != "Ubuntu" ]; then
1928+ echo "Only Ubuntu is supported by this script."
1929+ echo "If you are interested in using it with your distribution"
1930+ echo "then please join us in #ubuntu-quality on freenode"
1931+ echo
1932+ echo "Alternatively you can use vagrant to develop plainbox"
1933+ echo "on any operating system, even Windows ;-)"
1934+ echo
1935+ echo "See: http://www.vagrantup.com/ for details"
1936+ exit 1
1937+fi
1938+# From now on we can assume a Debian-like system
1939+
1940+# Do some conditional stuff depending on the particular Ubuntu release
1941+enable_system_site=0
1942+install_coverage=0
1943+install_distribute=0
1944+install_pip=0
1945+# We need:
1946+# python3:
1947+# because that's what plainbox is written in
1948+# python3-dev
1949+# because we may pip-install stuff as well and we want to build native extensions
1950+# python3-pkg-resources:
1951+# because it is used by plainbox to locate files and extension points
1952+# python3-setuptools:
1953+# because it is used by setup.py
1954+# python3-lxml:
1955+# because that's how we validate RealaxNG schemas
1956+# python3-mock:
1957+# because that's what we used to construct some of our tests
1958+# python3-sphinx:
1959+# because that's how we build our documentation
1960+# python-virtualenv:
1961+# because that's how we create the virtualenv to work in
1962+# checkbox:
1963+# because plainbox depends on it as a job provider
1964+required_pkgs_base="python3 python3-dev python3-pkg-resources python3-setuptools python3-lxml python3-mock python3-sphinx python-virtualenv checkbox"
1965+
1966+# The defaults, install everything from pip and all the base packages
1967+enable_system_site=1
1968+install_distribute=1
1969+install_pip=1
1970+install_coverage=1
1971+install_requests=1
1972+required_pkgs="$required_pkgs_base"
1973+
1974+case "$(lsb_release --short --release)" in
1975+ 12.04|0.2)
1976+ # Ubuntu 12.04, this is the LTS release that we have to support despite
1977+ # any difficulties. It has python3.2 and all of our core dependencies
1978+ # although some packages are old by 13.04 standards, make sure to be
1979+ # careful with testing against older APIs.
1980+ ;;
1981+ 12.10)
1982+ ;;
1983+ 13.04)
1984+ # On Raring we can use the system package for python3-requests
1985+ install_distribute=0
1986+ install_pip=0
1987+ install_requests=0
1988+ required_pkgs="$required_pkgs_base python3-requests"
1989+ ;;
1990+ *)
1991+ echo "Using this version of Ubuntu for development is not supported"
1992+ echo "Unsupported version: $(lsb_release --short --release)"
1993+ exit 1
1994+ ;;
1995+esac
1996+
1997+# Check if we can create a virtualenv
1998+if [ ! -d $(dirname $VENV_PATH) ]; then
1999+ echo "This script requires $(dirname $VENV_PATH) directory to exist"
2000+ echo "You can use different directory by passing it as argument"
2001+ echo "For a quick temporary location just pass /tmp/venv"
2002+ exit 1
2003+fi
2004+
2005+# Check if there's one already there
2006+if [ -d $VENV_PATH ]; then
2007+ echo "$VENV_PATH seems to already exist"
2008+ exit 1
2009+fi
2010+
2011+# Ensure that each required package is installed
2012+for pkg_name in $required_pkgs; do
2013+ # Ensure virtualenv is installed
2014+ if [ "$(dpkg -s $pkg_name 2>/dev/null | grep '^Status' 2>/dev/null)" != "Status: install ok installed" ]; then
2015+ if [ "$install_missing" -eq 1 ]; then
2016+ echo "Installing required package: $pkg_name"
2017+ sudo apt-get install $pkg_name --yes
2018+ else
2019+ echo "Required package is not installed: '$pkg_name'"
2020+ echo "Either install it manually with:"
2021+ echo "$ sudo apt-get install $pkg_name"
2022+ echo "Or rerun this script with --install-missing"
2023+ exit 1
2024+ fi
2025+ fi
2026+done
2027+
2028+# Create a virtualenv
2029+if [ $enable_system_site -eq 1 ]; then
2030+ virtualenv --system-site-packages -p python3 $VENV_PATH
2031+else
2032+ virtualenv -p python3 $VENV_PATH
2033+fi
2034+
2035+# Activate it to install additional stuff
2036+. $VENV_PATH/bin/activate
2037+
2038+# Install / upgrade distribute
2039+if [ $install_distribute -eq 1 ]; then
2040+ pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/coverage-3.6.tar.gz
2041+fi
2042+
2043+# Install / upgrade pip
2044+if [ $install_pip -eq 1 ]; then
2045+ pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/pip-1.3.1.tar.gz
2046+fi
2047+
2048+# Install coverage if required
2049+if [ $install_coverage -eq 1 ]; then
2050+ pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/coverage-3.6.tar.gz
2051+fi
2052+
2053+# Install requests if required
2054+if [ $install_requests -eq 1 ]; then
2055+ pip install --upgrade https://github.com/checkbox/external-tarballs/raw/master/pypi/requests-1.1.0.tar.gz
2056+fi
2057+
2058+# "develop" plainbox
2059+http_proxy=http://127.0.0.1:9/ python3 setup.py develop
2060+
2061+echo "To activate your virtualenv run:"
2062+echo "$ . $VENV_PATH/bin/activate"
2063
2064=== added directory 'plainbox/plainbox'
2065=== added directory 'plainbox/plainbox/impl'
2066=== added file 'plainbox/plainbox/impl/box.py.OTHER'
2067--- plainbox/plainbox/impl/box.py.OTHER 1970-01-01 00:00:00 +0000
2068+++ plainbox/plainbox/impl/box.py.OTHER 2013-08-28 12:37:27 +0000
2069@@ -0,0 +1,130 @@
2070+# This file is part of Checkbox.
2071+#
2072+# Copyright 2012 Canonical Ltd.
2073+# Written by:
2074+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
2075+#
2076+# Checkbox is free software: you can redistribute it and/or modify
2077+# it under the terms of the GNU General Public License as published by
2078+# the Free Software Foundation, either version 3 of the License, or
2079+# (at your option) any later version.
2080+#
2081+# Checkbox is distributed in the hope that it will be useful,
2082+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2083+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2084+# GNU General Public License for more details.
2085+#
2086+# You should have received a copy of the GNU General Public License
2087+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
2088+
2089+"""
2090+:mod:`plainbox.impl.box` -- command line interface
2091+==================================================
2092+
2093+.. warning::
2094+
2095+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
2096+"""
2097+
2098+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
2099+from argparse import _ as argparse_gettext
2100+from logging import basicConfig
2101+from logging import getLogger
2102+import argparse
2103+import sys
2104+
2105+from plainbox import __version__ as version
2106+from plainbox.impl.applogic import PlainBoxConfig
2107+from plainbox.impl.checkbox import CheckBox
2108+from plainbox.impl.commands.run import RunCommand
2109+from plainbox.impl.commands.selftest import SelfTestCommand
2110+from plainbox.impl.commands.special import SpecialCommand
2111+from plainbox.impl.commands.sru import SRUCommand
2112+from plainbox.impl.commands.check_config import CheckConfigCommand
2113+
2114+
2115+logger = getLogger("plainbox.box")
2116+
2117+
2118+class PlainBox:
2119+ """
2120+ High-level plainbox object
2121+ """
2122+
2123+ def __init__(self):
2124+ self._checkbox = CheckBox()
2125+
2126+ def main(self, argv=None):
2127+ # TODO: setup sane logging system that works just as well for Joe user
2128+ # that runs checkbox from the CD as well as for checkbox developers and
2129+ # custom debugging needs. It would be perfect^Hdesirable not to create
2130+ # another broken, never-rotated, uncapped logging crap that kills my
2131+ # SSD by writing junk to ~/.cache/
2132+ basicConfig(level="WARNING")
2133+ config = PlainBoxConfig.get()
2134+ parser = self._construct_parser(config)
2135+ ns = parser.parse_args(argv)
2136+ # Set the desired log level
2137+ getLogger("").setLevel(ns.log_level)
2138+ # Argh the horrror!
2139+ #
2140+ # Since CPython revision cab204a79e09 (landed for python3.3)
2141+ # http://hg.python.org/cpython/diff/cab204a79e09/Lib/argparse.py
2142+ # the argparse module behaves differently than it did in python3.2
2143+ #
2144+ # In practical terms subparsers are now optional in 3.3 so all of the
2145+ # commands are no longer required parameters.
2146+ #
2147+ # To compensate, on python3.3 and beyond, when the user just runs
2148+ # plainbox without specifying the command, we manually, explicitly do
2149+ # what python3.2 did: call parser.error(_('too few arguments'))
2150+ if (sys.version_info[:2] >= (3, 3)
2151+ and getattr(ns, "command", None) is None):
2152+ parser.error(argparse_gettext("too few arguments"))
2153+ else:
2154+ return ns.command.invoked(ns)
2155+
2156+ def _construct_parser(self, config):
2157+ parser = ArgumentParser(
2158+ prog="plainbox", formatter_class=ArgumentDefaultsHelpFormatter)
2159+ parser.add_argument(
2160+ "-v", "--version", action="version",
2161+ version="{}.{}.{}".format(*version[:3]))
2162+ parser.add_argument(
2163+ "-l", "--log-level", action="store",
2164+ choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
2165+ default='WARNING',
2166+ help=argparse.SUPPRESS)
2167+ subparsers = parser.add_subparsers()
2168+ RunCommand(self._checkbox).register_parser(subparsers)
2169+ SpecialCommand(self._checkbox).register_parser(subparsers)
2170+ SelfTestCommand().register_parser(subparsers)
2171+ SRUCommand(self._checkbox, config).register_parser(subparsers)
2172+ CheckConfigCommand(config).register_parser(subparsers)
2173+ #group = parser.add_argument_group(title="user interface options")
2174+ #group.add_argument(
2175+ # "-u", "--ui", action="store",
2176+ # default=None, choices=('headless', 'text', 'graphics'),
2177+ # help="select the UI front-end (defaults to auto)")
2178+ return parser
2179+
2180+
2181+def main(argv=None):
2182+ # Instantiate a global plainbox instance
2183+ # XXX: Allow one to control the checkbox= argument via
2184+ # environment or config.
2185+ box = PlainBox()
2186+ retval = box.main(argv)
2187+ raise SystemExit(retval)
2188+
2189+
2190+def get_builtin_jobs():
2191+ raise NotImplementedError("get_builtin_jobs() not implemented")
2192+
2193+
2194+def save(something, somewhere):
2195+ raise NotImplementedError("save() not implemented")
2196+
2197+
2198+def run(*args, **kwargs):
2199+ raise NotImplementedError("run() not implemented")
2200
2201=== added directory 'plainbox/plainbox/impl/commands'
2202=== added file 'plainbox/plainbox/impl/commands/checkbox.py.OTHER'
2203--- plainbox/plainbox/impl/commands/checkbox.py.OTHER 1970-01-01 00:00:00 +0000
2204+++ plainbox/plainbox/impl/commands/checkbox.py.OTHER 2013-08-28 12:37:27 +0000
2205@@ -0,0 +1,100 @@
2206+# This file is part of Checkbox.
2207+#
2208+# Copyright 2012-2013 Canonical Ltd.
2209+# Written by:
2210+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
2211+#
2212+# Checkbox is free software: you can redistribute it and/or modify
2213+# it under the terms of the GNU General Public License as published by
2214+# the Free Software Foundation, either version 3 of the License, or
2215+# (at your option) any later version.
2216+#
2217+# Checkbox is distributed in the hope that it will be useful,
2218+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2219+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2220+# GNU General Public License for more details.
2221+#
2222+# You should have received a copy of the GNU General Public License
2223+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
2224+
2225+"""
2226+:mod:`plainbox.impl.commands.checkbox` -- mix-in for checkbox commands
2227+======================================================================
2228+
2229+.. warning::
2230+
2231+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
2232+"""
2233+
2234+import re
2235+from argparse import FileType
2236+
2237+
2238+class CheckBoxInvocationMixIn:
2239+
2240+ def __init__(self, checkbox):
2241+ self.checkbox = checkbox
2242+
2243+ def get_job_list(self, ns):
2244+ """
2245+ Load and return a list of JobDefinition instances
2246+ """
2247+ return self.checkbox.get_builtin_jobs()
2248+
2249+ def _get_matching_job_list(self, ns, job_list):
2250+ # Find jobs that matched patterns
2251+ matching_job_list = []
2252+ # Pre-seed the include pattern list with data read from
2253+ # the whitelist file.
2254+ if ns.whitelist:
2255+ ns.include_pattern_list.extend([
2256+ pattern.strip()
2257+ for pattern in ns.whitelist.readlines()])
2258+ # Decide which of the known jobs to include
2259+ for job in job_list:
2260+ # Reject all jobs that match any of the exclude
2261+ # patterns, matching strictly from the start to
2262+ # the end of the line.
2263+ for pattern in ns.exclude_pattern_list:
2264+ regexp_pattern = r"^{pattern}$".format(pattern=pattern)
2265+ if re.match(regexp_pattern, job.name):
2266+ break
2267+ else:
2268+ # Then accept (include) all job that matches
2269+ # any of include patterns, matching strictly
2270+ # from the start to the end of the line.
2271+ for pattern in ns.include_pattern_list:
2272+ regexp_pattern = r"^{pattern}$".format(pattern=pattern)
2273+ if re.match(regexp_pattern, job.name):
2274+ matching_job_list.append(job)
2275+ break
2276+ return matching_job_list
2277+
2278+
2279+class CheckBoxCommandMixIn:
2280+ """
2281+ Mix-in class for plainbox commands that want to discover and load checkbox
2282+ jobs
2283+ """
2284+
2285+ def enhance_parser(self, parser):
2286+ """
2287+ Add common options for job selection to an existing parser
2288+ """
2289+ group = parser.add_argument_group(title="job definition options")
2290+ group.add_argument(
2291+ '-i', '--include-pattern', action="append",
2292+ metavar='PATTERN', default=[], dest='include_pattern_list',
2293+ help=("Run jobs matching the given regular expression. Matches "
2294+ "from the start to the end of the line."))
2295+ group.add_argument(
2296+ '-x', '--exclude-pattern', action="append",
2297+ metavar="PATTERN", default=[], dest='exclude_pattern_list',
2298+ help=("Do not run jobs matching the given regular expression. "
2299+ "Matches from the start to the end of the line."))
2300+ # TODO: Find a way to handle the encoding of the file
2301+ group.add_argument(
2302+ '-w', '--whitelist',
2303+ metavar="WHITELIST",
2304+ type=FileType("rt"),
2305+ help="Load whitelist containing run patterns")
2306
2307=== added file 'plainbox/plainbox/impl/commands/run.py.OTHER'
2308--- plainbox/plainbox/impl/commands/run.py.OTHER 1970-01-01 00:00:00 +0000
2309+++ plainbox/plainbox/impl/commands/run.py.OTHER 2013-08-28 12:37:27 +0000
2310@@ -0,0 +1,340 @@
2311+# This file is part of Checkbox.
2312+#
2313+# Copyright 2012-2013 Canonical Ltd.
2314+# Written by:
2315+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
2316+#
2317+# Checkbox is free software: you can redistribute it and/or modify
2318+# it under the terms of the GNU General Public License as published by
2319+# the Free Software Foundation, either version 3 of the License, or
2320+# (at your option) any later version.
2321+#
2322+# Checkbox is distributed in the hope that it will be useful,
2323+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2324+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2325+# GNU General Public License for more details.
2326+#
2327+# You should have received a copy of the GNU General Public License
2328+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
2329+
2330+"""
2331+:mod:`plainbox.impl.commands.run` -- run sub-command
2332+====================================================
2333+
2334+.. warning::
2335+
2336+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
2337+"""
2338+
2339+from argparse import FileType
2340+from logging import getLogger
2341+from os.path import join
2342+from shutil import copyfileobj
2343+import io
2344+import sys
2345+
2346+from requests.exceptions import ConnectionError, InvalidSchema, HTTPError
2347+
2348+from plainbox.impl.commands import PlainBoxCommand
2349+from plainbox.impl.commands.checkbox import CheckBoxCommandMixIn
2350+from plainbox.impl.commands.checkbox import CheckBoxInvocationMixIn
2351+from plainbox.impl.depmgr import DependencyDuplicateError
2352+from plainbox.impl.exporter import ByteStringStreamTranslator
2353+from plainbox.impl.exporter import get_all_exporters
2354+from plainbox.impl.result import JobResult
2355+from plainbox.impl.runner import JobRunner
2356+from plainbox.impl.runner import authenticate_warmup
2357+from plainbox.impl.runner import slugify
2358+from plainbox.impl.session import SessionState
2359+from plainbox.impl.transport import get_all_transports
2360+
2361+
2362+logger = getLogger("plainbox.commands.run")
2363+
2364+
2365+class RunInvocation(CheckBoxInvocationMixIn):
2366+
2367+ def __init__(self, checkbox, ns):
2368+ super(RunInvocation, self).__init__(checkbox)
2369+ self.ns = ns
2370+
2371+ def run(self):
2372+ ns = self.ns
2373+ if ns.output_format == '?':
2374+ self._print_output_format_list(ns)
2375+ return 0
2376+ elif ns.output_options == '?':
2377+ self._print_output_option_list(ns)
2378+ return 0
2379+ elif ns.transport == '?':
2380+ self._print_transport_list(ns)
2381+ return 0
2382+ else:
2383+ exporter = self._prepare_exporter(ns)
2384+ transport = self._prepare_transport(ns)
2385+ job_list = self.get_job_list(ns)
2386+ return self._run_jobs(ns, job_list, exporter, transport)
2387+
2388+ def _print_output_format_list(self, ns):
2389+ print("Available output formats: {}".format(
2390+ ', '.join(get_all_exporters())))
2391+
2392+ def _print_output_option_list(self, ns):
2393+ print("Each format may support a different set of options")
2394+ for name, exporter_cls in get_all_exporters().items():
2395+ print("{}: {}".format(
2396+ name, ", ".join(exporter_cls.supported_option_list)))
2397+
2398+ def _print_transport_list(self, ns):
2399+ print("Available transports: {}".format(
2400+ ', '.join(get_all_transports())))
2401+
2402+ def _prepare_exporter(self, ns):
2403+ exporter_cls = get_all_exporters()[ns.output_format]
2404+ if ns.output_options:
2405+ option_list = ns.output_options.split(',')
2406+ else:
2407+ option_list = None
2408+ try:
2409+ exporter = exporter_cls(option_list)
2410+ except ValueError as exc:
2411+ raise SystemExit(str(exc))
2412+ return exporter
2413+
2414+ def _prepare_transport(self, ns):
2415+ if ns.transport not in get_all_transports():
2416+ return None
2417+ transport_cls = get_all_transports()[ns.transport]
2418+ try:
2419+ return transport_cls(ns.transport_where, ns.transport_options)
2420+ except ValueError as exc:
2421+ raise SystemExit(str(exc))
2422+
2423+ def ask_for_resume(self, prompt=None, allowed=None):
2424+ # FIXME: Add support/callbacks for a GUI
2425+ if prompt is None:
2426+ prompt = "Do you want to resume the previous session [Y/n]? "
2427+ if allowed is None:
2428+ allowed = ('', 'y', 'Y', 'n', 'N')
2429+ answer = None
2430+ while answer not in allowed:
2431+ answer = input(prompt)
2432+ return False if answer in ('n', 'N') else True
2433+
2434+ def _run_jobs(self, ns, job_list, exporter, transport=None):
2435+ # Ask the password before anything else in order to run jobs requiring
2436+ # privileges
2437+ print("[ Authentication ]".center(80, '='))
2438+ return_code = authenticate_warmup()
2439+ if return_code:
2440+ raise SystemExit(return_code)
2441+ # Compute the run list, this can give us notification about problems in
2442+ # the selected jobs. Currently we just display each problem
2443+ matching_job_list = self._get_matching_job_list(ns, job_list)
2444+ print("[ Analyzing Jobs ]".center(80, '='))
2445+ # Create a session that handles most of the stuff needed to run jobs
2446+ try:
2447+ session = SessionState(job_list)
2448+ except DependencyDuplicateError as exc:
2449+ # Handle possible DependencyDuplicateError that can happen if
2450+ # someone is using plainbox for job development.
2451+ print("The job database you are currently using is broken")
2452+ print("At least two jobs contend for the name {0}".format(
2453+ exc.job.name))
2454+ print("First job defined in: {0}".format(exc.job.origin))
2455+ print("Second job defined in: {0}".format(
2456+ exc.duplicate_job.origin))
2457+ raise SystemExit(exc)
2458+ with session.open():
2459+ if session.previous_session_file():
2460+ if self.ask_for_resume():
2461+ session.resume()
2462+ else:
2463+ session.clean()
2464+ self._update_desired_job_list(session, matching_job_list)
2465+ if (sys.stdin.isatty() and sys.stdout.isatty() and not
2466+ ns.not_interactive):
2467+ outcome_callback = self.ask_for_outcome
2468+ else:
2469+ outcome_callback = None
2470+ runner = JobRunner(
2471+ session.session_dir,
2472+ session.jobs_io_log_dir,
2473+ outcome_callback=outcome_callback,
2474+ dry_run=ns.dry_run
2475+ )
2476+ self._run_jobs_with_session(ns, session, runner)
2477+ # Get a stream with exported session data.
2478+ exported_stream = io.BytesIO()
2479+ data_subset = exporter.get_session_data_subset(session)
2480+ exporter.dump(data_subset, exported_stream)
2481+ exported_stream.seek(0) # Need to rewind the file, puagh
2482+ # Write the stream to file if requested
2483+ self._save_results(ns.output_file, exported_stream)
2484+ # Invoke the transport?
2485+ if transport:
2486+ exported_stream.seek(0)
2487+ try:
2488+ transport.send(exported_stream.read())
2489+ except InvalidSchema as exc:
2490+ print("Invalid destination URL: {0}".format(exc))
2491+ except ConnectionError as exc:
2492+ print(("Unable to connect "
2493+ "to destination URL: {0}").format(exc))
2494+ except HTTPError as exc:
2495+ print(("Server returned an error when "
2496+ "receiving or processing: {0}").format(exc))
2497+
2498+ # FIXME: sensible return value
2499+ return 0
2500+
2501+ def _save_results(self, output_file, input_stream):
2502+ if output_file is sys.stdout:
2503+ print("[ Results ]".center(80, '='))
2504+ # This requires a bit more finesse, as exporters output bytes
2505+ # and stdout needs a string.
2506+ translating_stream = ByteStringStreamTranslator(
2507+ output_file, "utf-8")
2508+ copyfileobj(input_stream, translating_stream)
2509+ else:
2510+ print("Saving results to {}".format(output_file.name))
2511+ copyfileobj(input_stream, output_file)
2512+ if output_file is not sys.stdout:
2513+ output_file.close()
2514+
2515+ def ask_for_outcome(self, prompt=None, allowed=None):
2516+ if prompt is None:
2517+ prompt = "what is the outcome? "
2518+ if allowed is None:
2519+ allowed = (JobResult.OUTCOME_PASS,
2520+ JobResult.OUTCOME_FAIL,
2521+ JobResult.OUTCOME_SKIP)
2522+ answer = None
2523+ while answer not in allowed:
2524+ print("Allowed answers are: {}".format(", ".join(allowed)))
2525+ answer = input(prompt)
2526+ return answer
2527+
2528+ def _update_desired_job_list(self, session, desired_job_list):
2529+ problem_list = session.update_desired_job_list(desired_job_list)
2530+ if problem_list:
2531+ print("[ Warning ]".center(80, '*'))
2532+ print("There were some problems with the selected jobs")
2533+ for problem in problem_list:
2534+ print(" * {}".format(problem))
2535+ print("Problematic jobs will not be considered")
2536+
2537+ def _run_jobs_with_session(self, ns, session, runner):
2538+ # TODO: run all resource jobs concurrently with multiprocessing
2539+ # TODO: make local job discovery nicer, it would be best if
2540+ # desired_jobs could be managed entirely internally by SesionState. In
2541+ # such case the list of jobs to run would be changed during iteration
2542+ # but would be otherwise okay).
2543+ print("[ Running All Jobs ]".center(80, '='))
2544+ again = True
2545+ while again:
2546+ again = False
2547+ for job in session.run_list:
2548+ # Skip jobs that already have result, this is only needed when
2549+ # we run over the list of jobs again, after discovering new
2550+ # jobs via the local job output
2551+ if session.job_state_map[job.name].result.outcome is not None:
2552+ continue
2553+ self._run_single_job_with_session(ns, session, runner, job)
2554+ session.persistent_save()
2555+ if job.plugin == "local":
2556+ # After each local job runs rebuild the list of matching
2557+ # jobs and run everything again
2558+ new_matching_job_list = self._get_matching_job_list(
2559+ ns, session.job_list)
2560+ self._update_desired_job_list(
2561+ session, new_matching_job_list)
2562+ again = True
2563+ break
2564+
2565+ def _run_single_job_with_session(self, ns, session, runner, job):
2566+ print("[ {} ]".format(job.name).center(80, '-'))
2567+ if job.description is not None:
2568+ print(job.description)
2569+ print("^" * len(job.description.splitlines()[-1]))
2570+ print()
2571+ job_state = session.job_state_map[job.name]
2572+ logger.debug("Job name: %s", job.name)
2573+ logger.debug("Plugin: %s", job.plugin)
2574+ logger.debug("Direct dependencies: %s", job.get_direct_dependencies())
2575+ logger.debug("Resource dependencies: %s",
2576+ job.get_resource_dependencies())
2577+ logger.debug("Resource program: %r", job.requires)
2578+ logger.debug("Command: %r", job.command)
2579+ logger.debug("Can start: %s", job_state.can_start())
2580+ logger.debug("Readiness: %s", job_state.get_readiness_description())
2581+ if job_state.can_start():
2582+ print("Running... (output in {}.*)".format(
2583+ join(session.jobs_io_log_dir, slugify(job.name))))
2584+ job_result = runner.run_job(job)
2585+ print("Outcome: {}".format(job_result.outcome))
2586+ print("Comments: {}".format(job_result.comments))
2587+ else:
2588+ job_result = JobResult({
2589+ 'job': job,
2590+ 'outcome': JobResult.OUTCOME_NOT_SUPPORTED,
2591+ 'comments': job_state.get_readiness_description()
2592+ })
2593+ if job_result is not None:
2594+ session.update_job_result(job, job_result)
2595+
2596+
2597+class RunCommand(PlainBoxCommand, CheckBoxCommandMixIn):
2598+
2599+ def __init__(self, checkbox):
2600+ self.checkbox = checkbox
2601+
2602+ def invoked(self, ns):
2603+ return RunInvocation(self.checkbox, ns).run()
2604+
2605+ def register_parser(self, subparsers):
2606+ parser = subparsers.add_parser("run", help="run a test job")
2607+ parser.set_defaults(command=self)
2608+ group = parser.add_argument_group(title="user interface options")
2609+ group.add_argument(
2610+ '--not-interactive', action='store_true',
2611+ help="Skip tests that require interactivity")
2612+ group.add_argument(
2613+ '-n', '--dry-run', action='store_true',
2614+ help="Don't actually run any jobs")
2615+ group = parser.add_argument_group("output options")
2616+ assert 'text' in get_all_exporters()
2617+ group.add_argument(
2618+ '-f', '--output-format', default='text',
2619+ metavar='FORMAT', choices=['?'] + list(
2620+ get_all_exporters().keys()),
2621+ help=('Save test results in the specified FORMAT'
2622+ ' (pass ? for a list of choices)'))
2623+ group.add_argument(
2624+ '-p', '--output-options', default='',
2625+ metavar='OPTIONS',
2626+ help=('Comma-separated list of options for the export mechanism'
2627+ ' (pass ? for a list of choices)'))
2628+ group.add_argument(
2629+ '-o', '--output-file', default='-',
2630+ metavar='FILE', type=FileType("wb"),
2631+ help=('Save test results to the specified FILE'
2632+ ' (or to stdout if FILE is -)'))
2633+ group.add_argument(
2634+ '-t', '--transport',
2635+ metavar='TRANSPORT', choices=['?'] + list(
2636+ get_all_transports().keys()),
2637+ help=('use TRANSPORT to send results somewhere'
2638+ ' (pass ? for a list of choices)'))
2639+ group.add_argument(
2640+ '--transport-where',
2641+ metavar='WHERE',
2642+ help=('Where to send data using the selected transport.'
2643+ ' This is passed as-is and is transport-dependent.'))
2644+ group.add_argument(
2645+ '--transport-options',
2646+ metavar='OPTIONS',
2647+ help=('Comma-separated list of key-value options (k=v) to '
2648+ ' be passed to the transport.'))
2649+ # Call enhance_parser from CheckBoxCommandMixIn
2650+ self.enhance_parser(parser)
2651
2652=== added file 'plainbox/plainbox/impl/commands/special.py.OTHER'
2653--- plainbox/plainbox/impl/commands/special.py.OTHER 1970-01-01 00:00:00 +0000
2654+++ plainbox/plainbox/impl/commands/special.py.OTHER 2013-08-28 12:37:27 +0000
2655@@ -0,0 +1,159 @@
2656+# This file is part of Checkbox.
2657+#
2658+# Copyright 2012-2013 Canonical Ltd.
2659+# Written by:
2660+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
2661+#
2662+# Checkbox is free software: you can redistribute it and/or modify
2663+# it under the terms of the GNU General Public License as published by
2664+# the Free Software Foundation, either version 3 of the License, or
2665+# (at your option) any later version.
2666+#
2667+# Checkbox is distributed in the hope that it will be useful,
2668+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2669+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2670+# GNU General Public License for more details.
2671+#
2672+# You should have received a copy of the GNU General Public License
2673+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
2674+
2675+"""
2676+:mod:`plainbox.impl.commands.special` -- special sub-command
2677+============================================================
2678+
2679+.. warning::
2680+
2681+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
2682+"""
2683+
2684+from logging import getLogger
2685+
2686+from plainbox.impl.commands import PlainBoxCommand
2687+from plainbox.impl.commands.checkbox import CheckBoxCommandMixIn
2688+from plainbox.impl.commands.checkbox import CheckBoxInvocationMixIn
2689+
2690+
2691+logger = getLogger("plainbox.commands.special")
2692+
2693+
2694+class SpecialInvocation(CheckBoxInvocationMixIn):
2695+
2696+ def __init__(self, checkbox, ns):
2697+ super(SpecialInvocation, self).__init__(checkbox)
2698+ self.ns = ns
2699+
2700+ def run(self):
2701+ ns = self.ns
2702+ job_list = self.get_job_list(ns)
2703+ # Now either do a special action or run the jobs
2704+ if ns.special == "list-jobs":
2705+ self._print_job_list(ns, job_list)
2706+ elif ns.special == "list-job-hashes":
2707+ self._print_job_hash_list(ns, job_list)
2708+ elif ns.special == "list-expr":
2709+ self._print_expression_list(ns, job_list)
2710+ elif ns.special == "dep-graph":
2711+ self._print_dot_graph(ns, job_list)
2712+ # Always succeed
2713+ return 0
2714+
2715+ def _get_matching_job_list(self, ns, job_list):
2716+ matching_job_list = super(
2717+ SpecialInvocation, self)._get_matching_job_list(ns, job_list)
2718+ # As a special exception, when ns.special is set and we're either
2719+ # listing jobs or job dependencies then when no run pattern was
2720+ # specified just operate on the whole set. The ns.special check
2721+ # prevents people starting plainbox from accidentally running _all_
2722+ # jobs without prompting.
2723+ if ns.special is not None and not ns.include_pattern_list:
2724+ matching_job_list = job_list
2725+ return matching_job_list
2726+
2727+ def _print_job_list(self, ns, job_list):
2728+ matching_job_list = self._get_matching_job_list(ns, job_list)
2729+ for job in matching_job_list:
2730+ print("{}".format(job))
2731+
2732+ def _print_job_hash_list(self, ns, job_list):
2733+ matching_job_list = self._get_matching_job_list(ns, job_list)
2734+ for job in matching_job_list:
2735+ print("{} {}".format(job.get_checksum(), job))
2736+
2737+ def _print_expression_list(self, ns, job_list):
2738+ matching_job_list = self._get_matching_job_list(ns, job_list)
2739+ expressions = set()
2740+ for job in matching_job_list:
2741+ prog = job.get_resource_program()
2742+ if prog is not None:
2743+ for expression in prog.expression_list:
2744+ expressions.add(expression.text)
2745+ for expression in sorted(expressions):
2746+ print(expression)
2747+
2748+ def _print_dot_graph(self, ns, job_list):
2749+ matching_job_list = self._get_matching_job_list(ns, job_list)
2750+ print('digraph dependency_graph {')
2751+ print('\tnode [shape=box];')
2752+ for job in matching_job_list:
2753+ if job.plugin == "resource":
2754+ print('\t"{}" [shape=ellipse,color=blue];'.format(job.name))
2755+ elif job.plugin == "attachment":
2756+ print('\t"{}" [color=green];'.format(job.name))
2757+ elif job.plugin == "local":
2758+ print('\t"{}" [shape=invtriangle,color=red];'.format(
2759+ job.name))
2760+ elif job.plugin == "shell":
2761+ print('\t"{}" [];'.format(job.name))
2762+ elif job.plugin in ("manual", "user-verify", "user-interact"):
2763+ print('\t"{}" [color=orange];'.format(job.name))
2764+ for dep_name in job.get_direct_dependencies():
2765+ print('\t"{}" -> "{}";'.format(job.name, dep_name))
2766+ prog = job.get_resource_program()
2767+ if ns.dot_resources and prog is not None:
2768+ for expression in prog.expression_list:
2769+ print('\t"{}" [shape=ellipse,color=blue];'.format(
2770+ expression.resource_name))
2771+ print('\t"{}" -> "{}" [style=dashed, label="{}"];'.format(
2772+ job.name, expression.resource_name,
2773+ expression.text.replace('"', "'")))
2774+ print("}")
2775+
2776+
2777+class SpecialCommand(PlainBoxCommand, CheckBoxCommandMixIn):
2778+ """
2779+ Implementation of ``$ plainbox special``
2780+ """
2781+
2782+ def __init__(self, checkbox):
2783+ self.checkbox = checkbox
2784+
2785+ def invoked(self, ns):
2786+ return SpecialInvocation(self.checkbox, ns).run()
2787+
2788+ def register_parser(self, subparsers):
2789+ parser = subparsers.add_parser(
2790+ "special", help="special/internal commands")
2791+ parser.set_defaults(command=self)
2792+ group = parser.add_mutually_exclusive_group(required=True)
2793+ group.add_argument(
2794+ '-j', '--list-jobs',
2795+ help="List jobs instead of running them",
2796+ action="store_const", const="list-jobs", dest="special")
2797+ group.add_argument(
2798+ '-J', '--list-job-hashes',
2799+ help="List jobs with hashes instead of running them",
2800+ action="store_const", const="list-job-hashes", dest="special")
2801+ group.add_argument(
2802+ '-e', '--list-expressions',
2803+ help="List all unique resource expressions",
2804+ action="store_const", const="list-expr", dest="special")
2805+ group.add_argument(
2806+ '-d', '--dot',
2807+ help="Print a graph of jobs instead of running them",
2808+ action="store_const", const="dep-graph", dest="special")
2809+ parser.add_argument(
2810+ '--dot-resources',
2811+ help="Render resource relationships (for --dot)",
2812+ action='store_true')
2813+ # Call enhance_parser from CheckBoxCommandMixIn
2814+ self.enhance_parser(parser)
2815
2816=== added file 'plainbox/plainbox/impl/commands/sru.py.OTHER'
2817--- plainbox/plainbox/impl/commands/sru.py.OTHER 1970-01-01 00:00:00 +0000
2818+++ plainbox/plainbox/impl/commands/sru.py.OTHER 2013-08-28 12:37:27 +0000
2819@@ -0,0 +1,271 @@
2820+# This file is part of Checkbox.
2821+#
2822+#
2823+# Copyright 2013 Canonical Ltd.
2824+# Written by:
2825+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
2826+#
2827+# Checkbox is free software: you can redistribute it and/or modify
2828+# it under the terms of the GNU General Public License as published by
2829+# the Free Software Foundation, either version 3 of the License, or
2830+# (at your option) any later version.
2831+#
2832+# Checkbox is distributed in the hope that it will be useful,
2833+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2834+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2835+# GNU General Public License for more details.
2836+#
2837+# You should have received a copy of the GNU General Public License
2838+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
2839+
2840+"""
2841+:mod:`plainbox.impl.commands.sru` -- sru sub-command
2842+====================================================
2843+
2844+.. warning::
2845+
2846+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
2847+"""
2848+import logging
2849+import os
2850+import tempfile
2851+
2852+from requests.exceptions import ConnectionError, InvalidSchema, HTTPError
2853+
2854+from plainbox.impl.applogic import get_matching_job_list
2855+from plainbox.impl.applogic import run_job_if_possible
2856+from plainbox.impl.checkbox import WhiteList
2857+from plainbox.impl.commands import PlainBoxCommand
2858+from plainbox.impl.commands.check_config import CheckConfigInvocation
2859+from plainbox.impl.config import ValidationError, Unset
2860+from plainbox.impl.depmgr import DependencyDuplicateError
2861+from plainbox.impl.exporter import ByteStringStreamTranslator
2862+from plainbox.impl.exporter.xml import XMLSessionStateExporter
2863+from plainbox.impl.runner import JobRunner
2864+from plainbox.impl.session import SessionState
2865+from plainbox.impl.transport.certification import CertificationTransport
2866+from plainbox.impl.transport.certification import InvalidSecureIDError
2867+
2868+
2869+logger = logging.getLogger("plainbox.commands.sru")
2870+
2871+
2872+class _SRUInvocation:
2873+ """
2874+ Helper class instantiated to perform a particular invocation of the sru
2875+ command. Unlike the SRU command itself, this class is instantiated each
2876+ time.
2877+ """
2878+
2879+ def __init__(self, checkbox, config, ns):
2880+ self.checkbox = checkbox
2881+ self.config = config
2882+ self.ns = ns
2883+ self.whitelist = WhiteList.from_file(os.path.join(
2884+ self.checkbox.whitelists_dir, "sru.whitelist"))
2885+ self.job_list = self.checkbox.get_builtin_jobs()
2886+ # XXX: maybe allow specifying system_id from command line?
2887+ self.exporter = XMLSessionStateExporter(system_id=None)
2888+ self.session = None
2889+ self.runner = None
2890+
2891+ def run(self):
2892+ # Compute the run list, this can give us notification about problems in
2893+ # the selected jobs. Currently we just display each problem
2894+ # Create a session that handles most of the stuff needed to run jobs
2895+ try:
2896+ self.session = SessionState(self.job_list)
2897+ except DependencyDuplicateError as exc:
2898+ # Handle possible DependencyDuplicateError that can happen if
2899+ # someone is using plainbox for job development.
2900+ print("The job database you are currently using is broken")
2901+ print("At least two jobs contend for the name {0}".format(
2902+ exc.job.name))
2903+ print("First job defined in: {0}".format(exc.job.origin))
2904+ print("Second job defined in: {0}".format(
2905+ exc.duplicate_job.origin))
2906+ raise SystemExit(exc)
2907+ with self.session.open():
2908+ self._set_job_selection()
2909+ self.runner = JobRunner(
2910+ self.session.session_dir,
2911+ self.session.jobs_io_log_dir,
2912+ command_io_delegate=self,
2913+ outcome_callback=None, # SRU runs are never interactive
2914+ dry_run=self.ns.dry_run
2915+ )
2916+ self._run_all_jobs()
2917+ if self.config.fallback_file is not Unset:
2918+ self._save_results()
2919+ self._submit_results()
2920+ # FIXME: sensible return value
2921+ return 0
2922+
2923+ def _set_job_selection(self):
2924+ desired_job_list = get_matching_job_list(self.job_list, self.whitelist)
2925+ problem_list = self.session.update_desired_job_list(desired_job_list)
2926+ if problem_list:
2927+ logger.warning("There were some problems with the selected jobs")
2928+ for problem in problem_list:
2929+ logger.warning("- %s", problem)
2930+ logger.warning("Problematic jobs will not be considered")
2931+
2932+ def _save_results(self):
2933+ print("Saving results to {0}".format(self.config.fallback_file))
2934+ data = self.exporter.get_session_data_subset(self.session)
2935+ with open(self.config.fallback_file, "wt", encoding="UTF-8") as stream:
2936+ translating_stream = ByteStringStreamTranslator(stream, "UTF-8")
2937+ self.exporter.dump(data, translating_stream)
2938+
2939+ def _submit_results(self):
2940+ print("Submitting results to {0} for secure_id {1}".format(
2941+ self.config.c3_url, self.config.secure_id))
2942+ options_string = "secure_id={0}".format(self.config.secure_id)
2943+ # Create the transport object
2944+ try:
2945+ transport = CertificationTransport(
2946+ self.config.c3_url, options_string, self.config)
2947+ except InvalidSecureIDError as exc:
2948+ print(exc)
2949+ return False
2950+ # Prepare the data for submission
2951+ data = self.exporter.get_session_data_subset(self.session)
2952+ with tempfile.NamedTemporaryFile(mode='w+b') as stream:
2953+ # Dump the data to the temporary file
2954+ self.exporter.dump(data, stream)
2955+ # Flush and rewind
2956+ stream.flush()
2957+ stream.seek(0)
2958+ try:
2959+ # Send the data, reading from the temporary file
2960+ result = transport.send(stream)
2961+ if 'url' in result:
2962+ print("Successfully sent, submission status at {0}".format(
2963+ result['url']))
2964+ else:
2965+ print("Successfully sent, server response: {0}".format(
2966+ result))
2967+
2968+ except InvalidSchema as exc:
2969+ print("Invalid destination URL: {0}".format(exc))
2970+ except ConnectionError as exc:
2971+ print("Unable to connect to destination URL: {0}".format(exc))
2972+ except HTTPError as exc:
2973+ print(("Server returned an error when "
2974+ "receiving or processing: {0}").format(exc))
2975+ except IOError as exc:
2976+ print("Problem reading a file: {0}".format(exc))
2977+
2978+ def _run_all_jobs(self):
2979+ again = True
2980+ while again:
2981+ again = False
2982+ for job in self.session.run_list:
2983+ # Skip jobs that already have result, this is only needed when
2984+ # we run over the list of jobs again, after discovering new
2985+ # jobs via the local job output
2986+ result = self.session.job_state_map[job.name].result
2987+ if result.outcome is not None:
2988+ continue
2989+ self._run_single_job(job)
2990+ self.session.persistent_save()
2991+ if job.plugin == "local":
2992+ # After each local job runs rebuild the list of matching
2993+ # jobs and run everything again
2994+ self._set_job_selection()
2995+ again = True
2996+ break
2997+
2998+ def _run_single_job(self, job):
2999+ print("- {}:".format(job.name), end=' ')
3000+ job_state, job_result = run_job_if_possible(
3001+ self.session, self.runner, self.config, job)
3002+ print("{0}".format(job_result.outcome))
3003+ if job_result.comments is not None:
3004+ print("comments: {0}".format(job_result.comments))
3005+ if job_state.readiness_inhibitor_list:
3006+ print("inhibitors:")
3007+ for inhibitor in job_state.readiness_inhibitor_list:
3008+ print(" * {}".format(inhibitor))
3009+ self.session.update_job_result(job, job_result)
3010+
3011+
3012+class SRUCommand(PlainBoxCommand):
3013+ """
3014+ Command for running Stable Release Update (SRU) tests.
3015+
3016+ Stable release updates are periodic fixes for nominated bugs that land in
3017+ existing supported Ubuntu releases. To ensure a certain level of quality
3018+ all SRU updates affecting hardware enablement are automatically tested
3019+ on a pool of certified machines.
3020+
3021+ This command is _temporary_ and will eventually migrate to the checkbox
3022+ side. Its intended lifecycle is for the development and validation of
3023+ plainbox core on realistic workloads.
3024+ """
3025+
3026+ def __init__(self, checkbox, config):
3027+ self.checkbox = checkbox
3028+ self.config = config
3029+
3030+ def invoked(self, ns):
3031+ # Copy command-line arguments over configuration variables
3032+ try:
3033+ if ns.secure_id:
3034+ self.config.secure_id = ns.secure_id
3035+ if ns.fallback_file and ns.fallback_file is not Unset:
3036+ self.config.fallback_file = ns.fallback_file
3037+ if ns.c3_url:
3038+ self.config.c3_url = ns.c3_url
3039+ except ValidationError as exc:
3040+ print("Configuration problems prevent running SRU tests")
3041+ print(exc)
3042+ return 1
3043+ # Run check-config, if requested
3044+ if ns.check_config:
3045+ retval = CheckConfigInvocation(self.config).run()
3046+ if retval != 0:
3047+ return retval
3048+ return _SRUInvocation(self.checkbox, self.config, ns).run()
3049+
3050+ def register_parser(self, subparsers):
3051+ parser = subparsers.add_parser(
3052+ "sru", help="run automated stable release update tests")
3053+ parser.set_defaults(command=self)
3054+ parser.add_argument(
3055+ "--check-config",
3056+ action="store_true",
3057+ help="Run plainbox check-config before starting")
3058+ group = parser.add_argument_group("sru-specific options")
3059+ # Set defaults from based on values from the config file
3060+ group.set_defaults(
3061+ secure_id=self.config.secure_id,
3062+ c3_url=self.config.c3_url,
3063+ fallback_file=self.config.fallback_file)
3064+ group.add_argument(
3065+ '--secure-id', metavar="SECURE-ID",
3066+ action='store',
3067+ # NOTE: --secure-id is optional only when set in a config file
3068+ required=self.config.secure_id is Unset,
3069+ help=("Associate submission with a machine using this SECURE-ID"
3070+ " (%(default)s)"))
3071+ group.add_argument(
3072+ '--fallback', metavar="FILE",
3073+ dest='fallback_file',
3074+ action='store',
3075+ default=Unset,
3076+ help=("If submission fails save the test report as FILE"
3077+ " (%(default)s)"))
3078+ group.add_argument(
3079+ '--destination', metavar="URL",
3080+ dest='c3_url',
3081+ action='store',
3082+ help=("POST the test report XML to this URL"
3083+ " (%(default)s)"))
3084+ group = parser.add_argument_group(title="execution options")
3085+ group.add_argument(
3086+ '-n', '--dry-run',
3087+ action='store_true',
3088+ default=False,
3089+ help=("Skip all usual jobs."
3090+ " Only local, resource and attachment jobs are started"))
3091
3092=== added file 'plainbox/plainbox/impl/commands/test_run.py.OTHER'
3093--- plainbox/plainbox/impl/commands/test_run.py.OTHER 1970-01-01 00:00:00 +0000
3094+++ plainbox/plainbox/impl/commands/test_run.py.OTHER 2013-08-28 12:37:27 +0000
3095@@ -0,0 +1,142 @@
3096+# This file is part of Checkbox.
3097+#
3098+# Copyright 2013 Canonical Ltd.
3099+# Written by:
3100+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
3101+# Daniel Manrique <roadmr@ubuntu.com>
3102+#
3103+# Checkbox is free software: you can redistribute it and/or modify
3104+# it under the terms of the GNU General Public License as published by
3105+# the Free Software Foundation, either version 3 of the License, or
3106+# (at your option) any later version.
3107+#
3108+# Checkbox is distributed in the hope that it will be useful,
3109+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3110+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3111+# GNU General Public License for more details.
3112+#
3113+# You should have received a copy of the GNU General Public License
3114+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
3115+
3116+"""
3117+plainbox.impl.commands.test_run
3118+===============================
3119+
3120+Test definitions for plainbox.impl.run module
3121+"""
3122+
3123+import os
3124+import shutil
3125+import tempfile
3126+
3127+from inspect import cleandoc
3128+from mock import patch
3129+from unittest import TestCase
3130+
3131+from plainbox.impl.box import main
3132+from plainbox.testing_utils.io import TestIO
3133+
3134+
3135+class TestRun(TestCase):
3136+
3137+ def setUp(self):
3138+ # session data are kept in XDG_CACHE_HOME/plainbox/.session
3139+ # To avoid resuming a real session, we have to select a temporary
3140+ # location instead
3141+ self._sandbox = tempfile.mkdtemp()
3142+ self._env = os.environ
3143+ os.environ['XDG_CACHE_HOME'] = self._sandbox
3144+
3145+ def test_help(self):
3146+ with TestIO(combined=True) as io:
3147+ with self.assertRaises(SystemExit) as call:
3148+ main(['run', '--help'])
3149+ self.assertEqual(call.exception.args, (0,))
3150+ self.maxDiff = None
3151+ expected = """
3152+ usage: plainbox run [-h] [--not-interactive] [-n] [-f FORMAT] [-p OPTIONS]
3153+ [-o FILE] [-t TRANSPORT] [--transport-where WHERE]
3154+ [--transport-options OPTIONS] [-i PATTERN] [-x PATTERN]
3155+ [-w WHITELIST]
3156+
3157+ optional arguments:
3158+ -h, --help show this help message and exit
3159+
3160+ user interface options:
3161+ --not-interactive Skip tests that require interactivity
3162+ -n, --dry-run Don't actually run any jobs
3163+
3164+ output options:
3165+ -f FORMAT, --output-format FORMAT
3166+ Save test results in the specified FORMAT (pass ? for
3167+ a list of choices)
3168+ -p OPTIONS, --output-options OPTIONS
3169+ Comma-separated list of options for the export
3170+ mechanism (pass ? for a list of choices)
3171+ -o FILE, --output-file FILE
3172+ Save test results to the specified FILE (or to stdout
3173+ if FILE is -)
3174+ -t TRANSPORT, --transport TRANSPORT
3175+ use TRANSPORT to send results somewhere (pass ? for a
3176+ list of choices)
3177+ --transport-where WHERE
3178+ Where to send data using the selected transport. This
3179+ is passed as-is and is transport-dependent.
3180+ --transport-options OPTIONS
3181+ Comma-separated list of key-value options (k=v) to be
3182+ passed to the transport.
3183+
3184+ job definition options:
3185+ -i PATTERN, --include-pattern PATTERN
3186+ Run jobs matching the given regular expression.
3187+ Matches from the start to the end of the line.
3188+ -x PATTERN, --exclude-pattern PATTERN
3189+ Do not run jobs matching the given regular expression.
3190+ Matches from the start to the end of the line.
3191+ -w WHITELIST, --whitelist WHITELIST
3192+ Load whitelist containing run patterns
3193+ """
3194+ self.assertEqual(io.combined, cleandoc(expected) + "\n")
3195+
3196+ def test_run_without_args(self):
3197+ with TestIO(combined=True) as io:
3198+ with self.assertRaises(SystemExit) as call:
3199+ with patch('plainbox.impl.commands.run.authenticate_warmup') as mock_warmup:
3200+ mock_warmup.return_value = 0
3201+ main(['run'])
3202+ self.assertEqual(call.exception.args, (0,))
3203+ expected = """
3204+ ===============================[ Authentication ]===============================
3205+ ===============================[ Analyzing Jobs ]===============================
3206+ ==============================[ Running All Jobs ]==============================
3207+ ==================================[ Results ]===================================
3208+ """
3209+ self.assertEqual(io.combined, cleandoc(expected) + "\n")
3210+
3211+ def test_output_format_list(self):
3212+ with TestIO(combined=True) as io:
3213+ with self.assertRaises(SystemExit) as call:
3214+ main(['run', '--output-format=?'])
3215+ self.assertEqual(call.exception.args, (0,))
3216+ expected = """
3217+ Available output formats: json, rfc822, text, xml
3218+ """
3219+ self.assertEqual(io.combined, cleandoc(expected) + "\n")
3220+
3221+ def test_output_option_list(self):
3222+ with TestIO(combined=True) as io:
3223+ with self.assertRaises(SystemExit) as call:
3224+ main(['run', '--output-option=?'])
3225+ self.assertEqual(call.exception.args, (0,))
3226+ expected = """
3227+ Each format may support a different set of options
3228+ 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
3229+ 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
3230+ 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
3231+ xml:
3232+ """
3233+ self.assertEqual(io.combined, cleandoc(expected) + "\n")
3234+
3235+ def tearDown(self):
3236+ shutil.rmtree(self._sandbox)
3237+ os.environ = self._env
3238
3239=== added file 'plainbox/plainbox/impl/config.py.OTHER'
3240--- plainbox/plainbox/impl/config.py.OTHER 1970-01-01 00:00:00 +0000
3241+++ plainbox/plainbox/impl/config.py.OTHER 2013-08-28 12:37:27 +0000
3242@@ -0,0 +1,544 @@
3243+# This file is part of Checkbox.
3244+#
3245+# Copyright 2013 Canonical Ltd.
3246+# Written by:
3247+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
3248+#
3249+# Checkbox is free software: you can redistribute it and/or modify
3250+# it under the terms of the GNU General Public License as published by
3251+# the Free Software Foundation, either version 3 of the License, or
3252+# (at your option) any later version.
3253+#
3254+# Checkbox is distributed in the hope that it will be useful,
3255+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3256+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3257+# GNU General Public License for more details.
3258+#
3259+# You should have received a copy of the GNU General Public License
3260+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
3261+
3262+"""
3263+:mod:`plainbox.impl.config` -- configuration
3264+============================================
3265+
3266+.. warning::
3267+
3268+ THIS MODULE DOES NOT HAVE A STABLE PUBLIC API
3269+"""
3270+
3271+from abc import ABCMeta, abstractmethod
3272+import collections
3273+import configparser
3274+import logging
3275+import re
3276+
3277+
3278+logger = logging.getLogger("plainbox.config")
3279+
3280+
3281+class INameTracking(metaclass=ABCMeta):
3282+ """
3283+ Interface for classes that are instantiated as a part of definition of
3284+ another class. The purpose of this interface is to allow instances to learn
3285+ about the name (python identifier) that was assigned to the instance at
3286+ class definition time.
3287+
3288+ Subclasses must define the _set_tracked_name() method.
3289+ """
3290+
3291+ @abstractmethod
3292+ def _set_tracked_name(self, name):
3293+ """
3294+ Set the that corresponds to the symbol used in class definition. This
3295+ can be a no-op if the name was already set by other means
3296+ """
3297+
3298+
3299+class ConfigMetaData:
3300+ """
3301+ Class containing meta-data about a Config class
3302+
3303+ Sub-classes of this class are automatically added to each Config subclass
3304+ as a Meta class-level attribute.
3305+
3306+ This class has typically two attributes:
3307+
3308+ :cvar variable_list:
3309+ A list of all Variable objects defined in the class
3310+
3311+ :cvar section_list:
3312+ A list of all Section object defined in the class
3313+
3314+ :cvar filename_list:
3315+ A list of config files (pathnames) to read on call to
3316+ :meth:`Config.read`
3317+ """
3318+ variable_list = []
3319+ section_list = []
3320+ filename_list = []
3321+
3322+
3323+class UnsetType:
3324+ """
3325+ Class of the Unset object
3326+ """
3327+
3328+ def __str__(self):
3329+ return "unset"
3330+
3331+ def __repr__(self):
3332+ return "Unset"
3333+
3334+
3335+Unset = UnsetType()
3336+
3337+
3338+class Variable(INameTracking):
3339+ """
3340+ Variable that can be used in a configuration systems
3341+ """
3342+
3343+ _KIND_CHOICE = (bool, int, float, str)
3344+
3345+ def __init__(self, name=None, *, section='DEFAULT', kind=str,
3346+ default=Unset, validator_list=None, help_text=None):
3347+ # Ensure kind is correct
3348+ if kind not in self._KIND_CHOICE:
3349+ raise ValueError("unsupported kind")
3350+ # Ensure that we have a validator_list, even if empty
3351+ if validator_list is None:
3352+ validator_list = []
3353+ # Insert a KindValidator as the first validator to run
3354+ validator_list.insert(0, KindValidator)
3355+ # Assign all the attributes
3356+ self._name = name
3357+ self._section = section
3358+ self._kind = kind
3359+ self._default = default
3360+ self._validator_list = validator_list
3361+ self._help_text = help_text
3362+ self._validate_default_value()
3363+ # Workaround for Sphinx breaking if __doc__ is a property
3364+ self.__doc__ = self.help_text or self.__class__.__doc__
3365+
3366+ def _validate_default_value(self):
3367+ """
3368+ Validate the default value, unless it is Unset
3369+ """
3370+ if self.default is Unset:
3371+ return
3372+ for validator in self.validator_list:
3373+ message = validator(self, self.default)
3374+ if message is not None:
3375+ raise ValidationError(self, self.default, message)
3376+
3377+ def _set_tracked_name(self, name):
3378+ """
3379+ Internal method used by :meth:`ConfigMeta.__new__`
3380+ """
3381+ if self._name is None:
3382+ self._name = name
3383+
3384+ @property
3385+ def name(self):
3386+ """
3387+ name of this variable
3388+ """
3389+ return self._name
3390+
3391+ @property
3392+ def section(self):
3393+ """
3394+ name of the section this variable belongs to (in a configuration file)
3395+ """
3396+ return self._section
3397+
3398+ @property
3399+ def kind(self):
3400+ """
3401+ the "poor man's type", can be only str (default), bool, float or int
3402+ """
3403+ return self._kind
3404+
3405+ @property
3406+ def default(self):
3407+ """
3408+ a default value, if any
3409+ """
3410+ return self._default
3411+
3412+ @property
3413+ def validator_list(self):
3414+ """
3415+ a optional list of :class:`Validator` instances that are enforced on
3416+ the value
3417+ """
3418+ return self._validator_list
3419+
3420+ @property
3421+ def help_text(self):
3422+ """
3423+ an optional help text associated with this variable
3424+ """
3425+ return self._help_text
3426+
3427+ def __repr__(self):
3428+ return "<Variable name:{!r}>".format(self.name)
3429+
3430+ def __get__(self, instance, owner):
3431+ """
3432+ Get the value of a variable
3433+
3434+ Missing variables return the default value
3435+ """
3436+ if instance is None:
3437+ return self
3438+ try:
3439+ return instance._get_variable(self._name)
3440+ except KeyError:
3441+ return self.default
3442+
3443+ def __set__(self, instance, new_value):
3444+ """
3445+ Set the value of a variable
3446+
3447+ :raises ValidationError: if the new value is incorrect
3448+ """
3449+ # Check it against all validators
3450+ for validator in self.validator_list:
3451+ message = validator(self, new_value)
3452+ if message is not None:
3453+ raise ValidationError(self, new_value, message)
3454+ # Assign it to the backing store of the instance
3455+ instance._set_variable(self.name, new_value)
3456+
3457+ def __delete__(self, instance):
3458+ # NOTE: this is quite confusing, this method is a companion to __get__
3459+ # and __set__ but __del__ is entirely unrelated (object garbage
3460+ # collected, do final cleanup) so don't think this is a mistake
3461+ instance._del_variable(self._name)
3462+
3463+
3464+class Section(INameTracking):
3465+ """
3466+ A section of a configuration file.
3467+ """
3468+
3469+ def __init__(self, name=None, *, help_text=None):
3470+ self._name = name
3471+ self._help_text = help_text
3472+ # Workaround for Sphinx breaking if __doc__ is a property
3473+ self.__doc__ = self.help_text or self.__class__.__doc__
3474+
3475+ def _set_tracked_name(self, name):
3476+ """
3477+ Internal method used by :meth:`ConfigMeta.__new__`
3478+ """
3479+ if self._name is None:
3480+ self._name = name
3481+
3482+ @property
3483+ def name(self):
3484+ """
3485+ name of this section
3486+ """
3487+ return self._name
3488+
3489+ @property
3490+ def help_text(self):
3491+ """
3492+ an optional help text associated with this section
3493+ """
3494+ return self._help_text
3495+
3496+ def __get__(self, instance, owner):
3497+ if instance is None:
3498+ return self
3499+ try:
3500+ return instance._get_section(self._name)
3501+ except KeyError:
3502+ return Unset
3503+
3504+ def __set__(self, instance, new_value):
3505+ instance._set_section(self.name, new_value)
3506+
3507+ def __delete__(self, instance):
3508+ instance._del_section(self.name)
3509+
3510+
3511+class ConfigMeta(type):
3512+ """
3513+ Meta class for all configuration classes.
3514+
3515+ This meta class handles assignment of '_name' attribute to each
3516+ :class:`Variable` instance created in the class body.
3517+
3518+ It also accumulates such instances and assigns them to variable_list in a
3519+ helper Meta class which is assigned back to the namespace
3520+ """
3521+
3522+ def __new__(mcls, name, bases, namespace, **kwargs):
3523+ # Keep track of variables and sections from base class
3524+ variable_list = []
3525+ section_list = []
3526+ if 'Meta' in namespace:
3527+ if hasattr(namespace['Meta'], 'variable_list'):
3528+ variable_list = namespace['Meta'].variable_list[:]
3529+ if hasattr(namespace['Meta'], 'section_list'):
3530+ section_list = namespace['Meta'].section_list[:]
3531+ # Discover all Variable and Section instances
3532+ # defined in the class namespace
3533+ for name, item in namespace.items():
3534+ if isinstance(item, INameTracking):
3535+ item._set_tracked_name(name)
3536+ if isinstance(item, Variable):
3537+ variable_list.append(item)
3538+ elif isinstance(item, Section):
3539+ section_list.append(item)
3540+ # Get or create the class of the 'Meta' object.
3541+ #
3542+ # This class should always inherit from ConfigMetaData and whatever the
3543+ # user may have defined as Meta.
3544+ Meta_name = "Meta"
3545+ Meta_bases = (ConfigMetaData,)
3546+ Meta_ns = {
3547+ 'variable_list': variable_list,
3548+ 'section_list': section_list
3549+ }
3550+ if 'Meta' in namespace:
3551+ user_Meta_cls = namespace['Meta']
3552+ if not isinstance(user_Meta_cls, type):
3553+ raise TypeError("Meta must be a class")
3554+ Meta_bases = (user_Meta_cls, ConfigMetaData)
3555+ # Create a new type for the Meta class
3556+ namespace['Meta'] = type.__new__(
3557+ type(ConfigMetaData), Meta_name, Meta_bases, Meta_ns)
3558+ # Create a new type for the Config subclass
3559+ return type.__new__(mcls, name, bases, namespace)
3560+
3561+ @classmethod
3562+ def __prepare__(mcls, name, bases, **kwargs):
3563+ return collections.OrderedDict()
3564+
3565+
3566+class PlainBoxConfigParser(configparser.ConfigParser):
3567+ """
3568+ A simple ConfigParser subclass that does not lowercase
3569+ key names.
3570+ """
3571+ def optionxform(self, option):
3572+ return option
3573+
3574+
3575+class Config(metaclass=ConfigMeta):
3576+ """
3577+ Base class for configuration systems
3578+
3579+ :ivar _var:
3580+ storage backend for Variable definitions
3581+
3582+ :ivar _section:
3583+ storage backend for Section definitions
3584+
3585+ :ivar _filename_list:
3586+ list of pathnames to files that were loaded by the last call to
3587+ :meth:`read()`
3588+
3589+ :ivar _problem_list:
3590+ list of :class:`ValidationError` that were detected by the last call to
3591+ :meth:`read()`
3592+ """
3593+
3594+ def __init__(self):
3595+ """
3596+ Initialize an empty Config object
3597+ """
3598+ self._var = {}
3599+ self._section = {}
3600+ self._filename_list = []
3601+ self._problem_list = []
3602+
3603+ @property
3604+ def problem_list(self):
3605+ """
3606+ list of :class:`ValidationError` that were detected by the last call to
3607+ :meth:`read()`
3608+ """
3609+ return self._problem_list
3610+
3611+ @property
3612+ def filename_list(self):
3613+ """
3614+ list of pathnames to files that were loaded by the last call to
3615+ :meth:`read()`
3616+ """
3617+ return self._filename_list
3618+
3619+ @classmethod
3620+ def get(cls):
3621+ """
3622+ Get an instance of this Config class with all the configuration loaded
3623+ from default locations. The locations are determined by
3624+ Meta.filename_list attribute.
3625+
3626+ :returns: fresh :class:`Config` instance
3627+
3628+ """
3629+ self = cls()
3630+ self.read(cls.Meta.filename_list)
3631+ return self
3632+
3633+ def read(self, filename_list):
3634+ """
3635+ Load and merge settings from many files.
3636+
3637+ This method tries to open each file from the list of filenames, parse
3638+ it as an INI file using :class:`PlainBoxConfigParser` (a simple
3639+ ConfigParser subclass that respects the case of key names). The list of
3640+ files actually accessed is saved as available as
3641+ :attr:`Config.filename_list`.
3642+
3643+ If any problem is detected during parsing (e.g. syntax errors) those
3644+ are captured and added to the :attr:`Config.problem_list`.
3645+
3646+ After all files are loaded each :class:`Variable` and :class:`Section`
3647+ defined in the :class:`Config` class is assigned with the data from the
3648+ merged configuration data.
3649+
3650+ Any variables that cannot be assigned and raise
3651+ :class:`ValidationError` are ignored but the list of problems is saved.
3652+
3653+ All unused configuration (extra variables that are not defined as
3654+ either Variable or Section class) is silently ignored.
3655+
3656+ .. note::
3657+ This method resets :ivar:`_problem_list` and
3658+ :ivar:`_filename_list`.
3659+ """
3660+ parser = PlainBoxConfigParser()
3661+ # Reset filename list and problem list
3662+ self._filename_list = []
3663+ self._problem_list = []
3664+ # Try loading all of the config files
3665+ try:
3666+ self._filename_list = parser.read(filename_list)
3667+ except configparser.Error as exc:
3668+ self._problem_list.append(exc)
3669+ # Pick a reader function appropriate for the kind of variable
3670+ reader_fn = {
3671+ str: parser.get,
3672+ bool: parser.getboolean,
3673+ int: parser.getint,
3674+ float: parser.getfloat
3675+ }
3676+ # Load all variables that we know about
3677+ for variable in self.Meta.variable_list:
3678+ # Access the variable in the configuration file
3679+ try:
3680+ value = reader_fn[variable.kind](
3681+ variable.section, variable.name)
3682+ except configparser.NoSectionError:
3683+ continue
3684+ except configparser.NoOptionError:
3685+ continue
3686+ # Try to assign it
3687+ try:
3688+ variable.__set__(self, value)
3689+ except ValidationError as exc:
3690+ self.problem_list.append(exc)
3691+ # Load all sections that we know about
3692+ for section in self.Meta.section_list:
3693+ try:
3694+ # Access the section in the configuration file
3695+ value = dict(parser.items(section.name))
3696+ except configparser.NoSectionError:
3697+ continue
3698+ # Assign it
3699+ section.__set__(self, value)
3700+
3701+ def _get_variable(self, name):
3702+ """
3703+ Internal method called by :meth:`Variable.__get__`
3704+ """
3705+ return self._var[name]
3706+
3707+ def _set_variable(self, name, value):
3708+ """
3709+ Internal method called by :meth:`Variable.__set__`
3710+ """
3711+ self._var[name] = value
3712+
3713+ def _del_variable(self, name):
3714+ """
3715+ Internal method called by :meth:`Variable.__delete__`
3716+ """
3717+ del self._var[name]
3718+
3719+ def _get_section(self, name):
3720+ """
3721+ Internal method called by :meth:`Section.__get__`
3722+ """
3723+ return self._section[name]
3724+
3725+ def _set_section(self, name, value):
3726+ """
3727+ Internal method called by :meth:`Section.__set__`
3728+ """
3729+ self._section[name] = value
3730+
3731+ def _del_section(self, name):
3732+ """
3733+ Internal method called by :meth:`Section.__delete__`
3734+ """
3735+ del self._section[name]
3736+
3737+
3738+class ValidationError(ValueError):
3739+ """
3740+ Exception raised when configuration variables fail to validate
3741+ """
3742+
3743+ def __init__(self, variable, new_value, message):
3744+ self.variable = variable
3745+ self.new_value = new_value
3746+ self.message = message
3747+
3748+ def __str__(self):
3749+ return self.message
3750+
3751+
3752+class IValidator(metaclass=ABCMeta):
3753+ """
3754+ An interface for variable vale validators
3755+ """
3756+
3757+ @abstractmethod
3758+ def __call__(self, variable, new_value):
3759+ """
3760+ Check if a value is appropriate for the variable.
3761+
3762+ :returns: None if everything is okay
3763+ :returns: string that describes the problem if the value cannot be used
3764+ """
3765+
3766+
3767+def KindValidator(variable, new_value):
3768+ """
3769+ A validator ensuring that values match the "kind" of the variable.
3770+ """
3771+ if not isinstance(new_value, variable.kind):
3772+ return "expected a {}".format(variable.kind.__name__)
3773+
3774+
3775+class PatternValidator(IValidator):
3776+ """
3777+ A validator ensuring that values match a given pattern
3778+ """
3779+
3780+ def __init__(self, pattern_text):
3781+ self.pattern_text = pattern_text
3782+ self.pattern = re.compile(pattern_text)
3783+
3784+ def __call__(self, variable, new_value):
3785+ if not self.pattern.match(new_value):
3786+ return "does not match pattern: {!r}".format(self.pattern_text)
3787
3788=== added file 'plainbox/plainbox/impl/job.py.OTHER'
3789--- plainbox/plainbox/impl/job.py.OTHER 1970-01-01 00:00:00 +0000
3790+++ plainbox/plainbox/impl/job.py.OTHER 2013-08-28 12:37:27 +0000
3791@@ -0,0 +1,259 @@
3792+# This file is part of Checkbox.
3793+#
3794+# Copyright 2012, 2013 Canonical Ltd.
3795+# Written by:
3796+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
3797+#
3798+# Checkbox is free software: you can redistribute it and/or modify
3799+# it under the terms of the GNU General Public License as published by
3800+# the Free Software Foundation, either version 3 of the License, or
3801+# (at your option) any later version.
3802+#
3803+# Checkbox is distributed in the hope that it will be useful,
3804+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3805+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3806+# GNU General Public License for more details.
3807+#
3808+# You should have received a copy of the GNU General Public License
3809+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
3810+
3811+"""
3812+:mod:`plainbox.impl.job` -- job definition
3813+==========================================
3814+
3815+.. warning::
3816+
3817+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
3818+"""
3819+
3820+import logging
3821+import os
3822+import re
3823+
3824+from plainbox.abc import IJobDefinition
3825+from plainbox.impl.config import Unset
3826+from plainbox.impl.resource import ResourceProgram
3827+from plainbox.impl.secure.checkbox_trusted_launcher import BaseJob
3828+
3829+
3830+logger = logging.getLogger("plainbox.job")
3831+
3832+
3833+class JobDefinition(BaseJob, IJobDefinition):
3834+ """
3835+ Job definition class.
3836+
3837+ Thin wrapper around the RFC822 record that defines a checkbox job
3838+ definition
3839+ """
3840+
3841+ @property
3842+ def name(self):
3843+ return self.__getattr__('name')
3844+
3845+ @property
3846+ def requires(self):
3847+ try:
3848+ return self.__getattr__('requires')
3849+ except AttributeError:
3850+ return None
3851+
3852+ @property
3853+ def description(self):
3854+ try:
3855+ return self.__getattr__('description')
3856+ except AttributeError:
3857+ return None
3858+
3859+ @property
3860+ def depends(self):
3861+ try:
3862+ return self.__getattr__('depends')
3863+ except AttributeError:
3864+ return None
3865+
3866+ @property
3867+ def via(self):
3868+ """
3869+ The checksum of the "parent" job when the current JobDefinition comes
3870+ from a job output using the local plugin
3871+ """
3872+ return self._via
3873+
3874+ @property
3875+ def origin(self):
3876+ """
3877+ The Origin object associated with this JobDefinition
3878+
3879+ May be None
3880+ """
3881+ return self._origin
3882+
3883+ def __init__(self, data, origin=None, checkbox=None, via=None):
3884+ super(JobDefinition, self).__init__(data)
3885+ self._resource_program = None
3886+ self._origin = origin
3887+ self._checkbox = checkbox
3888+ self._via = via
3889+
3890+ def __str__(self):
3891+ return self.name
3892+
3893+ def __repr__(self):
3894+ return "<JobDefinition name:{!r} plugin:{!r}>".format(
3895+ self.name, self.plugin)
3896+
3897+ def __getattr__(self, attr):
3898+ if attr in self._data:
3899+ return self._data[attr]
3900+ gettext_attr = "_{}".format(attr)
3901+ if gettext_attr in self._data:
3902+ value = self._data[gettext_attr]
3903+ # TODO: feed through gettext
3904+ return value
3905+ raise AttributeError(attr)
3906+
3907+ def _get_persistance_subset(self):
3908+ state = {}
3909+ state['data'] = {}
3910+ for key, value in self._data.items():
3911+ state['data'][key] = value
3912+ if self.via is not None:
3913+ state['via'] = self.via
3914+ return state
3915+
3916+ def __eq__(self, other):
3917+ if not isinstance(other, JobDefinition):
3918+ return False
3919+ return self.get_checksum() == other.get_checksum()
3920+
3921+ def __ne__(self, other):
3922+ if not isinstance(other, JobDefinition):
3923+ return True
3924+ return self.get_checksum() != other.get_checksum()
3925+
3926+ def get_resource_program(self):
3927+ """
3928+ Return a ResourceProgram based on the 'requires' expression.
3929+
3930+ The program instance is cached in the JobDefinition and is not
3931+ compiled or validated on subsequent calls.
3932+
3933+ Returns ResourceProgram or None
3934+ Raises ResourceProgramError or SyntaxError
3935+ """
3936+ if self.requires is not None and self._resource_program is None:
3937+ self._resource_program = ResourceProgram(self.requires)
3938+ return self._resource_program
3939+
3940+ def get_direct_dependencies(self):
3941+ """
3942+ Compute and return a set of direct dependencies
3943+
3944+ To combat a simple mistake where the jobs are space-delimited any
3945+ mixture of white-space (including newlines) and commas are allowed.
3946+ """
3947+ if self.depends:
3948+ return {name for name in re.split('[\s,]+', self.depends)}
3949+ else:
3950+ return set()
3951+
3952+ def get_resource_dependencies(self):
3953+ """
3954+ Compute and return a set of resource dependencies
3955+ """
3956+ program = self.get_resource_program()
3957+ if program:
3958+ return program.required_resources
3959+ else:
3960+ return set()
3961+
3962+ @classmethod
3963+ def from_rfc822_record(cls, record):
3964+ """
3965+ Create a JobDefinition instance from rfc822 record
3966+
3967+ The record must be a RFC822Record instance.
3968+
3969+ Only the 'name' and 'plugin' keys are required.
3970+ All other data is stored as is and is entirely optional.
3971+ """
3972+ for key in ['plugin', 'name']:
3973+ if key not in record.data:
3974+ raise ValueError(
3975+ "Required record key {!r} was not found".format(key))
3976+ return cls(record.data, record.origin)
3977+
3978+ def modify_execution_environment(self, env, session_dir, config=None):
3979+ """
3980+ Alter execution environment as required to execute this job.
3981+
3982+ The environment is modified in place.
3983+
3984+ The session_dir argument can be passed to scripts to know where to
3985+ create temporary data. This data will persist during the lifetime of
3986+ the session.
3987+
3988+ The config argument (which defaults to None) should be a PlainBoxConfig
3989+ object. It is used to provide values for missing environment variables
3990+ that are required by the job (as expressed by the environ key in the
3991+ job definition file).
3992+
3993+ Computes and modifies the dictionary of additional values that need to
3994+ be added to the base environment. Note that all changes to the
3995+ environment (modifications, not replacements) depend on the current
3996+ environment. This may be of importance when attempting to setup the
3997+ test session as another user.
3998+
3999+ This environment has additional PATH, PYTHONPATH entries. It also uses
4000+ fixed LANG so that scripts behave as expected. Lastly it sets
4001+ CHECKBOX_SHARE that is required by some scripts.
4002+ """
4003+ # XXX: this obviously requires a checkbox object to know where stuff is
4004+ # but during the transition we may not have one available.
4005+ assert self._checkbox is not None
4006+ # Use PATH that can lookup checkbox scripts
4007+ if self._checkbox.extra_PYTHONPATH:
4008+ env['PYTHONPATH'] = os.pathsep.join(
4009+ [self._checkbox.extra_PYTHONPATH]
4010+ + env.get("PYTHONPATH", "").split(os.pathsep))
4011+ # Update PATH so that scripts can be found
4012+ env['PATH'] = os.pathsep.join(
4013+ [self._checkbox.extra_PATH]
4014+ + env.get("PATH", "").split(os.pathsep))
4015+ # Add CHECKBOX_SHARE that is needed by one script
4016+ env['CHECKBOX_SHARE'] = self._checkbox.CHECKBOX_SHARE
4017+ # Add CHECKBOX_DATA (temporary checkbox data)
4018+ env['CHECKBOX_DATA'] = session_dir
4019+ # Inject additional variables that are requested in the config
4020+ if config is not None and config.environment is not Unset:
4021+ for env_var in config.environment:
4022+ # Don't override anything that is already present in the
4023+ # current environment. This will allow users to customize
4024+ # variables without editing any config files.
4025+ if env_var in env:
4026+ continue
4027+ # If the environment section of the configuration file has a
4028+ # particular variable then copy it over.
4029+ env[env_var] = config.environment[env_var]
4030+
4031+ def create_child_job_from_record(self, record):
4032+ """
4033+ Create a new JobDefinition from RFC822 record.
4034+
4035+ This method should only be used to create additional jobs from local
4036+ jobs (plugin local). The intent is two-fold:
4037+ 1) to encapsulate the sharing of the embedded checkbox reference.
4038+ 2) to set the ``via`` attribute (to aid the trusted launcher)
4039+ """
4040+ job = self.from_rfc822_record(record)
4041+ job._checkbox = self._checkbox
4042+ job._via = self.get_checksum()
4043+ return job
4044+
4045+ @classmethod
4046+ def from_json_record(cls, record):
4047+ """
4048+ Create a JobDefinition instance from JSON record
4049+ """
4050+ return cls(record['data'], via=record.get('via'))
4051
4052=== added file 'plainbox/plainbox/impl/runner.py.OTHER'
4053--- plainbox/plainbox/impl/runner.py.OTHER 1970-01-01 00:00:00 +0000
4054+++ plainbox/plainbox/impl/runner.py.OTHER 2013-08-28 12:37:27 +0000
4055@@ -0,0 +1,421 @@
4056+# This file is part of Checkbox.
4057+#
4058+# Copyright 2012 Canonical Ltd.
4059+# Written by:
4060+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
4061+#
4062+# Checkbox is free software: you can redistribute it and/or modify
4063+# it under the terms of the GNU General Public License as published by
4064+# the Free Software Foundation, either version 3 of the License, or
4065+# (at your option) any later version.
4066+#
4067+# Checkbox is distributed in the hope that it will be useful,
4068+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4069+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4070+# GNU General Public License for more details.
4071+#
4072+# You should have received a copy of the GNU General Public License
4073+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
4074+
4075+"""
4076+:mod:`plainbox.impl.runner` -- job runner
4077+=========================================
4078+
4079+.. warning::
4080+
4081+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
4082+"""
4083+
4084+import collections
4085+import datetime
4086+import json
4087+import logging
4088+import os
4089+import string
4090+
4091+from plainbox.vendor import extcmd
4092+
4093+from plainbox.abc import IJobRunner
4094+from plainbox.impl.result import JobResult, IOLogRecord, IoLogEncoder
4095+
4096+logger = logging.getLogger("plainbox.runner")
4097+
4098+
4099+def slugify(_string):
4100+ """
4101+ Slugify - like Django does for URL - transform a random string to a valid
4102+ slug that can be later used in filenames
4103+ """
4104+ valid_chars = frozenset(
4105+ "-_.{}{}".format(string.ascii_letters, string.digits))
4106+ return ''.join(c if c in valid_chars else '_' for c in _string)
4107+
4108+
4109+def io_log_write(log, stream):
4110+ """
4111+ JSON call to serialize io_log objects to disk
4112+ """
4113+ json.dump(
4114+ log, stream, ensure_ascii=False, indent=None, cls=IoLogEncoder,
4115+ separators=(',', ':'))
4116+
4117+
4118+def authenticate_warmup():
4119+ """
4120+ Call the checkbox trusted launcher in warmup mode.
4121+
4122+ This will use the corresponding PolicyKit action and start the
4123+ authentication agent (depending on the installed policy file)
4124+ """
4125+ warmup_popen = extcmd.ExternalCommand()
4126+ return warmup_popen.call(
4127+ ['pkexec', 'checkbox-trusted-launcher', '--warmup'])
4128+
4129+
4130+class CommandIOLogBuilder(extcmd.DelegateBase):
4131+ """
4132+ Delegate for extcmd that builds io_log entries.
4133+
4134+ IO log entries are records kept by JobResult.io_log and correspond to all
4135+ of the data that was written by called process. The format is a sequence of
4136+ tuples (delay, stream_name, data).
4137+ """
4138+
4139+ def on_begin(self, args, kwargs):
4140+ """
4141+ Internal method of extcmd.DelegateBase
4142+
4143+ Called when a command is being invoked.
4144+ Begins tracking time (relative time entries) and creates the empty
4145+ io_log list.
4146+ """
4147+ logger.debug("io log starting for command: %r", args)
4148+ self.io_log = []
4149+ self.last_msg = datetime.datetime.utcnow()
4150+
4151+ def on_line(self, stream_name, line):
4152+ """
4153+ Internal method of IOLogBuilder
4154+
4155+ Appends each line to the io_log. Maintains a timestamp of the last
4156+ message so that approximate delay between each piece of output can be
4157+ recorded as well.
4158+ """
4159+ now = datetime.datetime.utcnow()
4160+ delay = now - self.last_msg
4161+ self.last_msg = now
4162+ record = IOLogRecord(delay.total_seconds(), stream_name, line)
4163+ self.io_log.append(record)
4164+ logger.debug("io log captured %r", record)
4165+
4166+
4167+class CommandOutputWriter(extcmd.DelegateBase):
4168+ """
4169+ Delegate for extcmd that writes output to a file on disk.
4170+
4171+ The file itself is only opened once on_begin() gets called by extcmd. This
4172+ makes it safe to instantiate this without worrying about dangling
4173+ resources.
4174+ """
4175+
4176+ def __init__(self, stdout_path, stderr_path):
4177+ """
4178+ Initialize new writer.
4179+
4180+ Just records output paths.
4181+ """
4182+ self.stdout_path = stdout_path
4183+ self.stderr_path = stderr_path
4184+
4185+ def on_begin(self, args, kwargs):
4186+ """
4187+ Internal method of extcmd.DelegateBase
4188+
4189+ Called when a command is being invoked
4190+ """
4191+ self.stdout = open(self.stdout_path, "wb")
4192+ self.stderr = open(self.stderr_path, "wb")
4193+
4194+ def on_end(self, returncode):
4195+ """
4196+ Internal method of extcmd.DelegateBase
4197+
4198+ Called when a command finishes running
4199+ """
4200+ self.stdout.close()
4201+ self.stderr.close()
4202+
4203+ def on_line(self, stream_name, line):
4204+ """
4205+ Internal method of extcmd.DelegateBase
4206+
4207+ Called for each line of output.
4208+ """
4209+ if stream_name == 'stdout':
4210+ self.stdout.write(line)
4211+ elif stream_name == 'stderr':
4212+ self.stderr.write(line)
4213+
4214+
4215+class FallbackCommandOutputPrinter(extcmd.DelegateBase):
4216+ """
4217+ Delegate for extcmd that prints all output to stdout.
4218+
4219+ This delegate is only used as a fallback when no delegate was explicitly
4220+ provided to a JobRunner instance.
4221+ """
4222+
4223+ def __init__(self, prompt):
4224+ self._prompt = prompt
4225+ self._lineno = collections.defaultdict(int)
4226+ self._abort = False
4227+
4228+ def on_line(self, stream_name, line):
4229+ if self._abort:
4230+ return
4231+ self._lineno[stream_name] += 1
4232+ try:
4233+ print("(job {}, <{}:{:05}>) {}".format(
4234+ self._prompt, stream_name, self._lineno[stream_name],
4235+ line.decode('UTF-8').rstrip()))
4236+ except UnicodeDecodeError:
4237+ self._abort = True
4238+
4239+
4240+class JobRunner(IJobRunner):
4241+ """
4242+ Runner for jobs - executes jobs and produces results
4243+
4244+ The runner is somewhat de-coupled from jobs and session. It still carries
4245+ all checkbox-specific logic about the various types of plugins.
4246+
4247+ The runner consumes jobs and configuration objects and produces job result
4248+ objects. The runner can operate in dry-run mode, when enabled, most jobs
4249+ are never started. Only jobs listed in DRY_RUN_PLUGINS are executed.
4250+ """
4251+
4252+ # List of plugins that are still executed
4253+ _DRY_RUN_PLUGINS = ('local', 'resource', 'attachment')
4254+
4255+ def __init__(self, session_dir, jobs_io_log_dir,
4256+ command_io_delegate=None, outcome_callback=None,
4257+ dry_run=False):
4258+ """
4259+ Initialize a new job runner.
4260+
4261+ Uses the specified session_dir as CHECKBOX_DATA environment variable.
4262+ Uses the specified IO delegate for extcmd.ExternalCommandWithDelegate
4263+ to track IO done by the called commands (optional, a simple console
4264+ printer is provided if missing).
4265+ """
4266+ self._session_dir = session_dir
4267+ self._jobs_io_log_dir = jobs_io_log_dir
4268+ self._command_io_delegate = command_io_delegate
4269+ self._outcome_callback = outcome_callback
4270+ self._dry_run = dry_run
4271+
4272+ def run_job(self, job, config=None):
4273+ """
4274+ Run the specified job an return the result
4275+ """
4276+ logger.info("Running %r", job)
4277+ func_name = "_plugin_" + job.plugin.replace('-', '_')
4278+ try:
4279+ runner = getattr(self, func_name)
4280+ except AttributeError:
4281+ return JobResult({
4282+ 'job': job,
4283+ 'outcome': JobResult.OUTCOME_NOT_IMPLEMENTED,
4284+ 'comment': 'This plugin is not supported'
4285+ })
4286+ else:
4287+ if self._dry_run and job.plugin not in self._DRY_RUN_PLUGINS:
4288+ return self._dry_run_result(job)
4289+ else:
4290+ return runner(job, config)
4291+
4292+ def _dry_run_result(self, job):
4293+ """
4294+ Produce the result that is used when running in dry-run mode
4295+ """
4296+ return JobResult({
4297+ 'job': job,
4298+ 'outcome': JobResult.OUTCOME_SKIP,
4299+ 'comments': "Job skipped in dry-run mode"
4300+ })
4301+
4302+ def _plugin_shell(self, job, config):
4303+ return self._just_run_command(job, config)
4304+
4305+ _plugin_attachment = _plugin_shell
4306+
4307+ def _plugin_resource(self, job, config):
4308+ return self._just_run_command(job, config)
4309+
4310+ def _plugin_local(self, job, config):
4311+ return self._just_run_command(job, config)
4312+
4313+ def _plugin_manual(self, job, config):
4314+ if self._outcome_callback is None:
4315+ return JobResult({
4316+ 'job': job,
4317+ 'outcome': JobResult.OUTCOME_SKIP,
4318+ 'comment': "non-interactive test run"
4319+ })
4320+ else:
4321+ result = self._just_run_command(job, config)
4322+ # XXX: make outcome writable
4323+ result._data['outcome'] = self._outcome_callback()
4324+ return result
4325+
4326+ _plugin_user_interact = _plugin_manual
4327+ _plugin_user_verify = _plugin_manual
4328+
4329+ def _just_run_command(self, job, config):
4330+ # Run the embedded command
4331+ return_code, io_log = self._run_command(job, config)
4332+ # Convert the return of the command to the outcome of the job
4333+ if return_code == 0:
4334+ outcome = JobResult.OUTCOME_PASS
4335+ else:
4336+ outcome = JobResult.OUTCOME_FAIL
4337+ # Create a result object and return it
4338+ return JobResult({
4339+ 'job': job,
4340+ 'outcome': outcome,
4341+ 'return_code': return_code,
4342+ 'io_log': io_log
4343+ })
4344+
4345+ def _get_script_env(self, job, config=None, only_changes=False):
4346+ """
4347+ Compute the environment the script will be executed in
4348+ """
4349+ # Get a proper environment
4350+ env = dict(os.environ)
4351+ # Use non-internationalized environment
4352+ env['LANG'] = 'C.UTF-8'
4353+ # Allow the job to customize anything
4354+ job.modify_execution_environment(env, self._session_dir, config)
4355+ # If a differential environment is requested return only the subset
4356+ # that has been altered.
4357+ #
4358+ # XXX: This will effectively give the root user our PATH which _may_ be
4359+ # good bud _might_ be dangerous. This will need some peer review.
4360+ if only_changes:
4361+ return {key: value
4362+ for key, value in env.items()
4363+ if key not in os.environ or os.environ[key] != value
4364+ or key in job.get_environ_settings()}
4365+ else:
4366+ return env
4367+
4368+ def _get_command_trusted(self, job, config=None):
4369+ # When the job requires to run as root then elevate our permissions
4370+ # via pkexec(1). Since pkexec resets environment we need to somehow
4371+ # pass the extra things we require. To do that we pass the list of
4372+ # changed environment variables in addition to the job hash.
4373+ cmd = ['checkbox-trusted-launcher', '--hash', job.get_checksum()] + [
4374+ "{key}={value}".format(key=key, value=value)
4375+ for key, value in self._get_script_env(
4376+ job, config, only_changes=True
4377+ ).items()
4378+ ]
4379+ if job.via is not None:
4380+ cmd += ['--via', job.via]
4381+ return cmd
4382+
4383+ def _get_command_src(self, job, config=None):
4384+ # Running PlainBox from source doesn't require the trusted launcher
4385+ # That's why we use the env(1)' command and pass it the list of
4386+ # changed environment variables.
4387+ # The whole pkexec and env part gets prepended to the command
4388+ # we were supposed to run.
4389+ cmd = ['env']
4390+ cmd += [
4391+ "{key}={value}".format(key=key, value=value)
4392+ for key, value in self._get_script_env(
4393+ job, only_changes=True
4394+ ).items()
4395+ ]
4396+ cmd += ['bash', '-c', job.command]
4397+ return cmd
4398+
4399+ def _run_command(self, job, config):
4400+ """
4401+ Run the shell command associated with the specified job.
4402+
4403+ Returns a tuple (return_code, io_log)
4404+ """
4405+ # Bail early if there is nothing do do
4406+ if job.command is None:
4407+ return None, ()
4408+ ui_io_delegate = self._command_io_delegate
4409+ # If there is no UI delegate specified create a simple
4410+ # delegate that logs all output to the console
4411+ if ui_io_delegate is None:
4412+ ui_io_delegate = FallbackCommandOutputPrinter(job.name)
4413+ # Create a delegate that writes all IO to disk
4414+ slug = slugify(job.name)
4415+ output_writer = CommandOutputWriter(
4416+ stdout_path=os.path.join(self._jobs_io_log_dir,
4417+ "{}.stdout".format(slug)),
4418+ stderr_path=os.path.join(self._jobs_io_log_dir,
4419+ "{}.stderr".format(slug)))
4420+ # Create a delegate that builds a log of all IO
4421+ io_log_builder = CommandIOLogBuilder()
4422+ # Create the delegate for routing IO
4423+ #
4424+ #
4425+ # Split the stream of data into three parts (each part is expressed as
4426+ # an element of extcmd.Chain()).
4427+ #
4428+ # Send the first copy of the data through bytes->text decoder and
4429+ # then to the UI delegate. This cold be something provided by the
4430+ # higher level caller or the default CommandOutputLogger.
4431+ #
4432+ # Send the second copy of the data to the _IOLogBuilder() instance that
4433+ # just concatenates subsequent bytes into neat time-stamped records.
4434+ #
4435+ # Send the third copy to the output writer that writes everything to
4436+ # disk.
4437+ delegate = extcmd.Chain([
4438+ ui_io_delegate,
4439+ io_log_builder,
4440+ output_writer])
4441+ logger.debug("job[%s] extcmd delegate: %r", job.name, delegate)
4442+ # Create a subprocess.Popen() like object that uses the delegate
4443+ # system to observe all IO as it occurs in real time.
4444+ logging_popen = extcmd.ExternalCommandWithDelegate(delegate)
4445+ # Start the process and wait for it to finish getting the
4446+ # result code. This will actually call a number of callbacks
4447+ # while the process is running. It will also spawn a few
4448+ # threads although all callbacks will be fired from a single
4449+ # thread (which is _not_ the main thread)
4450+ logger.debug("job[%s] starting command: %s", job.name, job.command)
4451+ if job.user is not None:
4452+ if job._checkbox._mode == 'src':
4453+ cmd = self._get_command_src(job, config)
4454+ else:
4455+ cmd = self._get_command_trusted(job, config)
4456+ cmd = ['pkexec', '--user', job.user] + cmd
4457+ logging.debug("job[%s] executing %r", job.name, cmd)
4458+ return_code = logging_popen.call(cmd)
4459+ else:
4460+ # XXX: sadly using /bin/sh results in broken output
4461+ # XXX: maybe run it both ways and raise exceptions on differences?
4462+ cmd = ['bash', '-c', job.command]
4463+ logging.debug("job[%s] executing %r", job.name, cmd)
4464+ return_code = logging_popen.call(
4465+ cmd, env=self._get_script_env(job, config))
4466+ logger.debug("job[%s] command return code: %r",
4467+ job.name, return_code)
4468+ # XXX: Perhaps handle process dying from signals here
4469+ # When the process is killed proc.returncode is not set
4470+ # and another (cannot remember now) attribute is set
4471+ fjson = os.path.join(self._jobs_io_log_dir, "{}.json".format(slug))
4472+ with open(fjson, "wt") as stream:
4473+ io_log_write(io_log_builder.io_log, stream)
4474+ stream.flush()
4475+ os.fsync(stream.fileno())
4476+ return return_code, fjson
4477
4478=== added directory 'plainbox/plainbox/impl/secure'
4479=== added file 'plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER'
4480--- plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER 1970-01-01 00:00:00 +0000
4481+++ plainbox/plainbox/impl/secure/checkbox_trusted_launcher.py.OTHER 2013-08-28 12:37:27 +0000
4482@@ -0,0 +1,395 @@
4483+# This file is part of Checkbox.
4484+#
4485+# Copyright 2013 Canonical Ltd.
4486+# Written by:
4487+# Sylvain Pineau <sylvain.pineau@canonical.com>
4488+#
4489+# Checkbox is free software: you can redistribute it and/or modify
4490+# it under the terms of the GNU General Public License as published by
4491+# the Free Software Foundation, either version 3 of the License, or
4492+# (at your option) any later version.
4493+#
4494+# Checkbox is distributed in the hope that it will be useful,
4495+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4496+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4497+# GNU General Public License for more details.
4498+#
4499+# You should have received a copy of the GNU General Public License
4500+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
4501+
4502+"""
4503+:mod:`plainbox.impl.secure.checkbox_trusted_launcher` -- command launcher
4504+=========================================================================
4505+
4506+.. warning::
4507+
4508+ THIS MODULE DOES NOT HAVE STABLE PUBLIC API
4509+"""
4510+
4511+import argparse
4512+import collections
4513+import glob
4514+import hashlib
4515+import json
4516+import os
4517+import re
4518+import subprocess
4519+from inspect import cleandoc
4520+
4521+
4522+class BaseJob:
4523+ """
4524+ Base Job definition class.
4525+ """
4526+
4527+ @property
4528+ def plugin(self):
4529+ return self.__getattr__('plugin')
4530+
4531+ @property
4532+ def command(self):
4533+ try:
4534+ return self.__getattr__('command')
4535+ except AttributeError:
4536+ return None
4537+
4538+ @property
4539+ def environ(self):
4540+ try:
4541+ return self.__getattr__('environ')
4542+ except AttributeError:
4543+ return None
4544+
4545+ @property
4546+ def user(self):
4547+ try:
4548+ return self.__getattr__('user')
4549+ except AttributeError:
4550+ return None
4551+
4552+ def __init__(self, data):
4553+ self._data = data
4554+
4555+ def __getattr__(self, attr):
4556+ if attr in self._data:
4557+ return self._data[attr]
4558+ raise AttributeError(attr)
4559+
4560+ def get_checksum(self):
4561+ """
4562+ Compute a checksum of the job definition.
4563+
4564+ This method can be used to compute the checksum of the canonical form
4565+ of the job definition. The canonical form is the UTF-8 encoded JSON
4566+ serialization of the data that makes up the full definition of the job
4567+ (all keys and values). The JSON serialization uses no indent and
4568+ minimal separators.
4569+
4570+ The checksum is defined as the SHA256 hash of the canonical form.
4571+ """
4572+ # Ideally we'd use simplejson.dumps() with sorted keys to get
4573+ # predictable serialization but that's another dependency. To get
4574+ # something simple that is equally reliable, just sort all the keys
4575+ # manually and ask standard json to serialize that..
4576+ sorted_data = collections.OrderedDict(sorted(self._data.items()))
4577+ # Compute the canonical form which is arbitrarily defined as sorted
4578+ # json text with default indent and separator settings.
4579+ canonical_form = json.dumps(
4580+ sorted_data, indent=None, separators=(',', ':'))
4581+ # Compute the sha256 hash of the UTF-8 encoding of the canonical form
4582+ # and return the hex digest as the checksum that can be displayed.
4583+ return hashlib.sha256(canonical_form.encode('UTF-8')).hexdigest()
4584+
4585+ def get_environ_settings(self):
4586+ """
4587+ Return a set of requested environment variables
4588+ """
4589+ if self.environ is not None:
4590+ return {variable for variable in re.split('[\s,]+', self.environ)}
4591+ else:
4592+ return set()
4593+
4594+ def modify_execution_environment(self, environ, packages):
4595+ """
4596+ Compute the environment the script will be executed in
4597+ """
4598+ # Get a proper environment
4599+ env = dict(os.environ)
4600+ # Use non-internationalized environment
4601+ env['LANG'] = 'C.UTF-8'
4602+ # Create CHECKBOX*_SHARE for every checkbox related packages
4603+ # Add their respective script directory to the PATH variable
4604+ # giving precedence to those located in /usr/lib/
4605+ for path in packages:
4606+ basename = os.path.basename(path)
4607+ env[basename.upper().replace('-', '_') + '_SHARE'] = path
4608+ # Update PATH so that scripts can be found
4609+ env['PATH'] = os.pathsep.join([
4610+ os.path.join('usr', 'lib', basename, 'bin'),
4611+ os.path.join(path, 'scripts')]
4612+ + env.get("PATH", "").split(os.pathsep))
4613+ if 'CHECKBOX_DATA' in env:
4614+ env['CHECKBOX_DATA'] = environ['CHECKBOX_DATA']
4615+ # Add new environment variables only if they are defined in the
4616+ # job environ property
4617+ for key in self.get_environ_settings():
4618+ if key in environ:
4619+ env[key] = environ[key]
4620+ return env
4621+
4622+
4623+class BaseRFC822Record:
4624+ """
4625+ Base class for tracking RFC822 records
4626+
4627+ This is a simple container for the dictionary of data.
4628+ """
4629+
4630+ def __init__(self, data):
4631+ self._data = data
4632+
4633+ @property
4634+ def data(self):
4635+ """
4636+ The data set (dictionary)
4637+ """
4638+ return self._data
4639+
4640+
4641+class RFC822SyntaxError(SyntaxError):
4642+ """
4643+ SyntaxError subclass for RFC822 parsing functions
4644+ """
4645+
4646+ def __init__(self, filename, lineno, msg):
4647+ self.filename = filename
4648+ self.lineno = lineno
4649+ self.msg = msg
4650+
4651+
4652+def load_rfc822_records(stream, data_cls=dict):
4653+ """
4654+ Load a sequence of rfc822-like records from a text stream.
4655+
4656+ Each record consists of any number of key-value pairs. Subsequent records
4657+ are separated by one blank line. A record key may have a multi-line value
4658+ if the line starts with whitespace character.
4659+
4660+ Returns a list of subsequent values as instances BaseRFC822Record class. If
4661+ the optional data_cls argument is collections.OrderedDict then the values
4662+ retain their original ordering.
4663+ """
4664+ return list(gen_rfc822_records(stream, data_cls))
4665+
4666+
4667+def gen_rfc822_records(stream, data_cls=dict):
4668+ """
4669+ Load a sequence of rfc822-like records from a text stream.
4670+
4671+ Each record consists of any number of key-value pairs. Subsequent records
4672+ are separated by one blank line. A record key may have a multi-line value
4673+ if the line starts with whitespace character.
4674+
4675+ Returns a list of subsequent values as instances BaseRFC822Record class. If
4676+ the optional data_cls argument is collections.OrderedDict then the values
4677+ retain their original ordering.
4678+ """
4679+ record = None
4680+ data = None
4681+ key = None
4682+ value_list = None
4683+
4684+ def _syntax_error(msg):
4685+ """
4686+ Report a syntax error in the current line
4687+ """
4688+ try:
4689+ filename = stream.name
4690+ except AttributeError:
4691+ filename = None
4692+ return RFC822SyntaxError(filename, lineno, msg)
4693+
4694+ def _new_record():
4695+ """
4696+ Reset local state to track new record
4697+ """
4698+ nonlocal key
4699+ nonlocal value_list
4700+ nonlocal record
4701+ nonlocal data
4702+ key = None
4703+ value_list = None
4704+ data = data_cls()
4705+ record = BaseRFC822Record(data)
4706+
4707+ def _commit_key_value_if_needed():
4708+ """
4709+ Finalize the most recently seen key: value pair
4710+ """
4711+ nonlocal key
4712+ if key is not None:
4713+ data[key] = cleandoc('\n'.join(value_list))
4714+ key = None
4715+
4716+ # Start with an empty record
4717+ _new_record()
4718+ # Iterate over subsequent lines of the stream
4719+ for lineno, line in enumerate(stream, start=1):
4720+ # Treat empty lines as record separators
4721+ if line.strip() == "":
4722+ # Commit the current record so that the multi-line value of the
4723+ # last key, if any, is saved as a string
4724+ _commit_key_value_if_needed()
4725+ # If data is non-empty, yield the record, this allows us to safely
4726+ # use newlines for formatting
4727+ if data:
4728+ yield record
4729+ # Reset local state so that we can build a new record
4730+ _new_record()
4731+ # Treat lines staring with whitespace as multi-line continuation of the
4732+ # most recently seen key-value
4733+ elif line.startswith(" "):
4734+ if key is None:
4735+ # If we have not seen any keys yet then this is a syntax error
4736+ raise _syntax_error("Unexpected multi-line value")
4737+ # Append the current line to the list of values of the most recent
4738+ # key. This prevents quadratic complexity of string concatenation
4739+ if line == " .\n":
4740+ value_list.append(" ")
4741+ elif line == " ..\n":
4742+ value_list.append(" .")
4743+ else:
4744+ value_list.append(line.rstrip())
4745+ # Treat lines with a colon as new key-value pairs
4746+ elif ":" in line:
4747+ # Since we have a new, key-value pair we need to commit any
4748+ # previous key that we may have (regardless of multi-line or
4749+ # single-line values).
4750+ _commit_key_value_if_needed()
4751+ # Parse the line by splitting on the colon, get rid of additional
4752+ # whitespace from both key and the value
4753+ key, value = line.split(":", 1)
4754+ key = key.strip()
4755+ value = value.strip()
4756+ # Check if the key already exist in this message
4757+ if key in record.data:
4758+ raise _syntax_error((
4759+ "Job has a duplicate key {!r} "
4760+ "with old value {!r} and new value {!r}").format(
4761+ key, record.data[key], value))
4762+ # Construct initial value list out of the (only) value that we have
4763+ # so far. Additional multi-line values will just append to
4764+ # value_list
4765+ value_list = [value]
4766+ # Treat all other lines as syntax errors
4767+ else:
4768+ raise _syntax_error("Unexpected non-empty line")
4769+ # Make sure to commit the last key from the record
4770+ _commit_key_value_if_needed()
4771+ # Once we've seen the whole file return the last record, if any
4772+ if data:
4773+ yield record
4774+
4775+
4776+class Runner:
4777+ """
4778+ Runner for jobs
4779+
4780+ Executes the command process and pipes back stdout/stderr
4781+ """
4782+
4783+ CHECKBOXES = "/usr/share/checkbox*"
4784+
4785+ def __init__(self, builtin_jobs=[], packages=[]):
4786+ # List of all available jobs in system-wide locations
4787+ self.builtin_jobs = builtin_jobs
4788+ # List of all checkbox variants, like checkbox-oem(-.*)?
4789+ self.packages = packages
4790+
4791+ def path_expand(self, path):
4792+ for p in glob.glob(path):
4793+ self.packages.append(p)
4794+ for dirpath, dirs, filenames in os.walk(os.path.join(p, 'jobs')):
4795+ for name in filenames:
4796+ if name.endswith(".txt"):
4797+ yield os.path.join(dirpath, name)
4798+
4799+ def main(self, argv=None):
4800+ parser = argparse.ArgumentParser(prog="checkbox-trusted-launcher")
4801+ group = parser.add_mutually_exclusive_group(required=True)
4802+ group.add_argument('--hash', metavar='HASH', help='job hash to match')
4803+ group.add_argument(
4804+ '--warmup',
4805+ action='store_true',
4806+ help='Return immediately, only useful when used with pkexec(1)')
4807+ parser.add_argument(
4808+ '--via',
4809+ metavar='LOCAL-JOB-HASH',
4810+ dest='via_hash',
4811+ help='Local job hash to use to match the generated job')
4812+ parser.add_argument(
4813+ 'ENV', metavar='NAME=VALUE', nargs='*',
4814+ help='Set each NAME to VALUE in the string environment')
4815+ args = parser.parse_args(argv)
4816+
4817+ if args.warmup:
4818+ return 0
4819+
4820+ for filename in self.path_expand(self.CHECKBOXES):
4821+ stream = open(filename, "r", encoding="utf-8")
4822+ for message in load_rfc822_records(stream):
4823+ self.builtin_jobs.append(BaseJob(message.data))
4824+ stream.close()
4825+ lookup_list = [j for j in self.builtin_jobs if j.user]
4826+
4827+ args.ENV = dict(item.split('=') for item in args.ENV)
4828+
4829+ if args.via_hash is not None:
4830+ local_list = [j for j in self.builtin_jobs if j.plugin == 'local']
4831+ desired_job_list = [j for j in local_list
4832+ if j.get_checksum() == args.via_hash]
4833+ if desired_job_list:
4834+ via_job = desired_job_list.pop()
4835+ via_job_result = subprocess.Popen(
4836+ via_job.command,
4837+ shell=True,
4838+ universal_newlines=True,
4839+ stdout=subprocess.PIPE,
4840+ env=via_job.modify_execution_environment(
4841+ args.ENV,
4842+ self.packages)
4843+ )
4844+ try:
4845+ for message in load_rfc822_records(via_job_result.stdout):
4846+ lookup_list.append(BaseJob(message.data))
4847+ finally:
4848+ # Always call Popen.wait() in order to avoid zombies
4849+ via_job_result.stdout.close()
4850+ via_job_result.wait()
4851+
4852+ try:
4853+ target_job = [j for j in lookup_list
4854+ if j.get_checksum() == args.hash][0]
4855+ except IndexError:
4856+ return "Job not found"
4857+ try:
4858+ os.execve(
4859+ '/bin/bash',
4860+ ['bash', '-c', target_job.command],
4861+ target_job.modify_execution_environment(
4862+ args.ENV,
4863+ self.packages)
4864+ )
4865+ # if execve doesn't fail, it never returns...
4866+ except OSError:
4867+ return "Fatal error"
4868+ finally:
4869+ return "Fatal error"
4870+
4871+
4872+def main(argv=None):
4873+ """
4874+ Entry point for the checkbox trusted launcher
4875+ """
4876+ runner = Runner()
4877+ raise SystemExit(runner.main(argv))
4878
4879=== added file 'plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER'
4880--- plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER 1970-01-01 00:00:00 +0000
4881+++ plainbox/plainbox/impl/secure/test_checkbox_trusted_launcher.py.OTHER 2013-08-28 12:37:27 +0000
4882@@ -0,0 +1,280 @@
4883+# This file is part of Checkbox.
4884+#
4885+# Copyright 2013 Canonical Ltd.
4886+# Written by:
4887+# Sylvain Pineau <sylvain.pineau@canonical.com>
4888+#
4889+# Checkbox is free software: you can redistribute it and/or modify
4890+# it under the terms of the GNU General Public License as published by
4891+# the Free Software Foundation, either version 3 of the License, or
4892+# (at your option) any later version.
4893+#
4894+# Checkbox is distributed in the hope that it will be useful,
4895+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4896+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4897+# GNU General Public License for more details.
4898+#
4899+# You should have received a copy of the GNU General Public License
4900+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
4901+
4902+"""
4903+plainbox.impl.secure.test_checkbox_trusted_launcher
4904+===================================================
4905+
4906+Test definitions for plainbox.impl.secure.checkbox_trusted_launcher module
4907+"""
4908+
4909+import os
4910+
4911+from inspect import cleandoc
4912+from io import StringIO
4913+from mock import Mock, patch
4914+from tempfile import NamedTemporaryFile, TemporaryDirectory
4915+from unittest import TestCase
4916+
4917+from plainbox.impl.secure.checkbox_trusted_launcher import BaseJob
4918+from plainbox.impl.secure.checkbox_trusted_launcher import load_rfc822_records
4919+from plainbox.impl.secure.checkbox_trusted_launcher import main
4920+from plainbox.impl.secure.checkbox_trusted_launcher import Runner
4921+from plainbox.impl.test_rfc822 import RFC822ParserTestsMixIn
4922+from plainbox.testing_utils.io import TestIO
4923+from plainbox.testing_utils.testcases import TestCaseWithParameters
4924+
4925+
4926+class TestJobDefinition(TestCase):
4927+
4928+ def setUp(self):
4929+ self._full_record = {
4930+ 'plugin': 'plugin',
4931+ 'command': 'command',
4932+ 'environ': 'environ',
4933+ 'user': 'user'
4934+ }
4935+ self._min_record = {
4936+ 'plugin': 'plugin',
4937+ 'name': 'name',
4938+ }
4939+
4940+ def test_smoke_full_record(self):
4941+ job = BaseJob(self._full_record)
4942+ self.assertEqual(job.plugin, "plugin")
4943+ self.assertEqual(job.command, "command")
4944+ self.assertEqual(job.environ, "environ")
4945+ self.assertEqual(job.user, "user")
4946+
4947+ def test_smoke_min_record(self):
4948+ job = BaseJob(self._min_record)
4949+ self.assertEqual(job.plugin, "plugin")
4950+ self.assertEqual(job.command, None)
4951+ self.assertEqual(job.environ, None)
4952+ self.assertEqual(job.user, None)
4953+
4954+ def test_checksum_smoke(self):
4955+ job1 = BaseJob({'plugin': 'plugin', 'user': 'root'})
4956+ identical_to_job1 = BaseJob({'plugin': 'plugin', 'user': 'root'})
4957+ # Two distinct but identical jobs have the same checksum
4958+ self.assertEqual(job1.get_checksum(), identical_to_job1.get_checksum())
4959+ job2 = BaseJob({'plugin': 'plugin', 'user': 'anonymous'})
4960+ # Two jobs with different definitions have different checksum
4961+ self.assertNotEqual(job1.get_checksum(), job2.get_checksum())
4962+ # The checksum is stable and does not change over time
4963+ self.assertEqual(
4964+ job1.get_checksum(),
4965+ "c47cc3719061e4df0010d061e6f20d3d046071fd467d02d093a03068d2f33400")
4966+
4967+
4968+class ParsingTests(TestCaseWithParameters):
4969+
4970+ parameter_names = ('glue',)
4971+ parameter_values = (
4972+ ('commas',),
4973+ ('spaces',),
4974+ ('tabs',),
4975+ ('newlines',),
4976+ ('spaces_and_commas',),
4977+ ('multiple_spaces',),
4978+ ('multiple_commas',)
4979+ )
4980+ parameters_keymap = {
4981+ 'commas': ',',
4982+ 'spaces': ' ',
4983+ 'tabs': '\t',
4984+ 'newlines': '\n',
4985+ 'spaces_and_commas': ', ',
4986+ 'multiple_spaces': ' ',
4987+ 'multiple_commas': ',,,,'
4988+ }
4989+
4990+ def test_environ_parsing_with_various_separators(self):
4991+ job = BaseJob({
4992+ 'name': 'name',
4993+ 'plugin': 'plugin',
4994+ 'environ': self.parameters_keymap[
4995+ self.parameters.glue].join(['foo', 'bar', 'froz'])})
4996+ expected = set({'foo', 'bar', 'froz'})
4997+ observed = job.get_environ_settings()
4998+ self.assertEqual(expected, observed)
4999+
5000+ def test_environ_parsing_empty(self):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: