Merge lp:~pwlars/ubuntu-test-cases/touch-rmrf into lp:ubuntu-test-cases

Proposed by Paul Larson
Status: Superseded
Proposed branch: lp:~pwlars/ubuntu-test-cases/touch-rmrf
Merge into: lp:ubuntu-test-cases
Diff against target: 6374 lines (+5848/-0) (has conflicts)
98 files modified
README-cli.rst (+80/-0)
jenkins/production.py (+95/-0)
jenkins/setup_jenkins.py (+213/-0)
jenkins/staging.py (+22/-0)
jenkins/templates/touch-base.xml.jinja2 (+93/-0)
jenkins/templates/touch-master.xml.jinja2 (+121/-0)
jenkins/templates/touch-smoke.xml.jinja2 (+163/-0)
jenkins/testconfig.py (+176/-0)
scripts/assert-image (+28/-0)
scripts/combine_results (+94/-0)
scripts/dashboard.py (+295/-0)
scripts/device (+191/-0)
scripts/get-adb-id (+39/-0)
scripts/jenkins.sh (+186/-0)
scripts/junit2utah.py (+70/-0)
scripts/provision.sh (+131/-0)
scripts/reboot-and-wait (+41/-0)
scripts/run-autopilot-tests.sh (+215/-0)
scripts/run-smoke (+357/-0)
scripts/run-touch-upgrade.sh (+46/-0)
scripts/statsd.py (+35/-0)
selftests/test_junit2utah.py (+49/-0)
selftests/test_reboot_and_wait.py (+66/-0)
selftests/test_run_smoke.py (+143/-0)
selftests/test_statsd.py (+64/-0)
tests/click_image_tests/check_preinstalled_list/check_preinstalled_list.py (+61/-0)
tests/click_image_tests/check_preinstalled_list/tc_control (+10/-0)
tests/click_image_tests/master.run (+5/-0)
tests/click_image_tests/tslist.run (+1/-0)
tests/customizations/master.run (+5/-0)
tests/customizations/setup.sh (+10/-0)
tests/customizations/ts_control (+1/-0)
tests/customizations/tslist.auto (+4/-0)
tests/default/ifconfig/tc_control (+13/-0)
tests/default/install/tc_control (+12/-0)
tests/default/master.run (+5/-0)
tests/default/netstat/tc_control (+12/-0)
tests/default/ping/pingtest.sh (+12/-0)
tests/default/ping/tc_control (+12/-0)
tests/default/pwd/tc_control (+9/-0)
tests/default/pwd/test.sh (+18/-0)
tests/default/route/tc_control (+12/-0)
tests/default/systemsettle/systemsettle.sh (+120/-0)
tests/default/systemsettle/tc_control (+9/-0)
tests/default/ts_control (+3/-0)
tests/default/tslist.run (+10/-0)
tests/default/uname/tc_control (+12/-0)
tests/default/unity8/tc_control (+9/-0)
tests/default/vmstat/tc_control (+12/-0)
tests/eventstat/eventstat/eventstat.sh (+5/-0)
tests/eventstat/eventstat/setup.sh (+7/-0)
tests/eventstat/eventstat/tc_control (+12/-0)
tests/eventstat/master.run (+5/-0)
tests/eventstat/tslist.run (+1/-0)
tests/memevent/master.run (+5/-0)
tests/memevent/setup.sh (+8/-0)
tests/memevent/ts_control (+1/-0)
tests/memevent/tslist.auto (+7/-0)
tests/memevent/ubuntu_test_cases/__init__.py (+6/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/__init__.py (+1/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/__init__.py (+21/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/browser.py (+103/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/camera.py (+58/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/gallery.py (+41/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/media_player.py (+53/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/matchers.py (+37/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/probes.py (+170/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/smem-tabs (+687/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/__init__.py (+1/-0)
tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/test_memory_usage.py (+79/-0)
tests/sdk/master.run (+5/-0)
tests/sdk/run_test.sh (+20/-0)
tests/sdk/setup.sh (+12/-0)
tests/sdk/ts_control (+1/-0)
tests/sdk/tslist.auto (+3/-0)
tests/security/master.run (+5/-0)
tests/security/run_test.sh (+25/-0)
tests/security/setup.sh (+12/-0)
tests/security/ts_control (+1/-0)
tests/security/tslist.auto (+6/-0)
tests/smem/master.run (+15/-0)
tests/smem/smem/smem-tabs (+687/-0)
tests/smem/smem/smem.sh (+12/-0)
tests/smem/smem/tc_control (+11/-0)
tests/smem/tslist.run (+1/-0)
tests/systemsettle/systemsettle-after/tc_control (+9/-0)
tests/systemsettle/systemsettle-before/tc_control (+9/-0)
tests/systemsettle/systemsettle.sh (+119/-0)
tests/systemsettle/tslist.run (+2/-0)
utils/host/adb-shell (+23/-0)
utils/host/autopilot-list (+18/-0)
utils/host/autopilot-run (+7/-0)
utils/host/prepare-autopilot-test.sh (+5/-0)
utils/target/autopilot-list (+13/-0)
utils/target/autopilot-run (+6/-0)
utils/target/prepare-autopilot-test.sh (+17/-0)
utils/target/unlock_screen.py (+98/-0)
utils/target/unlock_screen.sh (+8/-0)
Conflict adding file scripts.  Moved existing file to scripts.moved.
To merge this branch: bzr merge lp:~pwlars/ubuntu-test-cases/touch-rmrf
Reviewer Review Type Date Requested Status
Ubuntu Test Case Developers Pending
Review via email: mp+206217@code.launchpad.net

This proposal has been superseded by a proposal from 2014-02-13.

Commit message

Really remove the clientlogs/{{test}} directory (Fixes LP:1279744)

Description of the change

Fix for LP:1279744

To post a comment you must log in.

Unmerged revisions

186. By Paul Larson

Really remove the clientlogs/{{test}} dir

185. By Javier Collado

Fixed slider retrieval

184. By Andy Doan

provide an early exit for NETWORK_FILE as well as some documentation

183. By Andy Doan

assert-image failing due to typo

This gets assert-image working when the UUID's don't match

182. By Andy Doan

fix dashboard issue with install-and-boot

our utah yaml for this was missing some required fields

181. By Andy Doan

update testconfig.py to work with touch_custom and touch_sf4p

somewhere along the line this got broke. this fixes things.

180. By Andy Doan

don't leak the api-key to the public

179. By Andy Doan

run-autopilot-tests.sh requires DASHBOARD_PORT to be set

i meant for this to be optional, but its quicker just to set it
here for now

178. By Andy Doan

fix a jenkins stack-trace

177. By Andy Doan

master jobs need to be restricted to run from "master" and not just pick a random slave

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'README-cli.rst'
2--- README-cli.rst 1970-01-01 00:00:00 +0000
3+++ README-cli.rst 2014-02-13 16:08:19 +0000
4@@ -0,0 +1,80 @@
5+Touch Testing From the CLI
6+==========================
7+
8+The touch testing execution framework was written so that its very easy to
9+run tests from home in the exact same way test are run in the lab. The only
10+thigs you need are:
11+
12+ * This bzr branch
13+ * The phablet-tools_ package
14+ * An Ubuntu Touch supported_ device
15+
16+.. _phablet-tools: http://launchpad.net/phablet-tools
17+.. _supported: http://wiki.ubuntu.com/Touch/Devices
18+
19+There are two pieces to touch testing, provisioning and test execution. These
20+functions are independent of one another. eg, If your device already
21+has the proper image/configuration, you can simply use the test-runner.
22+
23+Provisioning
24+------------
25+
26+The provisioning script is a simple wrapper to commands from phablet-tools
27+to get a device ready for testing. Provisioning is performed with the
28+scripts/provision.sh command. Running::
29+
30+ ./scripts/provision.sh -h
31+
32+will list supported options.
33+
34+NOTE: provision.sh requires a path to a network-manager wifi connection that
35+can be copied to the target device. By default this is set to
36+/home/ubuntu/magners-wifi. This can be overriden with the -n parameter.
37+
38+Executing Tests
39+---------------
40+
41+The touch testing repository supports both autopilot and UTAH test definitions.
42+
43+Executing Autopilot Tests
44+~~~~~~~~~~~~~~~~~~~~~~~~~
45+
46+One or more autopilot tests can be run on the target device using the command::
47+
48+ ./scripts/run-autopilot-tests.sh
49+
50+This is a small wrapper that uses phablet-tools to drive the tests. The
51+script can run one or more autopilot tests. By default it will reboot the
52+device between each test and ensure the device is settled using the
53+*system-settle* script. Both of those options can be disabled via command
54+line options. By default the script will create a directory named
55+*clientlogs* and then a subdirectory for each testsuite with result files.
56+These sub-directories include a xUnit XML formatted file, *test_results.xml*,
57+as well as several log files from the device to help with debugging failures.
58+
59+An example testing two applications::
60+
61+ ./scripts/run-autopilot-tests.sh -a dropping_letters_app -a music_app
62+
63+Executing UTAH Tests
64+~~~~~~~~~~~~~~~~~~~~
65+
66+Executing UTAH tests locally will require you to install the UTAH client
67+package from a PPA::
68+
69+ sudo add-apt-repository ppa:utah/stable
70+ sudo apt-get update
71+ sudo apt-get install utah-client
72+
73+With that package installed UTAH tests can be run with::
74+
75+ ./scripts/jenkins.sh
76+
77+This script runs one test at a time and will put its test artifacts under the
78+*clientlogs* directory similar to the autopilot runner. The UTAH result file
79+will be named clientlogs/utah.yaml.
80+
81+An example of running the sdk test suite::
82+
83+ ./scripts/jenkins.sh -a sdk
84+
85
86=== added directory 'jenkins'
87=== added file 'jenkins/production.py'
88--- jenkins/production.py 1970-01-01 00:00:00 +0000
89+++ jenkins/production.py 2014-02-13 16:08:19 +0000
90@@ -0,0 +1,95 @@
91+# The configuration matrix of our production device testing
92+
93+JENKINS = 'http://q-jenkins:8080'
94+
95+
96+def _url(channel, device):
97+ return 'http://system-image.ubuntu.com/%s/%s/index.json' \
98+ % (channel, device)
99+
100+
101+TRUSTY_MATRIX = [
102+ {
103+ 'image-type': 'touch_sf4p',
104+ 'include-qa': False,
105+ 'dashboard-host': 'ci.ubuntu.com',
106+ 'dashboard-port': '80',
107+ 'dashboard-user': 'doanac',
108+ 'devices': [
109+ {
110+ 'name': 'mako',
111+ 'slave-label': 'daily-mako',
112+ 'trigger_url': _url('trusty-proposed', 'mako'),
113+ },
114+ {
115+ 'name': 'maguro',
116+ 'slave-label': 'daily-maguro',
117+ 'trigger_url': _url('trusty-proposed', 'maguro'),
118+ },
119+ ],
120+ },
121+ {
122+ 'image-type': 'touch',
123+ 'include-qa': True,
124+ 'dashboard-host': 'ci.ubuntu.com',
125+ 'dashboard-port': '80',
126+ 'dashboard-user': 'doanac',
127+ 'devices': [
128+ {
129+ 'name': 'mako',
130+ 'slave-label': 'daily-mako',
131+ 'trigger_url': _url('trusty-proposed', 'mako'),
132+ },
133+ {
134+ 'name': 'maguro',
135+ 'slave-label': 'daily-maguro',
136+ 'trigger_url': _url('trusty-proposed', 'maguro'),
137+ },
138+ ],
139+ },
140+ {
141+ 'image-type': 'touch_custom',
142+ 'include-qa': False,
143+ 'dashboard-host': 'ci.ubuntu.com',
144+ 'dashboard-port': '80',
145+ 'dashboard-user': 'doanac',
146+ 'devices': [
147+ {
148+ 'name': 'mako',
149+ 'slave-label': 'daily-mako',
150+ 'trigger_url': _url('trusty-proposed-customized', 'mako'),
151+ },
152+ ],
153+ 'IMAGE_OPT': 'export IMAGE_OPT="ubuntu-system -b '
154+ '--channel trusty-proposed-customized"'
155+ },
156+]
157+
158+SAUCY_MATRIX = [
159+ {
160+ 'image-type': 'touch_mir',
161+ 'include-qa': True,
162+ 'dashboard-host': 'ci.ubuntu.com',
163+ 'dashboard-port': '80',
164+ 'dashboard-user': 'doanac',
165+ 'devices': [
166+ {
167+ 'name': 'mako',
168+ 'slave-label': 'daily-mako',
169+ 'trigger_url': _url('saucy-proposed', 'mako'),
170+ },
171+ {
172+ 'name': 'maguro',
173+ 'slave-label': 'daily-maguro',
174+ 'trigger_url': _url('saucy-proposed', 'maguro'),
175+ },
176+ ],
177+ 'IMAGE_OPT': 'export IMAGE_OPT="ubuntu-system -b '
178+ '--channel saucy-proposed"'
179+ },
180+]
181+
182+MATRIX = {
183+ 'trusty': TRUSTY_MATRIX,
184+ 'saucy': SAUCY_MATRIX,
185+}
186
187=== added file 'jenkins/setup_jenkins.py'
188--- jenkins/setup_jenkins.py 1970-01-01 00:00:00 +0000
189+++ jenkins/setup_jenkins.py 2014-02-13 16:08:19 +0000
190@@ -0,0 +1,213 @@
191+#!/usr/bin/env python
192+
193+# Ubuntu Testing Automation Harness
194+# Copyright 2013 Canonical Ltd.
195+
196+# This program is free software: you can redistribute it and/or modify it
197+# under the terms of the GNU General Public License version 3, as published
198+# by the Free Software Foundation.
199+
200+# This program is distributed in the hope that it will be useful, but
201+# WITHOUT ANY WARRANTY; without even the implied warranties of
202+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
203+# PURPOSE. See the GNU General Public License for more details.
204+
205+# You should have received a copy of the GNU General Public License along
206+# with this program. If not, see <http://www.gnu.org/licenses/>.
207+
208+import argparse
209+import imp
210+import jenkins
211+import jinja2
212+import logging
213+import os
214+
215+import testconfig
216+
217+from distro_info import UbuntuDistroInfo
218+DEV_SERIES = UbuntuDistroInfo().devel()
219+
220+
221+def _get_parser():
222+ """Create and return command line parser.
223+
224+ :returns: command line parser
225+ :rtype: argparse.ArgumentParser
226+
227+ """
228+ parser = argparse.ArgumentParser(
229+ description='Create/Update upgrade testing jenkins jobs.')
230+ parser.add_argument("-d", "--dryrun", action="store_true",
231+ help="Dry run mode. Don't execute jenkins commands.")
232+ parser.add_argument("-u", "--username",
233+ help="username to use when logging into Jenkins.")
234+ parser.add_argument("-p", "--password",
235+ help="username to use when logging into Jenkins")
236+ parser.add_argument("--dashboard-key", default="",
237+ help="The api-key for dashboard updates")
238+ parser.add_argument("-b", "--branch", default="lp:ubuntu-test-cases/touch",
239+ help="The branch this is located. default=%(default)s")
240+ parser.add_argument("-c", "--config", required=True,
241+ type=argparse.FileType('r'),
242+ help="The job config to use.")
243+ parser.add_argument("-P", "--publish", action="store_true",
244+ help="Publish at the end of the job")
245+ parser.add_argument("--prefix",
246+ help="Prefix to add to the beginning of the job name")
247+ parser.add_argument("-s", "--series", default=DEV_SERIES,
248+ help=("series of Ubuntu to download "
249+ "(default=%(default)s)"))
250+ parser.add_argument("-w", "--wait", type=int, default=300,
251+ help=("How long to wait after jenkins triggers the"
252+ "install-and-boot job (default=%(default)d)"))
253+ return parser
254+
255+
256+def _get_jenkins(url, username, password):
257+ logging.info('Attempting to login to jenkins at %s', url)
258+ if username is not None:
259+ logging.info('...with authentication as user: %s', username)
260+ instance = jenkins.Jenkins(url, username=username, password=password)
261+ else:
262+ logging.info('...without authentication')
263+ instance = jenkins.Jenkins(url)
264+
265+ return instance
266+
267+
268+def _get_environment():
269+ base = os.path.join(os.path.dirname(__file__), 'templates')
270+ return jinja2.Environment(
271+ loader=jinja2.FileSystemLoader(base),
272+ undefined=jinja2.StrictUndefined,
273+ trim_blocks=True)
274+
275+
276+def _publish(instance, env, args, template, jobname, **params):
277+ tmpl = env.get_template(template)
278+ cfg = tmpl.render(**params)
279+ if args.dryrun:
280+ _dryrun_func(jobname, cfg)
281+ return
282+ if instance.job_exists(jobname):
283+ logging.info('reconfiguring job %s', jobname)
284+ instance.reconfig_job(jobname, cfg)
285+ else:
286+ logging.info('creating job %s', jobname)
287+ instance.create_job(jobname, cfg)
288+
289+
290+def _configure_smoke(instance, env, args, config_item, device):
291+ defserial = '$(${BZRDIR}/scripts/get-adb-id ${NODE_NAME})'
292+
293+ params = {
294+ 'name': device['slave-label'],
295+ 'serial': device.get('serial', defserial),
296+ 'publish': args.publish,
297+ 'branch': args.branch,
298+ 'imagetype': config_item['image-type'],
299+ 'image_opt': config_item.get('IMAGE_OPT', ''),
300+ 'statsd_key': config_item.get('statsd-key', ''),
301+ 'dashboard_host': config_item.get('dashboard-host', ''),
302+ 'dashboard_port': config_item.get('dashboard-port', ''),
303+ 'dashboard_prefix': config_item.get('dashboard-prefix', ''),
304+ 'dashboard_user': config_item.get('dashboard-user', ''),
305+ 'dashboard_key': args.dashboard_key,
306+ }
307+
308+ prefix = ""
309+ if(args.prefix):
310+ prefix = args.prefix + "-"
311+
312+ job = '{}{}-{}-{}-smoke-daily'.format(
313+ prefix, args.series, config_item['image-type'], device['name'])
314+ _publish(instance, env, args, 'touch-smoke.xml.jinja2', job, **params)
315+ return job
316+
317+
318+def _configure_qa_job(instance, env, args, config_item, device, test):
319+ defserial = '$(${BZRDIR}/scripts/get-adb-id ${NODE_NAME})'
320+ params = {
321+ 'name': device['slave-label'],
322+ 'serial': device.get('serial', defserial),
323+ 'publish': args.publish,
324+ 'branch': args.branch,
325+ 'imagetype': config_item['image-type'],
326+ 'image_opt': config_item.get('IMAGE_OPT', ''),
327+ 'test': test.name,
328+ }
329+ prefix = ""
330+ if(args.prefix):
331+ prefix = args.prefix + "-"
332+ job = test.fmt.format(prefix=prefix,
333+ series=args.series,
334+ testname=test.name,
335+ imagetype=config_item['image-type'],
336+ type=device['name'])
337+ _publish(instance, env, args, 'touch-base.xml.jinja2', job, **params)
338+ return job
339+
340+
341+def _configure_qa_jobs(instance, env, args, config_item, device):
342+ tests = testconfig.TESTSUITES
343+ tests = testconfig.filter_tests(tests, config_item['image-type'])
344+ tests = [t for t in tests if not t.smoke]
345+ jobs = []
346+ for t in tests:
347+ j = _configure_qa_job(instance, env, args, config_item, device, t)
348+ jobs.append(j)
349+ return jobs
350+
351+
352+def _configure_master(instance, env, args, config_item, device, smoke, jobs):
353+ params = {
354+ 'branch': args.branch,
355+ 'trigger_url': device.get('trigger_url', ''),
356+ 'wait': args.wait,
357+ 'projects': jobs,
358+ 'smoke_job': smoke,
359+ }
360+ prefix = ""
361+ if(args.prefix):
362+ prefix = args.prefix + "-"
363+ job = testconfig.DEF_FMT.format(prefix=prefix,
364+ series=args.series,
365+ testname='master',
366+ imagetype=config_item['image-type'],
367+ type=device['name'])
368+ _publish(instance, env, args, 'touch-master.xml.jinja2', job, **params)
369+ return job
370+
371+
372+def _dryrun_func(jobname, config):
373+ logging.debug(jobname)
374+ logging.debug(config)
375+
376+
377+def main():
378+ logging.basicConfig(level=logging.DEBUG)
379+ args = _get_parser().parse_args()
380+
381+ config = imp.load_source('', 'config.py', args.config)
382+ if args.series not in config.MATRIX:
383+ print('"%s" series is not supported by this config.' % args.series)
384+ exit(1)
385+
386+ jenkins_inst = _get_jenkins(config.JENKINS, args.username, args.password)
387+ if args.dryrun:
388+ jenkins_inst.create_job = _dryrun_func
389+ jenkins_inst.reconfig_job = _dryrun_func
390+
391+ env = _get_environment()
392+
393+ for item in config.MATRIX[args.series]:
394+ for device in item['devices']:
395+ job = _configure_smoke(jenkins_inst, env, args, item, device)
396+ jobs = []
397+ if item.get('include-qa'):
398+ jobs = _configure_qa_jobs(
399+ jenkins_inst, env, args, item, device)
400+ _configure_master(jenkins_inst, env, args, item, device, job, jobs)
401+
402+if __name__ == '__main__':
403+ main()
404
405=== added file 'jenkins/staging.py'
406--- jenkins/staging.py 1970-01-01 00:00:00 +0000
407+++ jenkins/staging.py 2014-02-13 16:08:19 +0000
408@@ -0,0 +1,22 @@
409+# The configuration matrix of our staging device testing
410+
411+JENKINS = 'http://dev-jenkins.ubuntu-ci:8080/'
412+
413+TRUSTY_MATRIX = [
414+ {
415+ 'image-type': 'touch',
416+ 'statsd-key': 'ubuntu-ci.daily-image.staging',
417+ 'include-qa': True,
418+ 'devices': [
419+ {
420+ 'name': 'mako',
421+ 'slave-label': 'mako',
422+ #'trigger_url': 'http://system-image.ubuntu.com/trusty-proposed/mako/index.json',
423+ }
424+ ],
425+ },
426+]
427+
428+MATRIX = {
429+ 'trusty': TRUSTY_MATRIX,
430+}
431
432=== added directory 'jenkins/templates'
433=== added file 'jenkins/templates/touch-base.xml.jinja2'
434--- jenkins/templates/touch-base.xml.jinja2 1970-01-01 00:00:00 +0000
435+++ jenkins/templates/touch-base.xml.jinja2 2014-02-13 16:08:19 +0000
436@@ -0,0 +1,93 @@
437+<?xml version='1.0' encoding='UTF-8'?>
438+<project>
439+ <actions/>
440+ <description>
441+&lt;pre&gt;
442+#NOTE: Automatically created from a script as part of daily smoke testing&#xd;
443+ {{branch}}&#xd;
444+&lt;/pre&gt;
445+ </description>
446+ <keepDependencies>false</keepDependencies>
447+ <properties>
448+ <hudson.model.ParametersDefinitionProperty>
449+ <parameterDefinitions>
450+ <hudson.model.StringParameterDefinition>
451+ <name>INSTALL_URL</name>
452+ <description>A URL to the previous job. If provided this job will use the same install options as it used. If the device executing the job happens to have the exact same image, then provisioning can be skipped.
453+ </description>
454+ <defaultValue></defaultValue>
455+ </hudson.model.StringParameterDefinition>
456+ </parameterDefinitions>
457+ </hudson.model.ParametersDefinitionProperty>
458+ <com.sonyericsson.rebuild.RebuildSettings>
459+ <autoRebuild>false</autoRebuild>
460+ </com.sonyericsson.rebuild.RebuildSettings>
461+ </properties>
462+ <scm class="hudson.scm.NullSCM"/>
463+ <assignedNode>{{ name }}</assignedNode>
464+ <canRoam>false</canRoam>
465+ <disabled>false</disabled>
466+ <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
467+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
468+ <concurrentBuild>true</concurrentBuild>
469+ <builders>
470+ <hudson.tasks.Shell>
471+ <command><![CDATA[
472+set -e
473+rm -rf *
474+
475+BRANCH="{{branch}}"
476+BZRDIR=`echo "$BRANCH" | awk -F/ '{ print $(NF) }'`
477+BZRDIR=$(readlink -f $BZRDIR)
478+bzr branch ${BRANCH} ${BZRDIR}
479+
480+export ANDROID_SERIAL={{serial}}
481+export IMAGE_TYPE={{imagetype}}
482+
483+{{image_opt}}
484+${BZRDIR}/scripts/run-smoke -t {{test}}
485+
486+# move results to base directory as expected by dashboard:
487+mv clientlogs/{{test}}/* ./clientlogs/
488+rm -rf clientlogs/{{test}}
489+
490+{% if test == 'memevent' %}
491+adb pull /tmp/memory_usage.json ./clientlogs/
492+{% endif %}
493+ ]]></command>
494+ </hudson.tasks.Shell>
495+ </builders>
496+ <publishers>
497+ <hudson.tasks.ArtifactArchiver>
498+ <artifacts>clientlogs/*, clientlogs/**/*</artifacts>
499+ <latestOnly>false</latestOnly>
500+ </hudson.tasks.ArtifactArchiver>
501+ <hudson.plugins.descriptionsetter.DescriptionSetterPublisher>
502+ <regexp>^= TOUCH IMAGE VERSION:([0-9]+.*)</regexp>
503+ <regexpForFailed>^= TOUCH IMAGE VERSION:([0-9]+.*)</regexpForFailed>
504+ <setForMatrix>false</setForMatrix>
505+ </hudson.plugins.descriptionsetter.DescriptionSetterPublisher>
506+ <hudson.tasks.Mailer>
507+ <recipients>paul.larson@canonical.com para.siva@canonical.com</recipients>
508+ <dontNotifyEveryUnstableBuild>false</dontNotifyEveryUnstableBuild>
509+ <sendToIndividuals>false</sendToIndividuals>
510+ </hudson.tasks.Mailer>
511+{% if publish %}
512+ <hudson.plugins.build__publisher.BuildPublisher>
513+ <publishUnstableBuilds>true</publishUnstableBuilds>
514+ <publishFailedBuilds>true</publishFailedBuilds>
515+ <postActions class="vector"/>
516+ </hudson.plugins.build__publisher.BuildPublisher>
517+{% endif %}
518+ </publishers>
519+ <buildWrappers>
520+ <hudson.plugins.build__timeout.BuildTimeoutWrapper>
521+ <timeoutMinutes>60</timeoutMinutes>
522+ <failBuild>true</failBuild>
523+ <writingDescription>false</writingDescription>
524+ <timeoutPercentage>0</timeoutPercentage>
525+ <timeoutType>absolute</timeoutType>
526+ <timeoutMinutesElasticDefault>3</timeoutMinutesElasticDefault>
527+ </hudson.plugins.build__timeout.BuildTimeoutWrapper>
528+ </buildWrappers>
529+</project>
530
531=== added file 'jenkins/templates/touch-master.xml.jinja2'
532--- jenkins/templates/touch-master.xml.jinja2 1970-01-01 00:00:00 +0000
533+++ jenkins/templates/touch-master.xml.jinja2 2014-02-13 16:08:19 +0000
534@@ -0,0 +1,121 @@
535+<?xml version='1.0' encoding='UTF-8'?>
536+<project>
537+ <actions/>
538+ <description>
539+&lt;pre&gt;
540+#NOTE: Automatically created from a script as part of daily smoke testing&#xd;
541+ {{branch}}&#xd;
542+&lt;/pre&gt;
543+ </description>
544+ <keepDependencies>false</keepDependencies>
545+ <properties>
546+ <hudson.plugins.throttleconcurrents.ThrottleJobProperty>
547+ <maxConcurrentPerNode>0</maxConcurrentPerNode>
548+ <maxConcurrentTotal>0</maxConcurrentTotal>
549+ <throttleEnabled>false</throttleEnabled>
550+ <throttleOption>project</throttleOption>
551+ </hudson.plugins.throttleconcurrents.ThrottleJobProperty>
552+ <hudson.plugins.build__publisher.ExternalProjectProperty/>
553+ </properties>
554+ <scm class="hudson.scm.NullSCM"/>
555+ <assignedNode>master</assignedNode>
556+ <canRoam>false</canRoam>
557+ <disabled>false</disabled>
558+ <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
559+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
560+{%if trigger_url %}
561+ <triggers class="vector">
562+ <com.redfin.hudson.UrlChangeTrigger>
563+ <spec></spec>
564+ <url>{{trigger_url}}</url>
565+ </com.redfin.hudson.UrlChangeTrigger>
566+ </triggers>
567+{% endif %}
568+ <concurrentBuild>false</concurrentBuild>
569+ <builders>
570+ <hudson.tasks.Shell>
571+ <command><![CDATA[
572+set -e
573+
574+# give the image time to show up before running tests
575+sleep {{wait}}
576+ ]]></command>
577+ </hudson.tasks.Shell>
578+ <!-- our smoke job. this must run successfully or we won't continue -->
579+ <hudson.plugins.parameterizedtrigger.TriggerBuilder>
580+ <configs>
581+ <hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig>
582+ <configs class="java.util.Collections$EmptyList"/>
583+ <projects>{{smoke_job}}</projects>
584+ <condition>ALWAYS</condition>
585+ <triggerWithNoParameters>false</triggerWithNoParameters>
586+ <block>
587+ <buildStepFailureThreshold>
588+ <name>FAILURE</name>
589+ <ordinal>2</ordinal>
590+ <color>RED</color>
591+ </buildStepFailureThreshold>
592+ <unstableThreshold>
593+ <name>UNSTABLE</name>
594+ <ordinal>1</ordinal>
595+ <color>YELLOW</color>
596+ </unstableThreshold>
597+ <failureThreshold>
598+ <name>FAILURE</name>
599+ <ordinal>2</ordinal>
600+ <color>RED</color>
601+ </failureThreshold>
602+ </block>
603+ <buildAllNodesWithLabel>false</buildAllNodesWithLabel>
604+ </hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig>
605+ </configs>
606+ </hudson.plugins.parameterizedtrigger.TriggerBuilder>
607+
608+ <hudson.tasks.Shell>
609+ <command><![CDATA[
610+set -e
611+
612+# Grab the INSTALL_URL from the smoke-job to help avoid reflashing the
613+# downstream jobs
614+child_number=$(env | grep ^TRIGGERED_BUILD_NUMBER_ | cut -d= -f2)
615+cat >child.params <<EOF
616+INSTALL_URL=${JENKINS_URL}job/{{smoke_job}}/${child_number}
617+EOF
618+
619+echo params for downstream jobs:
620+cat child.params
621+ ]]></command>
622+ </hudson.tasks.Shell>
623+
624+{% for project in projects %}
625+ <hudson.plugins.parameterizedtrigger.TriggerBuilder>
626+ <configs>
627+ <hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig>
628+ <configs>
629+ <hudson.plugins.parameterizedtrigger.FileBuildParameters>
630+ <propertiesFile>child.params</propertiesFile>
631+ <failTriggerOnMissing>true</failTriggerOnMissing>
632+ </hudson.plugins.parameterizedtrigger.FileBuildParameters>
633+ </configs>
634+ <projects>{{project}}</projects>
635+ <condition>ALWAYS</condition>
636+ <triggerWithNoParameters>false</triggerWithNoParameters>
637+ <buildAllNodesWithLabel>false</buildAllNodesWithLabel>
638+ </hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig>
639+ </configs>
640+ </hudson.plugins.parameterizedtrigger.TriggerBuilder>
641+{% endfor %}
642+ </builders>
643+ <publishers/>
644+ <buildWrappers>
645+ <hudson.plugins.build__timeout.BuildTimeoutWrapper>
646+ <timeoutMinutes>300</timeoutMinutes>
647+ <failBuild>true</failBuild>
648+ <writingDescription>false</writingDescription>
649+ <timeoutPercentage>0</timeoutPercentage>
650+ <timeoutType>absolute</timeoutType>
651+ <timeoutMinutesElasticDefault>3</timeoutMinutesElasticDefault>
652+ </hudson.plugins.build__timeout.BuildTimeoutWrapper>
653+ </buildWrappers>
654+</project>
655+
656
657=== added file 'jenkins/templates/touch-smoke.xml.jinja2'
658--- jenkins/templates/touch-smoke.xml.jinja2 1970-01-01 00:00:00 +0000
659+++ jenkins/templates/touch-smoke.xml.jinja2 2014-02-13 16:08:19 +0000
660@@ -0,0 +1,163 @@
661+<?xml version='1.0' encoding='UTF-8'?>
662+<project>
663+ <actions/>
664+ <description>
665+<![CDATA[
666+This job provides a flexible way to execute all the tests in our daily image
667+testing process that contribute to the
668+<a href="http://reports.qa.ubuntu.com/smokeng/">QA Dashboard</a>.
669+The job is parameterized to give flexibility in what gets tested. A couple of
670+common ways to run this job are:
671+<dl>
672+ <dt>Full Test Run</dt>
673+ <dd>TESTS=ALL</dd>
674+ <dd>APPS=ALL</dd>
675+ <dd>PACKAGES=ALL</dd>
676+ <dt>Re-run a Failed Autopilot Test</dt>
677+ <dd>INSTALL_URL=http://dev-jenkins:8080/job/saucy-touch_ro-mako/9</dd>
678+ <dd>APPS=friends_app</dd>
679+ <dt>Re-run a Failed UTAH Test</dt>
680+ <dd>INSTALL_URL=http://dev-jenkins:8080/job/saucy-touch_ro-mako/9</dd>
681+ <dd>TESTS=security</dd>
682+</dl>
683+<pre>
684+#NOTE: Automatically created from a script as part of daily smoke testing
685+ {{branch}}
686+</pre>
687+{% if statsd_key %}
688+<h3>Graphite Reports</h3>
689+<ul>
690+ <li><a href="https://graphite.engineering.canonical.com/graphlot/?width=841&height=770&&target=alias%28scale%28statsd.{{statsd_key}}.{{imagetype}}.provision.mean%2C0.000016667%29%2C%22Provisioning%20Time(minutes)%22%29&target=alias%28scale%28statsd.{{statsd_key}}.{{imagetype}}.test_autopilot.mean%2C0.000016667%29%2C%22Autopilot%20Tests%20Time%22%29&target=alias%28scale%28statsd.{{statsd_key}}.{{imagetype}}.test_utah.mean%2C0.000016667%29%2C%22UTAH%20Tests%20Time%22%29&from=-1weeks">Timings (1 week)</a></li>
691+ <li><a href="https://graphite.engineering.canonical.com/graphlot/?width=841&height=770&&target=alias%28scale%28statsd.{{statsd_key}}.{{imagetype}}.provision.mean%2C0.000016667%29%2C%22Provisioning%20Time(minutes)%22%29&target=alias%28scale%28statsd.{{statsd_key}}.{{imagetype}}.test_autopilot.mean%2C0.000016667%29%2C%22Autopilot%20Tests%20Time%22%29&target=alias%28scale%28statsd.{{statsd_key}}.{{imagetype}}.test_utah.mean%2C0.000016667%29%2C%22UTAH%20Tests%20Time%22%29&from=-4weeks">Timings (4 weeks)</a></li>
692+</ul>
693+{% endif %}
694+]]>
695+ </description>
696+ <keepDependencies>false</keepDependencies>
697+ <properties>
698+ <hudson.model.ParametersDefinitionProperty>
699+ <parameterDefinitions>
700+ <hudson.model.StringParameterDefinition>
701+ <name>INSTALL_URL</name>
702+ <description>A URL to the previous job. If provided this job will use the same install options as it used. If the device executing the job happens to have the exact same image, then provisioning can be skipped.
703+ </description>
704+ <defaultValue></defaultValue>
705+ </hudson.model.StringParameterDefinition>
706+ <hudson.model.StringParameterDefinition>
707+ <name>TESTS</name>
708+ <description>A space separated list of utah tests to run. "ALL" can be used to run all known utah tests.
709+ </description>
710+ <defaultValue>ALL</defaultValue>
711+ </hudson.model.StringParameterDefinition>
712+ <hudson.model.StringParameterDefinition>
713+ <name>APPS</name>
714+ <description>A space separated list of autopilot tests to run. "ALL" can be used to run all known tests.
715+ </description>
716+ <defaultValue>ALL</defaultValue>
717+ </hudson.model.StringParameterDefinition>
718+ <hudson.model.StringParameterDefinition>
719+ <name>PACKAGES</name>
720+ <description>A space separated list of packages required by autopilot tests. "ALL" can be used to install all known packages required.
721+ </description>
722+ <defaultValue>ALL</defaultValue>
723+ </hudson.model.StringParameterDefinition>
724+ </parameterDefinitions>
725+ </hudson.model.ParametersDefinitionProperty>
726+ <com.sonyericsson.rebuild.RebuildSettings>
727+ <autoRebuild>false</autoRebuild>
728+ </com.sonyericsson.rebuild.RebuildSettings>
729+ </properties>
730+ <scm class="hudson.scm.NullSCM"/>
731+ <assignedNode>{{ name }}</assignedNode>
732+ <canRoam>false</canRoam>
733+ <disabled>false</disabled>
734+ <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
735+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
736+ <triggers class="vector"/>
737+ <concurrentBuild>true</concurrentBuild>
738+ <builders>
739+ <hudson.tasks.Shell>
740+ <command>set -e
741+BRANCH=&quot;{{branch}}&quot;
742+BZRDIR=`echo &quot;$BRANCH&quot; | awk -F/ &apos;{ print $(NF) }&apos;`
743+BZRDIR=$(readlink -f $BZRDIR)
744+[ -d $BZRDIR ] &amp;&amp; rm -rf $BZRDIR
745+bzr branch ${BRANCH} ${BZRDIR}
746+
747+export ANDROID_SERIAL={{serial}}
748+export IMAGE_TYPE={{imagetype}}
749+
750+{% if statsd_key %}
751+# the txstatsd package is too old, use the one from LP:
752+[ -d txstatsd ] &amp;&amp; rm -rf txstatsd
753+bzr branch -r 109 lp:txstatsd
754+export PYTHONPATH=`pwd`/txstatsd
755+
756+export STATSD_KEY={{statsd_key}}.{{imagetype}}
757+{% endif %}
758+
759+{% if dashboard_host %}
760+export DASHBOARD_HOST="{{dashboard_host}}"
761+{% endif %}
762+{% if dashboard_user %}
763+export DASHBOARD_USER="{{dashboard_user}}"
764+{% endif %}
765+{% if dashboard_key %}
766+set +x # don't let this leak into the public
767+export DASHBOARD_KEY="{{dashboard_key}}"
768+set -x
769+{% endif %}
770+{% if dashboard_prefix %}
771+export DASHBOARD_PREFIX="{{dashboard_prefix}}"
772+{% endif %}
773+{% if dashboard_port %}
774+export DASHBOARD_PORT="{{dashboard_port}}"
775+{% endif %}
776+
777+{{image_opt}}
778+${BZRDIR}/scripts/run-smoke
779+ </command>
780+ </hudson.tasks.Shell>
781+ </builders>
782+ <publishers>
783+ <hudson.tasks.ArtifactArchiver>
784+ <artifacts>clientlogs/**</artifacts>
785+ <latestOnly>false</latestOnly>
786+ </hudson.tasks.ArtifactArchiver>
787+ <hudson.tasks.junit.JUnitResultArchiver>
788+ <testResults>clientlogs/**/*.xml</testResults>
789+ <keepLongStdio>true</keepLongStdio>
790+ <testDataPublishers>
791+ <hudson.plugins.junitattachments.AttachmentPublisher/>
792+ </testDataPublishers>
793+ </hudson.tasks.junit.JUnitResultArchiver>
794+ <hudson.plugins.descriptionsetter.DescriptionSetterPublisher>
795+ <regexp>^= TOUCH IMAGE VERSION:([0-9]+.*)</regexp>
796+ <regexpForFailed>^= TOUCH IMAGE VERSION:([0-9]+.*)</regexpForFailed>
797+ <setForMatrix>false</setForMatrix>
798+ </hudson.plugins.descriptionsetter.DescriptionSetterPublisher>
799+{% if publish %}
800+ <hudson.tasks.Mailer>
801+ <recipients>paul.larson@canonical.com</recipients>
802+ <dontNotifyEveryUnstableBuild>false</dontNotifyEveryUnstableBuild>
803+ <sendToIndividuals>false</sendToIndividuals>
804+ </hudson.tasks.Mailer>
805+ <hudson.plugins.build__publisher.BuildPublisher>
806+ <publishUnstableBuilds>true</publishUnstableBuilds>
807+ <publishFailedBuilds>true</publishFailedBuilds>
808+ <postActions class="vector"/>
809+ </hudson.plugins.build__publisher.BuildPublisher>
810+{% endif %}
811+ </publishers>
812+ <buildWrappers>
813+ <hudson.plugins.build__timeout.BuildTimeoutWrapper>
814+ <timeoutMinutes>300</timeoutMinutes>
815+ <failBuild>true</failBuild>
816+ <writingDescription>false</writingDescription>
817+ <timeoutPercentage>0</timeoutPercentage>
818+ <timeoutType>absolute</timeoutType>
819+ <timeoutMinutesElasticDefault>3</timeoutMinutesElasticDefault>
820+ </hudson.plugins.build__timeout.BuildTimeoutWrapper>
821+ </buildWrappers>
822+</project>
823+
824
825=== added file 'jenkins/testconfig.py'
826--- jenkins/testconfig.py 1970-01-01 00:00:00 +0000
827+++ jenkins/testconfig.py 2014-02-13 16:08:19 +0000
828@@ -0,0 +1,176 @@
829+#!/usr/bin/env python
830+
831+# Ubuntu Testing Automation Harness
832+# Copyright 2013 Canonical Ltd.
833+
834+# This program is free software: you can redistribute it and/or modify it
835+# under the terms of the GNU General Public License version 3, as published
836+# by the Free Software Foundation.
837+
838+# This program is distributed in the hope that it will be useful, but
839+# WITHOUT ANY WARRANTY; without even the implied warranties of
840+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
841+# PURPOSE. See the GNU General Public License for more details.
842+
843+# You should have received a copy of the GNU General Public License along
844+# with this program. If not, see <http://www.gnu.org/licenses/>.
845+
846+import argparse
847+
848+DEF_FMT = '{prefix}{series}-{imagetype}-{type}-smoke-{testname}'
849+IDLE_FMT = '{prefix}{testname}-{series}-{imagetype}-armhf-install-idle-{type}'
850+
851+
852+class Test(object):
853+ def __init__(self, name, fmt=DEF_FMT):
854+ self.name = name
855+ self.fmt = fmt
856+ self.smoke = fmt == DEF_FMT
857+
858+
859+class APTest(Test):
860+ def __init__(self, name, app=None, pkgs=None):
861+ Test.__init__(self, name)
862+ self.pkgs = pkgs
863+ if not app:
864+ # convert share-app-autopilot to share_app
865+ app = name.replace('-', '_').replace('_autopilot', '')
866+ self.app = app
867+
868+
869+TESTSUITES = [
870+ Test('default'),
871+]
872+TESTSUITES += [
873+ APTest('friends-app-autopilot', pkgs=['friends-app-autopilot']),
874+ APTest('mediaplayer-app-autopilot', pkgs=['mediaplayer-app-autopilot']),
875+ APTest('gallery-app-autopilot', pkgs=['gallery-app-autopilot']),
876+ APTest('webbrowser-app-autopilot', pkgs=['webbrowser-app-autopilot']),
877+ APTest('unity8-autopilot', 'unity8', pkgs=['python-gi']),
878+ APTest('notes-app-autopilot'),
879+ APTest('camera-app-autopilot', pkgs=['camera-app-autopilot']),
880+ APTest('dialer-app-autopilot', pkgs=['dialer-app-autopilot']),
881+ APTest('messaging-app-autopilot', pkgs=['messaging-app-autopilot']),
882+ APTest('address-book-app-autopilot', pkgs=['address-book-app-autopilot']),
883+ APTest('calendar-app-autopilot'),
884+ APTest('music-app-autopilot'),
885+ APTest('dropping-letters-app-autopilot'),
886+ APTest('ubuntu-calculator-app-autopilot'),
887+ APTest('ubuntu-clock-app-autopilot'),
888+ APTest('ubuntu-filemanager-app-autopilot'),
889+ APTest('ubuntu-rssreader-app-autopilot'),
890+ APTest('ubuntu-terminal-app-autopilot'),
891+ APTest('ubuntu-weather-app-autopilot'),
892+ APTest('ubuntu-ui-toolkit-autopilot', 'ubuntuuitoolkit',
893+ ['ubuntu-ui-toolkit-autopilot']),
894+ APTest('ubuntu-system-settings-autopilot',
895+ pkgs=['ubuntu-system-settings-autopilot']),
896+ APTest('ubuntu-system-settings-online-accounts-autopilot',
897+ 'online_accounts_ui',
898+ ['ubuntu-system-settings-online-accounts-autopilot']),
899+]
900+TESTSUITES += [
901+ Test('click_image_tests'),
902+ Test('sdk'),
903+ Test('security'),
904+ Test('eventstat', IDLE_FMT),
905+ Test('smem', IDLE_FMT),
906+ Test('memevent',
907+ '{prefix}{testname}-{series}-{imagetype}-armhf-default-{type}'),
908+]
909+
910+
911+def filter_tests(tests, image_type):
912+ if image_type:
913+ func = globals().get('get_tests_%s' % image_type)
914+ if func:
915+ tests = func(tests)
916+ elif image_type not in ['touch', 'touch_mir']:
917+ print('Unsupported image type: %s' % image_type)
918+ exit(1)
919+ return tests
920+
921+
922+def _get_tests(test_type, image_type):
923+ # take all our known tests, then call filter tests which should give
924+ # us a list of tests customized for things like sf4p or touch_custom.
925+ # then eliminate tests that aren't of the proper test_type
926+ tests = [t for t in filter_tests(TESTSUITES, image_type)
927+ if type(t) == test_type and t.fmt == DEF_FMT]
928+ return tests
929+
930+
931+def _handle_utah(args):
932+ tests = _get_tests(Test, args.image_type)
933+ if args.with_autopilot:
934+ tests = [t for t in TESTSUITES if t.fmt == DEF_FMT]
935+ print(' '.join([t.name for t in tests]))
936+
937+
938+def _handle_ap_apps(args):
939+ tests = _get_tests(APTest, args.image_type)
940+ print(' '.join([t.app for t in tests]))
941+
942+
943+def _handle_ap_packages(args):
944+ pkgs = []
945+ tests = _get_tests(APTest, args.image_type)
946+ for test in tests:
947+ if not args.app or test.app in args.app:
948+ if test.pkgs:
949+ pkgs.extend(test.pkgs)
950+ print(' '.join(pkgs))
951+
952+
953+def get_tests_touch_sf4p(common_tests):
954+ tests = []
955+ test_set = [
956+ 'install-and-boot',
957+ 'default',
958+ 'unity8-autopilot',
959+ ]
960+
961+ tests = [t for t in common_tests if t.name in test_set]
962+ return tests
963+
964+
965+def get_tests_touch_custom(common_tests):
966+ tests = common_tests
967+ tests.insert(1, Test('customizations'))
968+ return tests
969+
970+
971+def _get_parser():
972+ parser = argparse.ArgumentParser(
973+ description='List information on configured tests for touch')
974+ sub = parser.add_subparsers(title='Commands', metavar='')
975+
976+ p = sub.add_parser('utah', help='List UTAH tests')
977+ p.set_defaults(func=_handle_utah)
978+ p.add_argument('-i', '--image-type',
979+ help='Return list of test configured for an image type.')
980+ p.add_argument('-a', '--with-autopilot', action='store_true',
981+ help='Include autopilot tests that can be run under UTAH.')
982+
983+ p = sub.add_parser('apps', help='List autopilot application names')
984+ p.set_defaults(func=_handle_ap_apps)
985+ p.add_argument('-i', '--image-type',
986+ help='Return list of test configured for an image type.')
987+
988+ p = sub.add_parser('packages', help='List packages required for autopilot')
989+ p.set_defaults(func=_handle_ap_packages)
990+ g = p.add_mutually_exclusive_group()
991+ g.add_argument('-i', '--image-type',
992+ help='''If no apps are listed, limit to tests for an
993+ image type.''')
994+ g.add_argument('-a', '--app', action='append',
995+ help='Autopilot test application. eg share_app')
996+ return parser
997+
998+
999+def main():
1000+ args = _get_parser().parse_args()
1001+ return args.func(args)
1002+
1003+if __name__ == '__main__':
1004+ exit(main())
1005
1006=== added directory 'scripts'
1007=== renamed directory 'scripts' => 'scripts.moved'
1008=== added file 'scripts/assert-image'
1009--- scripts/assert-image 1970-01-01 00:00:00 +0000
1010+++ scripts/assert-image 2014-02-13 16:08:19 +0000
1011@@ -0,0 +1,28 @@
1012+#!/bin/sh
1013+
1014+set -e
1015+
1016+BASEDIR=$(readlink -f $(dirname $0)/..)
1017+
1018+# a simple script to ensure the proper image is installed on the target
1019+
1020+[ -z $INSTALL_URL ] && echo "= INSTALL_URL not set. Using target as-is." && exit 0
1021+
1022+echo "Ensuring target has proper image..."
1023+REQUIRED_UUID=$(curl ${INSTALL_URL}/artifact/clientlogs/.ci-uuid)
1024+ACTUAL_UUID=$(adb shell "cat /home/phablet/.ci-uuid | tr -d '\r\n'")
1025+if [ "$REQUIRED_UUID" != "$ACTUAL_UUID" ] ; then
1026+ echo "= UUIDs $REQUIRED_UUID != $ACTUAL_UUID"
1027+ ARGS=$(curl ${INSTALL_URL}/artifact/clientlogs/.ci-flash-args | tr -d '\r\n')
1028+ CUST=$(curl ${INSTALL_URL}/artifact/clientlogs/.ci-customizations | tr -d '\r\n')
1029+ echo "reprovisioning device with: $ARGS"
1030+ echo "customizing device with: $CUST"
1031+ UUID=$REQUIRED_UUID IMAGE_OPT=$ARGS CUSTOMIZE=$CUST \
1032+ ${BASEDIR}/scripts/provision.sh
1033+else
1034+ echo "= UUIDS match, reusing image on target"
1035+ echo $REQUIRED_UUID > clientlogs/.ci-uuid
1036+ curl ${INSTALL_URL}/artifact/clientlogs/.ci-flash-args > clientlogs/.ci-flash-args
1037+ curl ${INSTALL_URL}/artifact/clientlogs/.ci-customizations > clientlogs/.ci-customizations
1038+fi
1039+
1040
1041=== added file 'scripts/combine_results'
1042--- scripts/combine_results 1970-01-01 00:00:00 +0000
1043+++ scripts/combine_results 2014-02-13 16:08:19 +0000
1044@@ -0,0 +1,94 @@
1045+#!/usr/bin/env python3
1046+
1047+"""
1048+We always run the system-settle test before and after an autopilot test. This
1049+script takes the results of the before/after results and combines them in with
1050+the junit xml results from the autopilot test so we have one unified report.
1051+"""
1052+
1053+
1054+import os
1055+import sys
1056+
1057+from xml.etree import ElementTree
1058+
1059+
1060+def _build_node(classname, name, rcfile, stdout):
1061+ e = ElementTree.Element('testcase')
1062+ e.attrib['classname'] = classname
1063+ e.attrib['name'] = 'settle_%s' % name
1064+ rc = int(open(rcfile).read())
1065+ if rc != 0:
1066+ f = ElementTree.Element('failure')
1067+ e.append(f)
1068+ f.attrib['type'] = 'testtools.testresult.real._StringException'
1069+ f.text = open(stdout).read()
1070+ return e, rc != 0
1071+
1072+
1073+def _get_results(apfile):
1074+ try:
1075+ tree = ElementTree.parse(apfile)
1076+ except Exception as ex:
1077+ e = ElementTree.Element('testsuite')
1078+ tree = ElementTree.ElementTree(e)
1079+ e.attrib['errors'] = '1'
1080+ e.attrib['failures'] = '0'
1081+ e.attrib['tests'] = '1'
1082+
1083+ # make a guess at the classname:
1084+ classname = os.path.basename(os.path.dirname(apfile))
1085+
1086+ t = ElementTree.Element('testcase')
1087+ e.append(t)
1088+ t.attrib['classname'] = classname
1089+ t.attrib['name'] = 'phablet-test-run'
1090+
1091+ f = ElementTree.Element('failure')
1092+ t.append(f)
1093+ f.attrib['type'] = 'testtools.testresult.real._StringException'
1094+ f.text = str(ex)
1095+
1096+ return tree
1097+
1098+
1099+def _get_classname(results):
1100+ return results[0].attrib.get('classname', '???').split('.')[0]
1101+
1102+
1103+def combine(resdir):
1104+ ap_file = os.path.join(resdir, 'test_results.xml')
1105+ tree = _get_results(ap_file)
1106+ ap_results = tree.getroot()
1107+
1108+ errors = int(ap_results.attrib['errors'])
1109+
1110+ classname = _get_classname(ap_results)
1111+
1112+ rc = os.path.join(resdir, 'settle_before.rc')
1113+ log = os.path.join(resdir, 'settle_before.log')
1114+ node, failed = _build_node(classname, 'before', rc, log)
1115+ ap_results.insert(0, node)
1116+ if failed:
1117+ errors += 1
1118+
1119+ rc = os.path.join(resdir, 'settle_after.rc')
1120+ log = os.path.join(resdir, 'settle_after.log')
1121+ node, failed = _build_node(classname, 'after', rc, log)
1122+ ap_results.append(node)
1123+ if failed:
1124+ errors += 1
1125+
1126+ num = int(ap_results.attrib['tests']) + 2
1127+ ap_results.attrib['tests'] = str(num)
1128+ ap_results.attrib['errors'] = str(errors)
1129+
1130+ tree.write(ap_file)
1131+
1132+
1133+if __name__ == '__main__':
1134+ if len(sys.argv) != 2:
1135+ print('usage: {} <results directory>'.format(sys.argv[0]))
1136+ sys.exit(1)
1137+
1138+ combine(sys.argv[1])
1139
1140=== added file 'scripts/dashboard.py'
1141--- scripts/dashboard.py 1970-01-01 00:00:00 +0000
1142+++ scripts/dashboard.py 2014-02-13 16:08:19 +0000
1143@@ -0,0 +1,295 @@
1144+#!/usr/bin/python
1145+
1146+import argparse
1147+import datetime
1148+import json
1149+import logging
1150+import os
1151+
1152+import yaml
1153+
1154+from httplib import ACCEPTED, HTTPConnection, HTTPException, OK, CREATED
1155+from urllib import urlencode
1156+from urlparse import urlparse
1157+
1158+log = logging.getLogger()
1159+
1160+
1161+class API(object):
1162+ def __init__(self, host=None, port=None, user=None, key=None, prefix=None):
1163+ if not host:
1164+ host = os.environ.get('DASHBOARD_HOST', None)
1165+ if not port:
1166+ port = int(os.environ.get('DASHBOARD_PORT', '80'))
1167+ if not user:
1168+ user = os.environ.get('DASHBOARD_USER', None)
1169+ if not key:
1170+ key = os.environ.get('DASHBOARD_KEY', None)
1171+ if not prefix:
1172+ prefix = os.environ.get('DASHBOARD_PREFIX', None)
1173+
1174+ self.host = host
1175+ self.port = port
1176+ self.resource_base = prefix
1177+
1178+ self._headers = None
1179+ if user and key:
1180+ self._headers = {
1181+ 'Content-Type': 'application/json',
1182+ 'Authorization': 'ApiKey %s:%s' % (user, key)
1183+ }
1184+ # mod_wsgi will strip the Authorization header, but tastypie
1185+ # allows it as GET param also. More details for fixing apache:
1186+ # http://django-tastypie.rtfd.org/en/latest/authentication.html
1187+ self._auth_param = '?username=%s&api_key=%s' % (user, key)
1188+
1189+ def _connect(self):
1190+ if self.host:
1191+ return HTTPConnection(self.host, self.port)
1192+ return None
1193+
1194+ def _http_get(self, resource):
1195+ con = self._connect()
1196+ if not con:
1197+ # we just mock this for the case where the caller wants to
1198+ # use our API transparently enabled/disabled
1199+ return {}
1200+
1201+ if self.resource_base:
1202+ resource = self.resource_base + resource
1203+
1204+ logging.debug('doing get on: %s', resource)
1205+ headers = {'Content-Type': 'application/json'}
1206+ con.request('GET', resource, headers=headers)
1207+ resp = con.getresponse()
1208+ if resp.status != OK:
1209+ msg = ''
1210+ try:
1211+ msg = resp.read().decode()
1212+ except:
1213+ pass
1214+ fmt = '%d error getting resource(%s): %s'
1215+ raise HTTPException(fmt % (resp.status, resource, msg))
1216+ data = json.loads(resp.read().decode())
1217+ if len(data['objects']) == 0:
1218+ raise HTTPException('resource not found: %s' % resource)
1219+ assert len(data['objects']) == 1
1220+ return data['objects'][0]['resource_uri']
1221+
1222+ def _http_post(self, resource, params):
1223+ con = self._connect()
1224+ if not con or not self._headers:
1225+ return None
1226+
1227+ if self.resource_base:
1228+ resource = self.resource_base + resource
1229+ resource += self._auth_param
1230+
1231+ params = json.dumps(params)
1232+ log.debug('posting (%s): %s', resource, params)
1233+ con.request('POST', resource, params, self._headers)
1234+ resp = con.getresponse()
1235+ if resp.status != CREATED:
1236+ msg = ''
1237+ try:
1238+ msg = str(resp.getheaders())
1239+ msg += resp.read().decode()
1240+ except:
1241+ pass
1242+ raise HTTPException(
1243+ '%d creating resource(%s): %s' % (resp.status, resource, msg))
1244+ uri = resp.getheader('Location')
1245+ return urlparse(uri).path
1246+
1247+ def _http_patch(self, resource, params):
1248+ con = self._connect()
1249+ if not con or not self._headers:
1250+ return None
1251+
1252+ resource += self._auth_param
1253+
1254+ con.request('PATCH', resource, json.dumps(params), self._headers)
1255+ resp = con.getresponse()
1256+ if resp.status != ACCEPTED:
1257+ msg = ''
1258+ try:
1259+ msg = resp.getheaders()
1260+ except:
1261+ pass
1262+ raise HTTPException(
1263+ '%d patching resource(%s): %s' % (resp.status, resource, msg))
1264+ return resource
1265+
1266+ @staticmethod
1267+ def _uri_to_pk(resource_uri):
1268+ if resource_uri:
1269+ return resource_uri.split('/')[-2]
1270+ return None # we are mocked
1271+
1272+ def job_get(self, name):
1273+ resource = '/smokeng/api/v1/job/?' + urlencode({'name': name})
1274+ return self._http_get(resource)
1275+
1276+ def job_add(self, name):
1277+ resource = '/smokeng/api/v1/job/'
1278+ params = {
1279+ 'name': name,
1280+ 'url': 'http://jenkins.qa.ubuntu.com/job/' + name + '/'
1281+ }
1282+ return self._http_post(resource, params)
1283+
1284+ def build_add(self, job_name, job_number):
1285+ try:
1286+ logging.debug('trying to find job: %s', job_name)
1287+ job = self.job_get(job_name)
1288+ except HTTPException:
1289+ job = self.job_add(job_name)
1290+ logging.info('job is: %s', job)
1291+
1292+ resource = '/smokeng/api/v1/build/'
1293+ params = {
1294+ 'build_number': job_number,
1295+ 'job': job,
1296+ 'ran_at': datetime.datetime.now().isoformat(),
1297+ 'build_description': 'inprogress',
1298+ }
1299+ return self._http_post(resource, params)
1300+
1301+ def _image_get(self, build_number, release, variant, arch, flavor):
1302+ resource = '/smokeng/api/v1/image/?'
1303+ resource += urlencode({
1304+ 'build_number': build_number,
1305+ 'release': release,
1306+ 'flavor': flavor,
1307+ 'variant': variant,
1308+ 'arch': arch,
1309+ })
1310+ return self._http_get(resource)
1311+
1312+ def image_add(self, build_number, release, variant, arch, flavor):
1313+ try:
1314+ img = self._image_get(build_number, release, variant, arch, flavor)
1315+ return img
1316+ except HTTPException:
1317+ # image doesn't exist so go continue and create
1318+ pass
1319+
1320+ resource = '/smokeng/api/v1/image/'
1321+ params = {
1322+ 'build_number': build_number,
1323+ 'release': release,
1324+ 'flavor': flavor,
1325+ 'variant': variant,
1326+ 'arch': arch,
1327+ }
1328+ return self._http_post(resource, params)
1329+
1330+ def result_get(self, image, test):
1331+ # deal with getting resource uri's as parameters instead of id's
1332+ image = API._uri_to_pk(image)
1333+
1334+ resource = '/smokeng/api/v1/result/?'
1335+ resource += urlencode({
1336+ 'image': image,
1337+ 'name': test,
1338+ })
1339+ return self._http_get(resource)
1340+
1341+ def _result_status(self, image, build, test, status, results=None):
1342+ create = False
1343+ params = {
1344+ 'ran_at': datetime.datetime.now().isoformat(),
1345+ 'status': status,
1346+ 'jenkins_build': build,
1347+ }
1348+ if results:
1349+ params['results'] = results
1350+
1351+ try:
1352+ resource = self.result_get(image, test)
1353+ except HTTPException:
1354+ create = True
1355+ resource = '/smokeng/api/v1/result/'
1356+ params['image'] = image
1357+ params['name'] = test
1358+
1359+ if create:
1360+ return self._http_post(resource, params)
1361+ else:
1362+ return self._http_patch(resource, params)
1363+
1364+ def result_queue(self, image, build, test):
1365+ return self._result_status(image, build, test, 0)
1366+
1367+ def result_running(self, image, build, test):
1368+ return self._result_status(image, build, test, 1)
1369+
1370+ def result_syncing(self, image, build, test, results):
1371+ return self._result_status(image, build, test, 2, results)
1372+
1373+
1374+def _result_running(api, args):
1375+ return api.result_running(args.image, args.build, args.test)
1376+
1377+
1378+def _result_syncing(api, args):
1379+ results = {}
1380+ with open(args.results) as f:
1381+ results = yaml.safe_load(f.read())
1382+ return api.result_syncing(args.image, args.build, args.test, results)
1383+
1384+
1385+def _set_args(parser, names, func):
1386+ for n in names:
1387+ parser.add_argument(n, required=True)
1388+ parser.set_defaults(func=func)
1389+
1390+
1391+def _get_parser():
1392+ parser = argparse.ArgumentParser(
1393+ description='Interact with the CI dashboard API')
1394+
1395+ sub = parser.add_subparsers(title='Commands', metavar='')
1396+
1397+ args = ['--image', '--build', '--test']
1398+ p = sub.add_parser('result-running', help='Set a SmokeResult "Running".')
1399+ _set_args(p, args, _result_running)
1400+
1401+ p = sub.add_parser('result-syncing', help='Set a SmokeResult "Syncing".')
1402+ _set_args(p, args, _result_syncing)
1403+ p.add_argument('--results', required=True, help='UTAH yaml file')
1404+
1405+ return parser
1406+
1407+
1408+def _assert_env():
1409+ required = [
1410+ 'DASHBOARD_HOST', 'DASHBOARD_PORT', 'DASHBOARD_USER', 'DASHBOARD_KEY']
1411+ missing = []
1412+ for r in required:
1413+ if r not in os.environ:
1414+ missing.append(r)
1415+ if len(missing):
1416+ print('Missing the following environment variables:')
1417+ for x in missing:
1418+ print(' %s' % x)
1419+ exit(1)
1420+
1421+
1422+def _main(args):
1423+ _assert_env()
1424+
1425+ api = API()
1426+ try:
1427+ val = args.func(api, args)
1428+ if val:
1429+ print(val)
1430+ except HTTPException as e:
1431+ print('ERROR: %s' % e)
1432+ exit(1)
1433+
1434+ exit(0)
1435+
1436+if __name__ == '__main__':
1437+ args = _get_parser().parse_args()
1438+ exit(_main(args))
1439
1440=== added file 'scripts/device'
1441--- scripts/device 1970-01-01 00:00:00 +0000
1442+++ scripts/device 2014-02-13 16:08:19 +0000
1443@@ -0,0 +1,191 @@
1444+#!/usr/bin/python
1445+
1446+import argparse
1447+import contextlib
1448+import fcntl
1449+import imp
1450+import logging
1451+import os
1452+import sys
1453+import time
1454+
1455+
1456+class DeviceException(Exception):
1457+ """Just a handy way to pass errors to the CLI."""
1458+ pass
1459+
1460+
1461+@contextlib.contextmanager
1462+def _device_pool(args):
1463+ lockfile = os.path.join(args.device_pool, 'lockfile')
1464+ with open(lockfile, 'w') as f:
1465+ logging.debug('aquiring lock for device pool')
1466+ try:
1467+ fcntl.lockf(f, fcntl.LOCK_EX)
1468+ yield args.device_pool
1469+ finally:
1470+ fcntl.lockf(f, fcntl.LOCK_UN)
1471+
1472+
1473+def _get_tokenfile():
1474+ return '.device.%d' % os.getppid()
1475+
1476+
1477+def _lock_device(device_file):
1478+ logging.info('locking file %s', device_file)
1479+
1480+ name = os.path.basename(device_file)
1481+ lockfile = device_file + '.lck'
1482+
1483+ with open(device_file) as f:
1484+ contents = f.read()
1485+ with open(_get_tokenfile(), 'w') as w:
1486+ w.write(contents)
1487+ if contents[-1] != '\n':
1488+ w.write('\n')
1489+ w.write('DEVICE_NAME=%s\n' % name)
1490+ w.write('LOCK_FILE=%s\n' % lockfile)
1491+ os.rename(device_file, lockfile)
1492+
1493+
1494+def _get_by_name(args):
1495+ loops = 0
1496+ while True:
1497+ with _device_pool(args) as pool:
1498+ f = os.path.join(pool, args.name)
1499+ if os.path.exists(f):
1500+ _lock_device(f)
1501+ break
1502+ elif os.path.exists(f + '.lck'):
1503+ # throttle back logging as time progresses
1504+ if (loops < 10) or \
1505+ (loops < 100 and loops % 4 == 0) or \
1506+ (loops % 12 == 0):
1507+ logging.info(
1508+ 'device is locked, waiting... attempts(%d)', loops)
1509+ else:
1510+ raise DeviceException('No such device exists.')
1511+ time.sleep(5)
1512+ loops += 1
1513+
1514+
1515+def _get_by_type(args):
1516+ while True:
1517+ with _device_pool(args) as pool:
1518+ match = False
1519+ for d in os.listdir(pool):
1520+ if args.type in d:
1521+ match = True
1522+ if not d.endswith('.lck'):
1523+ _lock_device(os.path.join(pool, d))
1524+ return
1525+ if not match:
1526+ raise DeviceException(
1527+ 'No "%s" devices found in pool.' % args.type)
1528+ time.sleep(5)
1529+
1530+
1531+def _get_by_env(args, jobname):
1532+ # find the device portion of a string like:
1533+ # saucy-touch_ro-DEVICETYPE-smoke-ubuntu-terminal-app-autopilot
1534+ parts = jobname.split('-')
1535+ if len(parts) < 3:
1536+ raise DeviceException('Invalid jobname: %s.' % jobname)
1537+ args.type = parts[2]
1538+ _get_by_type(args)
1539+
1540+
1541+def _device_get(args):
1542+ job = os.environ.get('JOBNAME', False)
1543+
1544+ if args.name:
1545+ _get_by_name(args)
1546+ elif args.type:
1547+ _get_by_type(args)
1548+ elif job:
1549+ _get_by_env(args, job)
1550+ else:
1551+ raise DeviceException('no device, type or $JOBNAME specified.')
1552+
1553+
1554+def _device_free(args):
1555+ if not os.path.exists(args.tokenfile):
1556+ raise DeviceException('tokenfile(%s) does not exist.' % args.tokenfile)
1557+
1558+ lockfile = None
1559+ with open(args.tokenfile) as f:
1560+ for line in f.readlines():
1561+ if line.startswith('LOCK_FILE='):
1562+ lockfile = line.split('=', 2)[1].strip()
1563+ break
1564+
1565+ device_file = os.path.splitext(lockfile)[0]
1566+ with _device_pool(args):
1567+ logging.info('releasing lock for %s', device_file)
1568+ os.rename(lockfile, device_file)
1569+ os.unlink(args.tokenfile)
1570+
1571+
1572+def _populate(args):
1573+ if os.path.exists(args.device_pool):
1574+ raise DeviceException('pool(%s) already exists.' % args.device_pool)
1575+ os.makedirs(args.device_pool)
1576+
1577+ # jump through some hoops to import this module since it has dashes
1578+ # and doesn't have a .py extension
1579+ module = 'get-adb-id'
1580+ fname = os.path.join(os.path.dirname(__file__), module)
1581+ adb = imp.load_source(module, fname)
1582+
1583+ # import the config
1584+ config_module = imp.load_source('config', args.config)
1585+ for config in config_module.MATRIX.values():
1586+ for item in config:
1587+ for device in item['devices']:
1588+ name = device['name']
1589+ serial = device.get('serial', None)
1590+ if not serial:
1591+ serial = adb.DEVICES[name]
1592+ with open(os.path.join(args.device_pool, name), 'w') as f:
1593+ f.write('ANDROID_SERIAL=%s' % serial)
1594+
1595+
1596+def _get_args():
1597+ parser = argparse.ArgumentParser(
1598+ description='''Provides a mechanism to aquire a lock on a device in
1599+ a given resource pool. The lock info will be written in
1600+ the format .device.<calling pid>. This file can be sourced
1601+ by a shell script to get ANDROID_SERIAL etc.''')
1602+ parser.add_argument('--device-pool', default='/var/lib/jenkins/ci-devices',
1603+ help='location of the device pool. %(default)s')
1604+
1605+ sub = parser.add_subparsers(title='Commands', metavar='')
1606+ p = sub.add_parser('get', help='Aquire a lock on an idle device.')
1607+ grp = p.add_mutually_exclusive_group()
1608+ grp.add_argument('--name', help='Get device by name')
1609+ grp.add_argument('--type', help='Get device by type')
1610+ p.set_defaults(func=_device_get)
1611+
1612+ p = sub.add_parser('free', help='Release a lock on a device.')
1613+ p.add_argument('--tokenfile', default=_get_tokenfile(),
1614+ help='token file for device reservation. %(default)s')
1615+ p.set_defaults(func=_device_free)
1616+
1617+ p = sub.add_parser('populate', help='Initialize a device pool for usage')
1618+ p.add_argument('-c', '--config', required=True,
1619+ help='The jenkins config to find devices in.')
1620+ p.set_defaults(func=_populate)
1621+
1622+ return parser.parse_args()
1623+
1624+
1625+if __name__ == '__main__':
1626+ logging.basicConfig(level=logging.INFO)
1627+ args = _get_args()
1628+ rc = 0
1629+ try:
1630+ args.func(args)
1631+ except DeviceException as e:
1632+ rc = 1
1633+ print('ERROR: %s' % e)
1634+ sys.exit(rc)
1635
1636=== added file 'scripts/get-adb-id'
1637--- scripts/get-adb-id 1970-01-01 00:00:00 +0000
1638+++ scripts/get-adb-id 2014-02-13 16:08:19 +0000
1639@@ -0,0 +1,39 @@
1640+#!/usr/bin/python
1641+
1642+import sys
1643+
1644+DEVICES = {
1645+ "maguro-01": "0149BD7E0A019003",
1646+ "maguro-02": "0149BD7E0200E00F",
1647+ "maguro-03": "0149C7A50A00C011",
1648+ "maguro-04": "0149BDCC0500701B",
1649+ "maguro-05": "0149BD3418014013",
1650+ "maguro-06": "014E058C0F00C013",
1651+ "maguro-07": "01498FE717002014",
1652+ "mako-01": "01aa3d7a5dcba4a2",
1653+ "mako-02": "01ade38b552014d4",
1654+ "mako-03": "04c6714ed7c863f2",
1655+ "mako-05": "01b22f82dc5cec63",
1656+ "mako-06": "04ed70928fdc13ba",
1657+ "mako-07": "01e2f64788556934",
1658+ "mako-08": "04ea16a163930769",
1659+ "mako-10": "01ce848e48dfa6a2",
1660+ "mako-11": "04ed727c929709ba",
1661+ "grouper-02": "015d1884951c020e",
1662+ "grouper-03": "015d18ad5c27f80d",
1663+ "grouper-04": "015d188445242611",
1664+ "grouper-05": "015d21d9151c0019",
1665+ "manta-01": "R32D102RPZL",
1666+ "manta-02": "R32D102RPPK",
1667+ "manta-04": "R32D203DDZR",
1668+ "manta-05": "R32D203DMBY",
1669+}
1670+
1671+if __name__ == '__main__':
1672+ name = sys.argv[1]
1673+
1674+ try:
1675+ print(DEVICES[name])
1676+ except KeyError:
1677+ print("Unknown device name: '%s'" % name)
1678+ sys.exit(-1)
1679
1680=== added file 'scripts/jenkins.sh'
1681--- scripts/jenkins.sh 1970-01-01 00:00:00 +0000
1682+++ scripts/jenkins.sh 2014-02-13 16:08:19 +0000
1683@@ -0,0 +1,186 @@
1684+#!/bin/bash
1685+
1686+## This is the script jenkins should run to execute various touch applications
1687+
1688+set -e
1689+
1690+BASEDIR=$(dirname $(readlink -f $0))/..
1691+
1692+RESDIR="${RESDIR-`pwd`/clientlogs}"
1693+UTAHFILE=${RESDIR}/utah.yaml
1694+UTAH_PHABLET_CMD="${UTAH_PHABLET_CMD-/usr/share/utah/examples/run_utah_phablet.py}"
1695+
1696+
1697+usage() {
1698+ cat <<EOF
1699+usage: $0 -a APP [-s ANDROID_SERIAL] [-p FILE -p FILE ...] [-Q]
1700+
1701+Provisions the given device with the latest build
1702+
1703+OPTIONS:
1704+ -h Show this message
1705+ -s Specify the serial of the device to install
1706+ -a The application under the "tests" directory to test
1707+ -p Extra file to pull from target (absolute path or relative to /home/phablet)
1708+ -Q "Quick" don't do a reboot of the device before running the test
1709+
1710+EOF
1711+}
1712+
1713+PIDS=""
1714+
1715+cleanup() {
1716+ set +e
1717+ echo "killing child pids: $PIDS"
1718+ for p in $PIDS ; do
1719+ kill $p
1720+ done
1721+}
1722+
1723+test_from_host() {
1724+ export PATH=${BASEDIR}/utils/host:${PATH}
1725+
1726+ # allow for certain commands to run from host/target
1727+ # see unity8-autopilot/ts_control for example
1728+ export TARGET_PREFIX=adb-shell
1729+
1730+ [ -z $ANDROID_SERIAL ] || ADBOPTS="-s $ANDROID_SERIAL"
1731+
1732+ sudo TARGET_PREFIX=$TARGET_PREFIX PATH="${PATH}" ${UTAH_PHABLET_CMD} \
1733+ ${ADBOPTS} \
1734+ --from-host \
1735+ --whoopsie \
1736+ --results-dir ${RESDIR} \
1737+ --skip-install --skip-network --skip-utah \
1738+ --pull /var/crash \
1739+ --pull /home/phablet/.cache/upstart \
1740+ --pull /tmp/xmlresults \
1741+ $EXTRA_PULL \
1742+ -l ${TESTSUITE_HOST}/master.run
1743+
1744+ # make sure the user running this script can remove its artifacts.
1745+ sudo chown -R `whoami` ${RESDIR}
1746+}
1747+
1748+assert_image() {
1749+ [ -z $INSTALL_URL ] && return
1750+ echo "Ensuring target has proper image..."
1751+ REQUIRED_UUID=$(curl ${INSTALL_URL}/artifact/clientlogs/.ci-uuid)
1752+ ACTUAL_UUID=$(adb shell "cat /home/phablet/.ci-uuid | tr -d '\r\n'")
1753+ if [ "$REQUIRED_UUID" != "$ACTUAL_UUID" ] ; then
1754+ echo "UUIDs $REQUIRED_UUID != $ACTUAL_UUID, reprovisioning device..."
1755+ ARGS=$(curl ${INSTALL_URL}/artifact/clientlogs/.ci-utah-args | tr -d '\r\n')
1756+ UUID=$REQUIRED_UUID IMAGE_OPT=$ARGS ${BASEDIR}/scripts/provision.sh
1757+ else
1758+ echo "UUIDS match"
1759+ fi
1760+}
1761+
1762+main() {
1763+ rm -rf $RESDIR
1764+ mkdir $RESDIR
1765+
1766+ assert_image
1767+
1768+ # print the build date so the jenkins job can use it as the
1769+ # build description
1770+ adb pull /var/log/installer/media-info ${RESDIR}
1771+ BUILDID=$(adb shell cat /home/phablet/.ci-version)
1772+ echo "= TOUCH IMAGE VERSION:$BUILDID"
1773+
1774+ adb shell "top -n1 -b" > ${RESDIR}/top.log
1775+
1776+ set -x
1777+ adb shell 'rm -f /var/crash/*'
1778+ if [ -z $QUICK ] ; then
1779+ # get the phone in sane place
1780+ adb reboot
1781+ # sometimes reboot doesn't happen fast enough, so add a little
1782+ # delay to help ensure its actually rebooted and we didn't just
1783+ # connect back to the device before it rebooted
1784+ adb wait-for-device
1785+ sleep 5
1786+ adb wait-for-device
1787+ phablet-network --skip-setup -t 90s
1788+ adb shell powerd-cli active &
1789+ PIDS="$PIDS $!"
1790+ adb shell powerd-cli display on &
1791+ PIDS="$PIDS $!"
1792+ else
1793+ echo "SKIPPING phone reboot..."
1794+ fi
1795+
1796+ ${BASEDIR}/utils/host/adb-shell "aa-clickhook -f --include=/usr/share/autopilot-touch/apparmor/click.rules"
1797+
1798+ echo "launching test from the host...."
1799+ test_from_host
1800+ adb shell 'rm -f /var/crash/*'
1801+
1802+ if ! `grep "^errors: [!0]" < $UTAHFILE >/dev/null` ; then
1803+ echo "errors found"
1804+ EXITCODE=1
1805+ fi
1806+ if ! `grep "^failures: [!0]" < $UTAHFILE >/dev/null` ; then
1807+ echo "failures found"
1808+ EXITCODE=2
1809+ fi
1810+ echo "Results Summary"
1811+ echo "---------------"
1812+ egrep '^(errors|failures|passes|fetch_errors):' $UTAHFILE
1813+ exit $EXITCODE
1814+}
1815+
1816+while getopts p:s:a:Qh opt; do
1817+ case $opt in
1818+ h)
1819+ usage
1820+ exit 0
1821+ ;;
1822+ s)
1823+ export ANDROID_SERIAL=$OPTARG
1824+ ;;
1825+ a)
1826+ APP=$OPTARG
1827+ ;;
1828+ p)
1829+ EXTRA_PULL_FILE=$OPTARG
1830+
1831+ if [ ! -z $EXTRA_PULL_FILE ]; then
1832+ # relative paths are assumed to be relative to /home/phablet
1833+ E_P_START=`echo $EXTRA_PULL_FILE | cut -c1`
1834+
1835+ if [ $E_P_START = '/' ]; then
1836+ EXTRA_PULL="$EXTRA_PULL --pull $EXTRA_PULL_FILE"
1837+ else
1838+ EXTRA_PULL="$EXTRA_PULL --pull /home/phablet/$EXTRA_PULL_FILE"
1839+ fi
1840+ fi
1841+ ;;
1842+ Q)
1843+ QUICK=1
1844+ ;;
1845+ esac
1846+done
1847+
1848+if [ -z $ANDROID_SERIAL ] ; then
1849+ # ensure we only have one device attached
1850+ lines=$(adb devices | wc -l)
1851+ if [ $lines -gt 3 ] ; then
1852+ echo "ERROR: More than one device attached, please use -s option"
1853+ echo
1854+ usage
1855+ exit 1
1856+ fi
1857+fi
1858+if [ -z $APP ] ; then
1859+ echo "ERROR: No app specified"
1860+ usage
1861+ exit 1
1862+fi
1863+
1864+TESTSUITE_HOST=$(readlink -f ${BASEDIR}/tests/${APP})
1865+TESTSUITE_TARGET_BASE=/tmp/tests
1866+TESTSUITE_TARGET=${TESTSUITE_TARGET_BASE}/$(basename ${TESTSUITE_HOST})
1867+
1868+trap cleanup TERM INT EXIT
1869+main
1870
1871=== added file 'scripts/junit2utah.py'
1872--- scripts/junit2utah.py 1970-01-01 00:00:00 +0000
1873+++ scripts/junit2utah.py 2014-02-13 16:08:19 +0000
1874@@ -0,0 +1,70 @@
1875+#!/usr/bin/python
1876+
1877+import datetime
1878+import sys
1879+
1880+from xml.etree import ElementTree
1881+
1882+import yaml
1883+
1884+
1885+def _convert_testcase(tc):
1886+ x = {
1887+ 'testcase': tc.attrib['name'],
1888+ 'testsuite': tc.attrib['classname'],
1889+ 'command': 'autopilot',
1890+ 'cmd_type': 'testcase_test',
1891+ 'stdout': '',
1892+ 'stderr': '',
1893+ 'returncode': 0
1894+ }
1895+ t = tc.attrib.get('time', False)
1896+ if t:
1897+ x['time_delta'] = t
1898+
1899+ for e in tc.getchildren():
1900+ if e.tag in ('failure', 'error'):
1901+ x['stderr'] = e.text
1902+ x['returncode'] = 1
1903+ elif e.tag == 'skip':
1904+ # NOTE: this isn't a real thing in UTAH. However, the
1905+ # qa-dashboard code doesn't care and will display it as skipped
1906+ x['cmd_type'] = 'testcase_skipped'
1907+ x['stdout'] = e.text
1908+ else:
1909+ raise RuntimeError('Unknown element type: %s' % e.tag)
1910+ return x
1911+
1912+
1913+def _get_results(stream):
1914+ tree = ElementTree.fromstring(stream.read())
1915+ results = {
1916+ 'errors': int(tree.attrib.get('errors', '0')),
1917+ 'failures': int(tree.attrib.get('failures', '0')),
1918+ 'commands': [],
1919+ 'fetch_errors': 0,
1920+ 'uname': 'n/a',
1921+ 'media-info': 'n/a',
1922+ 'install_type': 'n/a',
1923+ 'arch': 'n/a',
1924+ 'release': 'n/a',
1925+ 'build_number': 'n/a',
1926+ 'name': 'unamed',
1927+ 'runlist': 'n/a',
1928+ 'ran_at': datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
1929+ }
1930+ results['passes'] = \
1931+ int(tree.attrib['tests']) - results['errors'] - results['failures']
1932+
1933+ for tc in tree.getchildren():
1934+ results['commands'].append(_convert_testcase(tc))
1935+ return results
1936+
1937+
1938+def _main(stream):
1939+ results = _get_results(stream)
1940+ print(yaml.safe_dump(results, default_flow_style=False))
1941+
1942+
1943+if __name__ == '__main__':
1944+ exit(_main(sys.stdin))
1945
1946=== added file 'scripts/provision.sh'
1947--- scripts/provision.sh 1970-01-01 00:00:00 +0000
1948+++ scripts/provision.sh 2014-02-13 16:08:19 +0000
1949@@ -0,0 +1,131 @@
1950+#!/bin/bash
1951+
1952+## This is the script jenkins should run to provision a device in the lab
1953+
1954+set -e
1955+
1956+BASEDIR=$(dirname $(readlink -f $0))
1957+
1958+RESDIR=`pwd`/clientlogs
1959+
1960+NETWORK_FILE="${NETWORK_FILE-/home/ubuntu/magners-wifi}"
1961+
1962+IMAGE_OPT="${IMAGE_OPT-ubuntu-system -b --channel trusty-proposed}"
1963+UUID="${UUID-$(uuidgen -r)}"
1964+
1965+usage() {
1966+cat <<EOF
1967+usage: $0 [-s ANDROID_SERIAL] [-n NETWORK_FILE] [-P ppa] [-p package] [-w]
1968+
1969+Provisions the given device with the latest build
1970+
1971+OPTIONS:
1972+ -h Show this message
1973+ -s Specify the serial of the device to install
1974+ -n Select network file
1975+ -P add the ppa to the target (can be repeated)
1976+ -p add the package to the target (can be repeated)
1977+ -w make the system writeable (implied with -p and -P arguments)
1978+
1979+EOF
1980+}
1981+
1982+image_info() {
1983+ # mark the version we installed in /home/phablet/.ci-[uuid,flash-args]
1984+ # adb shell messes up \n's with \r\n's so do the whole of the regex on the target
1985+ IMAGEVER=$(adb shell "system-image-cli -i | sed -n -e 's/version version: \([0-9]*\)/\1/p' -e 's/version ubuntu: \([0-9]*\)/\1/p' -e 's/version device: \([0-9]*\)/\1/p' | paste -s -d:")
1986+ CHAN=$(adb shell "system-image-cli -i | sed -n -e 's/channel: \(.*\)/\1/p' | paste -s -d:")
1987+ REV=$(echo $IMAGEVER | cut -d: -f1)
1988+ echo "$IMAGE_OPT" | grep -q "\-\-revision" || IMAGE_OPT="${IMAGE_OPT} --revision $REV"
1989+ echo "$IMAGE_OPT" | grep -q "\-\-channel" || IMAGE_OPT="${IMAGE_OPT} --channel $CHAN"
1990+ adb shell "echo '${IMAGEVER}' > /home/phablet/.ci-version"
1991+ echo $UUID > $RESDIR/.ci-uuid
1992+ adb push $RESDIR/.ci-uuid /home/phablet/
1993+ cat > $RESDIR/.ci-flash-args <<EOF
1994+$IMAGE_OPT
1995+EOF
1996+ adb push $RESDIR/.ci-flash-args /home/phablet/.ci-flash-args
1997+ echo $CUSTOMIZE > $RESDIR/.ci-customizations
1998+ adb push $RESDIR/.ci-customizations /home/phablet/.ci-customizations
1999+}
2000+
2001+log() {
2002+ echo = $(date): $*
2003+}
2004+
2005+while getopts i:s:n:P:p:wh opt; do
2006+ case $opt in
2007+ h)
2008+ usage
2009+ exit 0
2010+ ;;
2011+ n)
2012+ NETWORK_FILE=$OPTARG
2013+ ;;
2014+ s)
2015+ export ANDROID_SERIAL=$OPTARG
2016+ ;;
2017+ i)
2018+ IMAGE_TYPE=$OPTARG
2019+ ;;
2020+ w)
2021+ # making this a non-zero length string enables the logic
2022+ CUSTOMIZE=" "
2023+ ;;
2024+ P)
2025+ CUSTOMIZE="$CUSTOMIZE --ppa $OPTARG"
2026+ ;;
2027+ p)
2028+ CUSTOMIZE="$CUSTOMIZE -p $OPTARG"
2029+ ;;
2030+ esac
2031+done
2032+
2033+if [ -z $ANDROID_SERIAL ] ; then
2034+ # ensure we only have one device attached
2035+ lines=$(adb devices | wc -l)
2036+ if [ $lines -gt 3 ] ; then
2037+ echo "ERROR: More than one device attached, please use -s option"
2038+ echo
2039+ usage
2040+ exit 1
2041+ fi
2042+fi
2043+
2044+if [ ! -f $NETWORK_FILE ] ; then
2045+ echo "ERROR: NETWORK_FILE, $NETWORK_FILE, not found"
2046+ exit 1
2047+fi
2048+
2049+set -x
2050+[ -d $RESDIR ] && rm -rf $RESDIR
2051+mkdir -p $RESDIR
2052+
2053+log "FLASHING DEVICE"
2054+phablet-flash $IMAGE_OPT
2055+adb wait-for-device
2056+sleep 20 #give the system a little time
2057+
2058+log "SETTING UP CLICK PACKAGES"
2059+phablet-click-test-setup
2060+
2061+log "SETTING UP WIFI"
2062+phablet-network -n $NETWORK_FILE
2063+
2064+if [ "$IMAGE_TYPE" = "touch_sf4p" ]; then
2065+ adb shell rm -f /home/phablet/.display-mir
2066+fi
2067+
2068+phablet-config edges-intro --disable
2069+
2070+# get our target-based utilities into our PATH
2071+adb push ${BASEDIR}/../utils/target /home/phablet/bin
2072+
2073+image_info
2074+
2075+if [ -n "$CUSTOMIZE" ] ; then
2076+ log "CUSTOMIZING IMAGE"
2077+ phablet-config writable-image $CUSTOMIZE
2078+ # Make sure whoopsie-upload-all can work (bug #1245524)
2079+ adb shell "sed -i '/Waiting for whoopsie/ a\ subprocess.call([\"restart\", \"whoopsie\"])' /usr/share/apport/whoopsie-upload-all"
2080+fi
2081
2082=== added file 'scripts/reboot-and-wait'
2083--- scripts/reboot-and-wait 1970-01-01 00:00:00 +0000
2084+++ scripts/reboot-and-wait 2014-02-13 16:08:19 +0000
2085@@ -0,0 +1,41 @@
2086+#!/usr/bin/python
2087+
2088+import argparse
2089+import logging
2090+import time
2091+
2092+from phabletutils.device import AndroidBridge
2093+
2094+
2095+def _get_arg_parser():
2096+ parser = argparse.ArgumentParser(
2097+ description='Reboot device and waits for networking to become active.')
2098+ parser.add_argument('-s', '--serial', help='Device serial')
2099+ parser.add_argument('-n', '--num-tries', type=int, default=3,
2100+ help='''How many times to retry on failure.
2101+ default=%(default)d''')
2102+ return parser
2103+
2104+
2105+def main(args):
2106+ device = AndroidBridge(args.serial)
2107+ device.wait_for_device()
2108+ for i in range(args.num_tries):
2109+ device.reboot()
2110+ device.wait_for_device()
2111+ time.sleep(5)
2112+ device.wait_for_device()
2113+ try:
2114+ device.wait_for_network()
2115+ return 0
2116+ except:
2117+ pass # try the loop again
2118+ logging.error('device failed to start and activate networking')
2119+ return 1
2120+
2121+
2122+if __name__ == '__main__':
2123+ logging.basicConfig(level=logging.INFO)
2124+ logging.getLogger().name = 'reboot-and-wait'
2125+ args = _get_arg_parser().parse_args()
2126+ exit(main(args))
2127
2128=== added file 'scripts/run-autopilot-tests.sh'
2129--- scripts/run-autopilot-tests.sh 1970-01-01 00:00:00 +0000
2130+++ scripts/run-autopilot-tests.sh 2014-02-13 16:08:19 +0000
2131@@ -0,0 +1,215 @@
2132+#!/bin/sh
2133+
2134+set -e
2135+
2136+BASEDIR=$(dirname $(readlink -f $0))/..
2137+RESDIR=`pwd`/clientlogs
2138+
2139+export PATH=${BASEDIR}/utils/host:${PATH}
2140+export TARGET_PREFIX=adb-shell
2141+
2142+
2143+usage() {
2144+ cat <<EOF
2145+usage: $0 -a APP [-s ANDROID_SERIAL] [-Q] [-o results_dir] [-S]
2146+
2147+Runs a set of autopilot tests on the target
2148+
2149+OPTIONS:
2150+ -h Show this message
2151+ -s Specify the serial of the device to test
2152+ -a The application to test (can be repeated)
2153+ -o Specify the directory to place results in.
2154+ Default: $RESDIR
2155+ -Q "Quick" don't do a reboot of the device before/between testsuites.
2156+ -S Skip the system-settle tests that run before/after each testsuite.
2157+
2158+EOF
2159+}
2160+
2161+log_error() {
2162+ echo ERROR: $* >> ${RESDIR}/runner-errors.txt
2163+}
2164+
2165+system_settle() {
2166+ [ -z $NOSETTLE ] || return 0
2167+
2168+ label=$1
2169+ odir=$2
2170+ rc=0
2171+ timeout=120s
2172+ if [ "$label" = "before" ] ; then
2173+ timeout=300s
2174+ fi
2175+
2176+ settle=${BASEDIR}/tests/systemsettle/systemsettle.sh
2177+ {
2178+ export UTAH_PROBE_DIR=${odir} # needed for log file location
2179+ timeout $timeout $settle -c5 -d6 -p 97.5 -l $label || rc=1
2180+ echo $rc > ${odir}/settle_${label}.rc
2181+ } 2>&1 | tee ${odir}/settle_${label}.log
2182+
2183+ if [ "$label" = "after" ] ; then
2184+ ${BASEDIR}/scripts/combine_results ${odir}
2185+ fi
2186+}
2187+
2188+test_app() {
2189+ app=$1
2190+
2191+ odir=${RESDIR}/${app}
2192+ [ -d $odir ] && rm -rf $odir
2193+ mkdir -p $odir || return 1
2194+
2195+ system_settle before $odir
2196+ phablet-config autopilot --dbus-probe enable || \
2197+ (log_error "'autopilot dbus-probe enable' failed"; return 1)
2198+
2199+ NOSHELL=""
2200+ [ "$app" = "unity8" ] && NOSHELL="-n"
2201+
2202+ if adb-shell /home/phablet/bin/unlock_screen.sh ; then
2203+ phablet-test-run \
2204+ $NOSHELL \
2205+ -o ${odir} \
2206+ -a /var/crash -a /home/phablet/.cache/upstart \
2207+ -v $app || true
2208+ else
2209+ log_error "screen unlock failed, skipping $app"
2210+ fi
2211+ system_settle after $odir
2212+}
2213+
2214+reboot_wait() {
2215+ if [ -z $QUICK ] ; then
2216+ ${BASEDIR}/scripts/reboot-and-wait
2217+ FILES="/var/crash/* /home/phablet/.cache/upstart/*.log*"
2218+ if ! adb shell "rm -rf $FILES" ; then
2219+ log_error "unable to remove crash and log files, retrying"
2220+ adb wait-for-device
2221+ adb shell "rm -rf $FILES"
2222+ fi
2223+ else
2224+ echo "SKIPPING phone reboot..."
2225+ fi
2226+}
2227+
2228+grab_powerd() {
2229+ echo "grabbing powerd cli locks..."
2230+ adb shell powerd-cli active &
2231+ PIDS="$!"
2232+ adb shell powerd-cli display on &
2233+ PIDS="$PIDS $!"
2234+}
2235+
2236+release_powerd() {
2237+ if [ -n "$PIDS" ] ; then
2238+ echo "killing child pids: $PIDS"
2239+ for p in $PIDS ; do
2240+ kill $p || true
2241+ done
2242+ PIDS=""
2243+ fi
2244+}
2245+
2246+dashboard_update() {
2247+ # only try and update the dashboard if we are configured to
2248+ [ -z $DASHBOARD_KEY ] && return 0
2249+ [ -z $DASHBOARD_BUILD ] && return 0
2250+ [ -z $DASHBOARD_IMAGE ] && return 0
2251+ ${BASEDIR}/scripts/dashboard.py $* \
2252+ --image $DASHBOARD_IMAGE \
2253+ --build $DASHBOARD_BUILD || true
2254+}
2255+
2256+dashboard_result_running() {
2257+ dashboard_update result-running --test $1
2258+}
2259+
2260+dashboard_result_syncing() {
2261+ xunit=${RESDIR}/${app}/test_results.xml
2262+ [ -f $xunit ] || return 0
2263+
2264+ # save a utah.yaml version of the results so the dashboard can process
2265+ cat $xunit | ${BASEDIR}/scripts/junit2utah.py > ${RESDIR}/${app}/utah.yaml
2266+ dashboard_update result-syncing --test $1 --results ${RESDIR}/${app}/utah.yaml
2267+}
2268+
2269+main() {
2270+ # print the build date so the jenkins job can use it as the
2271+ # build description
2272+ BUILDID=$(adb shell cat /home/phablet/.ci-version)
2273+ echo "= TOUCH IMAGE VERSION:$BUILDID"
2274+
2275+ [ -d $RESDIR ] || mkdir -p $RESDIR
2276+
2277+ set -x
2278+
2279+ for app in $APPS ; do
2280+ set +x
2281+ echo "========================================================"
2282+ echo "= testing $app"
2283+ echo "========================================================"
2284+ set -x
2285+ dashboard_result_running $app
2286+ reboot_wait
2287+
2288+ grab_powerd
2289+
2290+ if ! test_app $app ; then
2291+ log_error "testing $app, retrying"
2292+ # we sometimes see sporatic adb failures that seem to
2293+ # related to MTP. This adds a retry for the test.
2294+ # test_app only fails on a device error (not a test
2295+ # case error)
2296+ adb wait-for-device
2297+ test_app $app
2298+ fi
2299+ dashboard_result_syncing $app
2300+
2301+ release_powerd
2302+ done
2303+}
2304+
2305+while getopts s:a:o:QSh opt; do
2306+ case $opt in
2307+ h)
2308+ usage
2309+ exit 0
2310+ ;;
2311+ s)
2312+ export ANDROID_SERIAL=$OPTARG
2313+ ;;
2314+ o)
2315+ RESDIR=$OPTARG
2316+ ;;
2317+ a)
2318+ APPS="$APPS $OPTARG"
2319+ ;;
2320+ Q)
2321+ QUICK=1
2322+ ;;
2323+ S)
2324+ NOSETTLE=1
2325+ ;;
2326+ esac
2327+done
2328+
2329+if [ -z $ANDROID_SERIAL ] ; then
2330+ # ensure we only have one device attached
2331+ lines=$(adb devices | wc -l)
2332+ if [ $lines -gt 3 ] ; then
2333+ echo "ERROR: More than one device attached, please use -s option"
2334+ echo
2335+ usage
2336+ exit 1
2337+ fi
2338+fi
2339+if [ -z "$APPS" ] ; then
2340+ echo "ERROR: No app specified"
2341+ usage
2342+ exit 1
2343+fi
2344+
2345+trap release_powerd TERM INT EXIT
2346+main
2347
2348=== added file 'scripts/run-smoke'
2349--- scripts/run-smoke 1970-01-01 00:00:00 +0000
2350+++ scripts/run-smoke 2014-02-13 16:08:19 +0000
2351@@ -0,0 +1,357 @@
2352+#!/usr/bin/python
2353+
2354+import argparse
2355+import datetime
2356+import logging
2357+import os
2358+import shutil
2359+import subprocess
2360+
2361+import yaml
2362+
2363+from phabletutils.environment import detect_device
2364+
2365+import dashboard
2366+import statsd
2367+
2368+log = logging.getLogger()
2369+script_dir = os.path.dirname(__file__)
2370+res_dir = os.path.join(os.getcwd(), 'clientlogs')
2371+
2372+dashboard_api = dashboard.API()
2373+
2374+
2375+class SerialAction(argparse.Action):
2376+ def __call__(self, parser, namespace, values, option_string=None):
2377+ log.info('android serial: %s', values[0])
2378+ os.environ['ANDROID_SERIAL'] = values[0]
2379+
2380+
2381+class DebugAction(argparse.Action):
2382+ def __call__(self, parser, namespace, values, option_string=None):
2383+ log.setLevel(level=logging.DEBUG)
2384+ log.debug('debug logging enabled')
2385+
2386+
2387+def _serial_required():
2388+ required = 'ANDROID_SERIAL' not in os.environ
2389+ if required:
2390+ try:
2391+ out = subprocess.check_output(['adb', 'devices'])
2392+ required = (len(out.decode().split('\n')) != 4)
2393+ except subprocess.CalledProcessError as e:
2394+ logging.debug('error getting adb devices: %s', e)
2395+ return required
2396+
2397+
2398+def _get_parser():
2399+ parser = argparse.ArgumentParser(
2400+ description='Run the complete test-execution-service suite.')
2401+
2402+ parser.add_argument('-s', '--serial', action=SerialAction, nargs=1,
2403+ required=_serial_required(),
2404+ help='Android serial if more than one device present')
2405+ parser.add_argument('--debug', action=DebugAction, nargs=0,
2406+ help='''Enable debug logging.''')
2407+
2408+ parser.add_argument('--install-url',
2409+ help='''Flash with image from previous jenkins job.
2410+ This option will check if the device already has image
2411+ noted from this URL and will skip provisioning. The URL
2412+ should be the path the job like:
2413+ http://q-jenkins:8080/job/<your job>/<build number>''')
2414+ parser.add_argument('-p', '--package', action='append',
2415+ help='Additional packages to install on target.')
2416+ parser.add_argument('-P', '--ppa', action='append',
2417+ help='Additional PPA to configure on target.')
2418+ parser.add_argument('-a', '--app', action='append',
2419+ help='Autopilot tests tor run.')
2420+ parser.add_argument('-t', '--test', action='append',
2421+ help='UTAH tests tor run.')
2422+ parser.add_argument('--hooks-dir',
2423+ help='''A directory containing scripts to be run after
2424+ the target has been provisioned and before testing.''')
2425+ parser.add_argument('--image-opt',
2426+ help='Options to pass to phablet-flash')
2427+ parser.add_argument('--image-type', default='touch',
2428+ help='''Image type being tested. This can be changed
2429+ to 'touch_sf4p' so that SurfaceFlinger will be used
2430+ instead of Mir. default=%(default)s''')
2431+ return parser
2432+
2433+
2434+def _arg_from_env(args, attr, envkey, array):
2435+ val = os.environ.get(envkey, False)
2436+ if val:
2437+ if array:
2438+ setattr(args, attr, val.split())
2439+ else:
2440+ setattr(args, attr, val)
2441+ del os.environ[envkey]
2442+
2443+
2444+def _merge_env(args):
2445+ '''When run in Jenkins everything comes as environment variables.
2446+
2447+ Its makes a much simpler job this way. While command line args are
2448+ much easier for a user.
2449+ '''
2450+ _arg_from_env(args, 'app', 'APPS', True)
2451+ _arg_from_env(args, 'test', 'TESTS', True)
2452+ _arg_from_env(args, 'package', 'PACKAGES', True)
2453+ _arg_from_env(args, 'ppa', 'PPAS', True)
2454+ _arg_from_env(args, 'image_opt', 'IMAGE_OPT', False)
2455+ _arg_from_env(args, 'image_type', 'IMAGE_TYPE', False)
2456+ _arg_from_env(args, 'install_url', 'INSTALL_URL', False)
2457+
2458+
2459+def _assert_args(args):
2460+ if args.install_url:
2461+ # this means you shouldn't specify packages, ppas, or image options
2462+ if args.package or args.ppa or args.image_opt:
2463+ msg = 'ERROR: --install-url can\'t be used with ' \
2464+ '--package, -ppa, or --image_opt'
2465+ print(msg)
2466+ return False
2467+
2468+ # don't bother the install_url check, a user might be copy/pasting and
2469+ # doesn't hurt. Its just good to not encourage it.
2470+ _merge_env(args)
2471+
2472+ script = os.path.join(script_dir, '../jenkins/testconfig.py')
2473+ if args.package and args.package[0] == 'ALL':
2474+ logging.info('Discovering all required dependencies')
2475+ out = subprocess.check_output(
2476+ [script, 'packages', '-i', args.image_type])
2477+ args.package = [x for x in out.decode().split()]
2478+
2479+ if args.app and args.app[0] == 'ALL':
2480+ logging.info('Discovering all autopilot tests')
2481+ out = subprocess.check_output(
2482+ [script, 'apps', '-i', args.image_type])
2483+ args.app = [x for x in out.decode().split()]
2484+
2485+ if args.test and args.test[0].startswith('ALL'):
2486+ logging.info('Discovering all UTAH tests')
2487+ argv = [script, 'utah', '-i', args.image_type]
2488+ if args.test[0] == 'ALL_INCLUDING_AUTOPILOT':
2489+ argv.append('-a')
2490+ out = subprocess.check_output(argv)
2491+ args.test = [x for x in out.decode().split()]
2492+
2493+ logging.debug('ARGS: %r', args)
2494+
2495+ statsd.gauge_it('PACKAGES', args.package)
2496+ statsd.gauge_it('APPS', args.app)
2497+ statsd.gauge_it('TESTS', args.test)
2498+
2499+ return True
2500+
2501+
2502+def _run(args, ignore_error=False):
2503+ try:
2504+ logging.info('Running: %s', ' '.join(args))
2505+ subprocess.check_call(args)
2506+ except subprocess.CalledProcessError:
2507+ if ignore_error:
2508+ logging.error('failed to run %r, continuing', args)
2509+ else:
2510+ exit(1)
2511+
2512+
2513+def _image_info():
2514+ info = subprocess.check_output(['adb', 'shell', 'system-image-cli', '-i'])
2515+ v_ver = u_ver = d_ver = channel = None
2516+ for line in info.split('\n'):
2517+ if not line.strip():
2518+ continue
2519+ key, val = line.split(':', 1)
2520+ if key == 'version version':
2521+ v_ver = val.strip()
2522+ elif key == 'version ubuntu':
2523+ u_ver = val.strip()
2524+ elif key == 'version device':
2525+ d_ver = val.strip()
2526+ elif key == 'channel':
2527+ channel = val.strip()
2528+ ver = '%s:%s:%s' % (v_ver, u_ver, d_ver)
2529+ return ver, channel
2530+
2531+
2532+def _assert_image(args):
2533+ log.info('checking if device has proper image ...')
2534+ os.environ['INSTALL_URL'] = args.install_url
2535+ _run([os.path.join(script_dir, 'assert-image')])
2536+
2537+
2538+def _write_utah(start, end, passed):
2539+ passes = failures = rc = 0
2540+ if passed:
2541+ passes = 1
2542+ else:
2543+ rc = failures = 1
2544+
2545+ delta = '%s' % (end - start)
2546+ start = start.strftime('%Y-%m-%d %H:%M:%S')
2547+ data = {
2548+ 'name': 'install-and-boot',
2549+ 'errors': 0,
2550+ 'failures': failures,
2551+ 'passes': passes,
2552+ 'fetch_errors': 0,
2553+ 'uname': 'n/a',
2554+ 'media-info': 'n/a',
2555+ 'install_type': 'n/a',
2556+ 'arch': 'n/a',
2557+ 'release': 'n/a',
2558+ 'build_number': 'n/a',
2559+ 'runlist': 'n/a',
2560+ 'ran_at': start,
2561+ 'commands': [{
2562+ 'cmd_type': 'testcase_test',
2563+ 'command': 'provision',
2564+ 'returncode': rc,
2565+ 'start_time': start,
2566+ 'time_delta': delta,
2567+ 'stderr': '',
2568+ 'stdout': '',
2569+ 'testcase': 'boot',
2570+ 'testsuite': 'install-and-boot',
2571+ }]
2572+ }
2573+ path = os.path.join(res_dir, 'install-and-boot')
2574+ if not os.path.exists(path):
2575+ os.mkdir(path)
2576+ with open(os.path.join(path, 'utah.yaml'), 'w') as f:
2577+ f.write(yaml.safe_dump(data, default_flow_style=False))
2578+
2579+
2580+def _post_install_hooks(args):
2581+ if not args.hooks_dir:
2582+ return
2583+ log.info('running post install hooks ...')
2584+ if not os.path.isdir(args.hooks_dir):
2585+ log.warn('hooks directory (%s) does not exist ... skipping',
2586+ args.hooks_dir)
2587+ for hook in sorted(os.listdir(args.hooks_dir)):
2588+ s = os.stat(os.path.join(args.hooks_dir, hook))
2589+ if s.st_mode & os.path.stat.S_IXUSR == 0:
2590+ log.warn('skipping hook (%s) - not executable', hook)
2591+ continue
2592+ log.info('executing hook: %s', hook)
2593+ hook = os.path.join(args.hooks_dir, hook)
2594+ subprocess.check_call([hook])
2595+
2596+
2597+def _provision(args):
2598+ log.info('provisioning device ...')
2599+ if args.image_opt:
2600+ log.debug('overriding IMAGE_OPT with: %s', args.image_opt)
2601+ os.environ['IMAGE_OPT'] = args.image_opt
2602+
2603+ cargs = [os.path.join(script_dir, 'provision.sh'), '-i', args.image_type]
2604+
2605+ if args.package:
2606+ for p in args.package:
2607+ cargs.extend(['-p', p])
2608+ if args.ppa:
2609+ for p in args.ppa:
2610+ cargs.extend(['-P', p])
2611+ if args.test and not args.ppa and not args.package:
2612+ # UTAH tests require a writeable system the -p and -P args
2613+ # implicitly create a writable system. so we have to ensure here:
2614+ cargs.append('-w')
2615+
2616+ with statsd.time_it('provision'):
2617+ start = datetime.datetime.utcnow()
2618+ passed = False
2619+ try:
2620+ _run(cargs)
2621+ _post_install_hooks(args)
2622+ passed = True
2623+ finally:
2624+ end = datetime.datetime.utcnow()
2625+ _write_utah(start, end, passed)
2626+
2627+
2628+def _test_autopilot(args, build, image):
2629+ if args.app:
2630+ if build:
2631+ os.environ['DASHBOARD_BUILD'] = build
2632+ if image:
2633+ os.environ['DASHBOARD_IMAGE'] = image
2634+ cargs = [os.path.join(script_dir, 'run-autopilot-tests.sh')]
2635+ for app in args.app:
2636+ cargs.extend(['-a', app])
2637+ with statsd.time_it('APPS'):
2638+ _run(cargs)
2639+
2640+
2641+def _sync_results(build, image, test, fname):
2642+ with open(fname) as f:
2643+ d = yaml.safe_load(f)
2644+ dashboard_api.result_syncing(image, build, test, d)
2645+
2646+
2647+def _test_utah(args, build, image):
2648+ if args.test:
2649+ cargs = [os.path.join(script_dir, 'jenkins.sh')]
2650+ with statsd.time_it('TESTS'):
2651+ for test in args.test:
2652+ os.environ['RESDIR'] = os.path.join(res_dir, test)
2653+ dashboard_api.result_running(image, build, test)
2654+ _run(cargs + ['-a', test], ignore_error=True)
2655+ fname = os.path.join(res_dir, test, 'utah.yaml')
2656+ _sync_results(build, image, test, fname)
2657+
2658+
2659+def _image_add(args):
2660+ build_number, channel = _image_info()
2661+ release = channel.split('-')[0]
2662+ return dashboard_api.image_add(
2663+ build_number, release, args.image_type, detect_device(None), 'ubuntu')
2664+
2665+
2666+def main(args):
2667+ with statsd.time_it('main'):
2668+ if os.path.exists(res_dir):
2669+ logging.info('deleting old result directory: %s', res_dir)
2670+ shutil.rmtree(res_dir)
2671+ os.mkdir(res_dir)
2672+
2673+ job_name = os.environ.get('JOB_NAME', '')
2674+ job_number = os.environ.get('BUILD_NUMBER', '')
2675+ build = dashboard_api.build_add(job_name, job_number)
2676+
2677+ if args.install_url:
2678+ _assert_image(args)
2679+ else:
2680+ _provision(args)
2681+
2682+ # TODO - this should be incororated into provision and assert_image
2683+ # so that the status is updated *before* flashing rather than after
2684+ image = _image_add(args)
2685+
2686+ if args.test:
2687+ for x in args.test:
2688+ dashboard_api.result_queue(image, build, x)
2689+ if args.app:
2690+ for x in args.app:
2691+ dashboard_api.result_queue(image, build, x)
2692+
2693+ _test_utah(args, build, image)
2694+ _test_autopilot(args, build, image)
2695+
2696+ return 0
2697+
2698+
2699+if __name__ == '__main__':
2700+ logging.basicConfig(level=logging.INFO)
2701+ log.name = 'run-smoke'
2702+ dashboard.log = logging.getLogger('dashboard')
2703+
2704+ args = _get_parser().parse_args()
2705+ if not _assert_args(args):
2706+ exit(1)
2707+
2708+ exit(main(args))
2709
2710=== added file 'scripts/run-touch-upgrade.sh'
2711--- scripts/run-touch-upgrade.sh 1970-01-01 00:00:00 +0000
2712+++ scripts/run-touch-upgrade.sh 2014-02-13 16:08:19 +0000
2713@@ -0,0 +1,46 @@
2714+#!/bin/bash
2715+
2716+## This is the script jenkins should run to test upgrading a system image
2717+## in the lab.
2718+## Intersting environment variables that must be set:
2719+## ANDROID_SERIAL - specify another android device
2720+## RUNLIST - the path the runlist
2721+## NETWORK_FILE - specify an alternative network file (passed to runlist)
2722+## UPGRADE_FROM - the revision to upgrade from, eg -1 (passed to runlist)
2723+
2724+set -eux
2725+
2726+BASEDIR=$(dirname $(readlink -f $0))
2727+
2728+RESDIR=`pwd`/clientlogs
2729+
2730+UTAH_PHABLET_CMD="${UTAH_PHABLET_CMD-/usr/share/utah/examples/run_utah_phablet.py}"
2731+RUNLIST=${RUNLIST-"`pwd`/smoke-touch-apps/upgrade/master.run"}
2732+ANDROID_SERIAL="${ANDROID_SERIAL-015d1884b20c1c0f}" #doanac's nexus7 at home
2733+NETWORK_FILE="${NETWORK_FILE-/home/ubuntu/magners-wifi}"
2734+
2735+rm -rf clientlogs
2736+mkdir clientlogs
2737+
2738+export ANDROID_SERIAL=$ANDROID_SERIAL
2739+
2740+set +e
2741+sudo NETWORK_FILE=$NETWORK_FILE \
2742+ $UTAH_PHABLET_CMD -s $ANDROID_SERIAL \
2743+ --from-host --skip-install --skip-utah --skip-network -l $RUNLIST \
2744+ --results-dir=$RESDIR
2745+EXITCODE=$?
2746+
2747+UTAHFILE=$RESDIR/utah.yaml
2748+if ! `grep "^errors: [!0]" < $UTAHFILE >/dev/null` ; then
2749+ echo "errors found"
2750+ EXITCODE=1
2751+fi
2752+if ! `grep "^failures: [!0]" < $UTAHFILE >/dev/null` ; then
2753+ echo "failures found"
2754+ EXITCODE=2
2755+fi
2756+echo "Results Summary"
2757+echo "---------------"
2758+egrep '^(errors|failures|passes|fetch_errors):' $UTAHFILE
2759+exit $EXITCODE
2760
2761=== added file 'scripts/statsd.py'
2762--- scripts/statsd.py 1970-01-01 00:00:00 +0000
2763+++ scripts/statsd.py 2014-02-13 16:08:19 +0000
2764@@ -0,0 +1,35 @@
2765+#!/usr/bin/python
2766+
2767+import os
2768+import contextlib
2769+import time
2770+
2771+
2772+_namespace = os.environ.get('STATSD_KEY')
2773+if _namespace:
2774+ from txstatsd.client import UdpStatsDClient
2775+ from txstatsd.metrics.timermetric import TimerMetric
2776+ from txstatsd.metrics.gaugemetric import GaugeMetric
2777+ _host = os.environ.get('SERVER', 'snakefruit.canonical.com')
2778+ _port = int(os.environ.get('PORT', '10041'))
2779+ _client = UdpStatsDClient(_host, _port)
2780+ _client.connect()
2781+
2782+
2783+@contextlib.contextmanager
2784+def time_it(key):
2785+ start = time.time()
2786+ try:
2787+ yield
2788+ finally:
2789+ if _namespace:
2790+ m = TimerMetric(_client, _namespace + '.' + key)
2791+ m.mark(time.time() - start)
2792+
2793+
2794+def gauge_it(key, array):
2795+ val = 0
2796+ if array:
2797+ val = len(array)
2798+ if _namespace:
2799+ GaugeMetric(_client, _namespace + '.' + key).mark(val)
2800
2801=== added directory 'selftests'
2802=== added file 'selftests/test_junit2utah.py'
2803--- selftests/test_junit2utah.py 1970-01-01 00:00:00 +0000
2804+++ selftests/test_junit2utah.py 2014-02-13 16:08:19 +0000
2805@@ -0,0 +1,49 @@
2806+import StringIO
2807+import os
2808+import sys
2809+import unittest
2810+
2811+here = os.path.dirname(__file__)
2812+sys.path.append(os.path.join(here, '../scripts'))
2813+
2814+import junit2utah
2815+
2816+RESULT = '''
2817+<testsuite errors="0" failures="1" name="" tests="4" time="0.001">
2818+<testcase classname="classname" name="testFails" time="0.000">
2819+<failure type="testtools.testresult.real._StringException">
2820+testtools.testresult.real._StringException: Traceback (most recent call last):
2821+ File "junit2utah.py", line 14, in testFails
2822+ self.assertFalse(True)
2823+ File "/usr/lib/python2.7/unittest/case.py", line 418, in assertFalse
2824+ raise self.failureException(msg)
2825+AssertionError: True is not false
2826+</failure>
2827+</testcase>
2828+<testcase classname="classname" name="testPasses" time="0.100"/>
2829+<testcase classname="classname" name="testPasses2" time="0.200"/>
2830+<testcase classname="classname" name="testSkip" time="0.000">
2831+<skip>ensure skip works</skip>
2832+</testcase>
2833+</testsuite>
2834+'''
2835+
2836+
2837+class TestJunit2Utah(unittest.TestCase):
2838+ def testFull(self):
2839+ stream = StringIO.StringIO(RESULT)
2840+ results = junit2utah._get_results(stream)
2841+ self.assertEquals(3, results['passes'])
2842+ self.assertEquals(1, results['failures'])
2843+ self.assertEquals(0, results['errors'])
2844+
2845+ tcs = results['commands']
2846+ self.assertEqual('classname', tcs[0]['testsuite'])
2847+ self.assertEqual('testFails', tcs[0]['testcase'])
2848+ self.assertIn('AssertionError', tcs[0]['stderr'])
2849+
2850+ self.assertEqual('0.200', tcs[2]['time_delta'])
2851+
2852+ self.assertEqual('classname', tcs[3]['testsuite'])
2853+ self.assertEqual('testSkip', tcs[3]['testcase'])
2854+ self.assertEqual('ensure skip works', tcs[3]['stdout'])
2855
2856=== added file 'selftests/test_reboot_and_wait.py'
2857--- selftests/test_reboot_and_wait.py 1970-01-01 00:00:00 +0000
2858+++ selftests/test_reboot_and_wait.py 2014-02-13 16:08:19 +0000
2859@@ -0,0 +1,66 @@
2860+# Ubuntu Test Cases for Touch
2861+# Copyright 2013 Canonical Ltd.
2862+
2863+# This program is free software: you can redistribute it and/or modify it
2864+# under the terms of the GNU General Public License version 3, as published
2865+# by the Free Software Foundation.
2866+
2867+# This program is distributed in the hope that it will be useful, but
2868+# WITHOUT ANY WARRANTY; without even the implied warranties of
2869+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2870+# PURPOSE. See the GNU General Public License for more details.
2871+
2872+# You should have received a copy of the GNU General Public License along
2873+# with this program. If not, see <http://www.gnu.org/licenses/>.
2874+
2875+from __future__ import print_function
2876+
2877+import imp
2878+import mock
2879+import os
2880+import unittest
2881+
2882+
2883+class TestRebootAndWait(unittest.TestCase):
2884+
2885+ """Simple set of tests to make sure the reboot-and-wait command works."""
2886+
2887+ def setUp(self):
2888+ # do some trickery to load this as module
2889+ module = 'reboot-and-wait'
2890+ fname = os.path.join(os.path.dirname(__file__), '../scripts', module)
2891+ m = imp.load_source(module, fname)
2892+ self._main = m.main
2893+ self._get_parser = m._get_arg_parser
2894+
2895+ @mock.patch('phabletutils.device.AndroidBridge.reboot')
2896+ def testRebootFail(self, reboot):
2897+ reboot.side_effect = RuntimeError('foo')
2898+ args = self._get_parser().parse_args([])
2899+ with self.assertRaisesRegexp(RuntimeError, 'foo'):
2900+ self._main(args)
2901+
2902+ @mock.patch('phabletutils.device.AndroidBridge.reboot')
2903+ @mock.patch('phabletutils.device.AndroidBridge.wait_for_device')
2904+ def testWaitForDeviceFail(self, wait_for, reboot):
2905+ wait_for.side_effect = RuntimeError('foo')
2906+ args = self._get_parser().parse_args([])
2907+ with self.assertRaisesRegexp(RuntimeError, 'foo'):
2908+ self._main(args)
2909+ reboot.assert_called_once_with()
2910+
2911+ @mock.patch('phabletutils.device.AndroidBridge.reboot')
2912+ @mock.patch('phabletutils.device.AndroidBridge.wait_for_device')
2913+ @mock.patch('phabletutils.device.AndroidBridge.wait_for_network')
2914+ def testRetries(self, wait_for_net, wait_for_dev, reboot):
2915+ args = self._get_parser().parse_args([])
2916+ wait_for_net.side_effect = RuntimeError('foo')
2917+ self.assertEquals(1, self._main(args))
2918+ self.assertEquals(args.num_tries, reboot.call_count)
2919+
2920+ # now make sure it can recover after a single network failure
2921+ reboot.reset_mock()
2922+ wait_for_net.reset_mock()
2923+ wait_for_net.side_effect = [RuntimeError('foo'), None]
2924+ self.assertEquals(0, self._main(args))
2925+ self.assertEquals(2, reboot.call_count)
2926
2927=== added file 'selftests/test_run_smoke.py'
2928--- selftests/test_run_smoke.py 1970-01-01 00:00:00 +0000
2929+++ selftests/test_run_smoke.py 2014-02-13 16:08:19 +0000
2930@@ -0,0 +1,143 @@
2931+# Ubuntu Test Cases for Touch
2932+# Copyright 2013 Canonical Ltd.
2933+
2934+# This program is free software: you can redistribute it and/or modify it
2935+# under the terms of the GNU General Public License version 3, as published
2936+# by the Free Software Foundation.
2937+
2938+# This program is distributed in the hope that it will be useful, but
2939+# WITHOUT ANY WARRANTY; without even the implied warranties of
2940+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2941+# PURPOSE. See the GNU General Public License for more details.
2942+
2943+# You should have received a copy of the GNU General Public License along
2944+# with this program. If not, see <http://www.gnu.org/licenses/>.
2945+
2946+import imp
2947+import mock
2948+import os
2949+import subprocess
2950+import sys
2951+import unittest
2952+
2953+
2954+class TestRunSmoke(unittest.TestCase):
2955+
2956+ """Simple set of tests to make sure the smoke-run command works."""
2957+
2958+ def setUp(self):
2959+ # load the module
2960+ module = 'run-smoke'
2961+ path = os.path.join(os.path.dirname(__file__), '../scripts')
2962+ sys.path.append(path)
2963+ fname = os.path.join(path, module)
2964+ self.run_smoke = imp.load_source(module, fname)
2965+ if 'ANDROID_SERIAL' in os.environ:
2966+ del os.environ['ANDROID_SERIAL']
2967+
2968+ @mock.patch.dict('os.environ')
2969+ @mock.patch('subprocess.check_output')
2970+ def testSerialRequired(self, check_output):
2971+ '''Ensure android serial is required when appropriate'''
2972+ check_output.return_value = '1'.encode()
2973+ self.assertTrue(self.run_smoke._serial_required())
2974+
2975+ check_output.return_value = '1\n2\n3\n4'.encode()
2976+ self.assertFalse(self.run_smoke._serial_required())
2977+
2978+ check_output.return_value = '1\n2\n3\n4\n5'.encode()
2979+ self.assertTrue(self.run_smoke._serial_required())
2980+
2981+ # make sure serial isn't required if specified in env
2982+ os.environ['ANDROID_SERIAL'] = 'foo'
2983+ check_output.return_value = '1\n2\n3\n4\n5'.encode()
2984+ self.assertFalse(self.run_smoke._serial_required())
2985+
2986+ @mock.patch('statsd.gauge_it')
2987+ def testAssertArgs(self, gauge):
2988+ '''Ensure install-url is used properly'''
2989+ patterns = [
2990+ (['--install-url', 'x', '-p', 'x'], False),
2991+ (['--install-url', 'x', '-P', 'x'], False),
2992+ (['--install-url', 'x', '--image-opt', 'x'], False),
2993+ (['-p', 'x', '-P', 'x', '--image-opt', 'x'], True),
2994+ ]
2995+ for pat, val in patterns:
2996+ args = self.run_smoke._get_parser().parse_args(['-s', 'foo'] + pat)
2997+ self.assertEqual(val, self.run_smoke._assert_args(args))
2998+
2999+ # ensure the -p ALL pulls in all packages
3000+ gauge.reset_mock()
3001+ args = self.run_smoke._get_parser().parse_args(['-p', 'ALL'])
3002+ self.assertTrue(self.run_smoke._assert_args(args))
3003+ self.assertTrue(len(args.package) > 1)
3004+ args = gauge.call_args_list[0][0]
3005+ self.assertEqual('PACKAGES', args[0])
3006+ self.assertLess(1, len(args[1]))
3007+
3008+ args = gauge.call_args_list[1][0]
3009+ self.assertEqual('APPS', args[0])
3010+ self.assertIsNone(args[1])
3011+ args = gauge.call_args_list[2][0]
3012+ self.assertEqual('TESTS', args[0])
3013+ self.assertIsNone(args[1])
3014+
3015+ # don't bother checking gauge calls for the remaining "ALL" tests
3016+
3017+ # ensure the -a ALL pulls in all APPS
3018+ args = self.run_smoke._get_parser().parse_args(['-a', 'ALL'])
3019+ self.assertTrue(self.run_smoke._assert_args(args))
3020+ self.assertTrue(len(args.app) > 1)
3021+
3022+ # ensure the -t ALL pulls in all TESTS
3023+ args = self.run_smoke._get_parser().parse_args(['-t', 'ALL'])
3024+ self.assertTrue(self.run_smoke._assert_args(args))
3025+ self.assertTrue(len(args.test) > 1)
3026+
3027+ def testAssertArgsEnv(self):
3028+ '''Ensure we pull in environment variables that jenkins uses.'''
3029+ with mock.patch.dict('os.environ'):
3030+ os.environ['APPS'] = 'apps'
3031+ os.environ['TESTS'] = 'tests'
3032+ os.environ['PACKAGES'] = 'packages'
3033+ os.environ['PPAS'] = 'ppas'
3034+ os.environ['IMAGE_TYPE'] = 'type'
3035+ os.environ['INSTALL_URL'] = 'url'
3036+ os.environ['IMAGE_OPT'] = 'opts opts'
3037+ os.environ['ANDROID_SERIAL'] = 'foo'
3038+
3039+ args = self.run_smoke._get_parser().parse_args([])
3040+ self.assertTrue(self.run_smoke._assert_args(args))
3041+
3042+ self.assertEqual(args.app, ['apps'])
3043+ self.assertEqual(args.test, ['tests'])
3044+ self.assertEqual(args.package, ['packages'])
3045+ self.assertEqual(args.ppa, ['ppas'])
3046+ self.assertEqual(args.image_type, 'type')
3047+ self.assertEqual(args.install_url, 'url')
3048+ self.assertEqual(args.image_opt, 'opts opts')
3049+
3050+ def testProvision(self):
3051+ orig = os.environ.get('IMAGE_OPT', '')
3052+ with mock.patch.object(self.run_smoke, '_run') as run:
3053+ args = self.run_smoke._get_parser().parse_args(
3054+ ['-s', 'foo', '--image-opt', 'FOOBAR', '-p', '1', '-p', '2'])
3055+ self.run_smoke._provision(args)
3056+ self.assertTrue(run.called)
3057+ val = os.environ.get('IMAGE_OPT')
3058+ os.environ['IMAGE_OPT'] = orig
3059+ self.assertEqual('FOOBAR', val)
3060+
3061+ def testUtahTests(self):
3062+ args = self.run_smoke._get_parser().parse_args(
3063+ ['-s', 'foo', '-t', 'a', '-t', 'b'])
3064+ with mock.patch.object(self.run_smoke, '_run') as run:
3065+ with mock.patch.dict('os.environ'):
3066+ run.side_effects = [subprocess.CalledProcessError, None]
3067+ with mock.patch.object(self.run_smoke, '_sync_results') as sr:
3068+ self.run_smoke._test_utah(args, None, None)
3069+ p = os.path.join(self.run_smoke.res_dir, 'b')
3070+ # ensuring b ran means, that we handled the failure of test
3071+ # 'a' and that the environment is setup correctly
3072+ self.assertEqual(os.environ['RESDIR'], p)
3073+ self.assertTrue(sr.called)
3074
3075=== added file 'selftests/test_statsd.py'
3076--- selftests/test_statsd.py 1970-01-01 00:00:00 +0000
3077+++ selftests/test_statsd.py 2014-02-13 16:08:19 +0000
3078@@ -0,0 +1,64 @@
3079+# Ubuntu Test Cases for Touch
3080+# Copyright 2013 Canonical Ltd.
3081+
3082+# This program is free software: you can redistribute it and/or modify it
3083+# under the terms of the GNU General Public License version 3, as published
3084+# by the Free Software Foundation.
3085+
3086+# This program is distributed in the hope that it will be useful, but
3087+# WITHOUT ANY WARRANTY; without even the implied warranties of
3088+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3089+# PURPOSE. See the GNU General Public License for more details.
3090+
3091+# You should have received a copy of the GNU General Public License along
3092+# with this program. If not, see <http://www.gnu.org/licenses/>.
3093+
3094+import os
3095+import mock
3096+import unittest
3097+import sys
3098+import time
3099+
3100+path = os.path.join(os.path.dirname(__file__), '../scripts')
3101+sys.path.append(path)
3102+
3103+import statsd
3104+
3105+
3106+class TestStatsd(unittest.TestCase):
3107+
3108+ def setUp(self):
3109+ self.origkey = os.environ.get('STATSD_KEY')
3110+ os.environ['STATSD_KEY'] = 'prefix'
3111+ reload(statsd)
3112+
3113+ def tearDown(self):
3114+ if not self.origkey:
3115+ del os.environ['STATSD_KEY']
3116+ else:
3117+ os.environ['STATSD_KEY'] = self.origkey
3118+ reload(statsd)
3119+
3120+ """Simple set of tests to make sure the statsd calls work."""
3121+
3122+ @mock.patch('txstatsd.metrics.metric.Metric.write')
3123+ def testGaugeIt(self, _statsd):
3124+ statsd.gauge_it('foo', None)
3125+ _statsd.assert_called_with('prefix.foo:0|g')
3126+ _statsd.reset_mock()
3127+
3128+ statsd.gauge_it('foo', [])
3129+ _statsd.assert_called_with('prefix.foo:0|g')
3130+ _statsd.reset_mock()
3131+
3132+ statsd.gauge_it('foo', [1, 2, 3])
3133+ _statsd.assert_called_with('prefix.foo:3|g')
3134+ _statsd.reset_mock()
3135+
3136+ @mock.patch('txstatsd.metrics.metric.Metric.write')
3137+ def testTimeIt(self, _statsd):
3138+ with statsd.time_it('foo'):
3139+ time.sleep(0.1)
3140+ # should have a timing of about 100ms
3141+ self.assertRegexpMatches(
3142+ _statsd.call_args[0][0], 'prefix.foo:100.[\d+]|ms')
3143
3144=== added directory 'tests'
3145=== added directory 'tests/click_image_tests'
3146=== added directory 'tests/click_image_tests/check_preinstalled_list'
3147=== added file 'tests/click_image_tests/check_preinstalled_list/check_preinstalled_list.py'
3148--- tests/click_image_tests/check_preinstalled_list/check_preinstalled_list.py 1970-01-01 00:00:00 +0000
3149+++ tests/click_image_tests/check_preinstalled_list/check_preinstalled_list.py 2014-02-13 16:08:19 +0000
3150@@ -0,0 +1,61 @@
3151+#!/usr/bin/python3
3152+# Copyright (C) 2013 Canonical Ltd.
3153+# Author: Sergio Schvezov <sergio.schvezov@canonical.com>
3154+
3155+# This program is free software: you can redistribute it and/or modify
3156+# it under the terms of the GNU General Public License as published by
3157+# the Free Software Foundation; version 3 of the License.
3158+#
3159+# This program is distributed in the hope that it will be useful,
3160+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3161+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3162+# GNU General Public License for more details.
3163+#
3164+# You should have received a copy of the GNU General Public License
3165+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3166+
3167+import subprocess
3168+import unittest
3169+import urllib.request
3170+
3171+
3172+click_list_url = \
3173+ 'http://people.canonical.com/~ubuntu-archive/click_packages/click_list'
3174+
3175+
3176+def get_install_list():
3177+ request = urllib.request.urlopen(click_list_url).read().decode('utf-8')
3178+ click_files = [x for x in request.split('\n') if x]
3179+ click_apps = {}
3180+ for entry in click_files:
3181+ entry_parts = entry.split('_')
3182+ click_apps[entry_parts[0]] = entry_parts[1]
3183+ return click_apps
3184+
3185+
3186+def get_image_list():
3187+ click_list = subprocess.check_output(
3188+ ['adb', 'shell', 'sudo', '-u', 'phablet',
3189+ 'bash', '-ic', 'click list']).decode('utf-8').split('\n')
3190+ click_entries = [x for x in click_list if x]
3191+ click_apps = {}
3192+ for entry in click_entries:
3193+ entry_parts = entry.split('\t')
3194+ click_apps[entry_parts[0]] = entry_parts[1].strip()
3195+ return click_apps
3196+
3197+
3198+class ClickPreinstalled(unittest.TestCase):
3199+
3200+ def setUp(self):
3201+ self.image_list = get_image_list()
3202+ self.install_list = get_install_list()
3203+ print('Search for %s on image' % self.install_list.keys())
3204+
3205+ def testPreinstalled(self):
3206+ for entry in self.install_list:
3207+ self.assertIn(entry, self.image_list.keys())
3208+
3209+
3210+if __name__ == '__main__':
3211+ unittest.main()
3212
3213=== added file 'tests/click_image_tests/check_preinstalled_list/tc_control'
3214--- tests/click_image_tests/check_preinstalled_list/tc_control 1970-01-01 00:00:00 +0000
3215+++ tests/click_image_tests/check_preinstalled_list/tc_control 2014-02-13 16:08:19 +0000
3216@@ -0,0 +1,10 @@
3217+description: check that all the expected click packages are installed
3218+dependencies: a touch read-only image
3219+action: |
3220+ 1. download the list of click packages
3221+ 2. list the current click packages installed on the device
3222+expected_results: |
3223+ 1. the current list of click packages from the archive should match
3224+type: userland
3225+timeout: 120
3226+command: ./check_preinstalled_list.py
3227
3228=== added file 'tests/click_image_tests/master.run'
3229--- tests/click_image_tests/master.run 1970-01-01 00:00:00 +0000
3230+++ tests/click_image_tests/master.run 2014-02-13 16:08:19 +0000
3231@@ -0,0 +1,5 @@
3232+---
3233+testsuites:
3234+ - name: click_image_tests
3235+ fetch_method: dev
3236+ fetch_location: ./
3237
3238=== added file 'tests/click_image_tests/tslist.run'
3239--- tests/click_image_tests/tslist.run 1970-01-01 00:00:00 +0000
3240+++ tests/click_image_tests/tslist.run 2014-02-13 16:08:19 +0000
3241@@ -0,0 +1,1 @@
3242+- test: check_preinstalled_list
3243
3244=== added directory 'tests/customizations'
3245=== added file 'tests/customizations/master.run'
3246--- tests/customizations/master.run 1970-01-01 00:00:00 +0000
3247+++ tests/customizations/master.run 2014-02-13 16:08:19 +0000
3248@@ -0,0 +1,5 @@
3249+---
3250+testsuites:
3251+ - name: customizations
3252+ fetch_method: dev
3253+ fetch_location: ./
3254
3255=== added file 'tests/customizations/setup.sh'
3256--- tests/customizations/setup.sh 1970-01-01 00:00:00 +0000
3257+++ tests/customizations/setup.sh 2014-02-13 16:08:19 +0000
3258@@ -0,0 +1,10 @@
3259+#!/bin/sh
3260+
3261+set -ex
3262+
3263+BZR_HOME=/dev/null bzr export customization_tests lp:savilerow/tests
3264+
3265+# copy the autopilot scripts over if needed (ie we are testing from HOST)
3266+[ -z $ANDROID_SERIAL ] || adb push ./customization_tests /home/phablet/autopilot/customization_tests
3267+
3268+prepare-autopilot-test.sh
3269
3270=== added file 'tests/customizations/ts_control'
3271--- tests/customizations/ts_control 1970-01-01 00:00:00 +0000
3272+++ tests/customizations/ts_control 2014-02-13 16:08:19 +0000
3273@@ -0,0 +1,1 @@
3274+ts_setup: ./setup.sh
3275
3276=== added file 'tests/customizations/tslist.auto'
3277--- tests/customizations/tslist.auto 1970-01-01 00:00:00 +0000
3278+++ tests/customizations/tslist.auto 2014-02-13 16:08:19 +0000
3279@@ -0,0 +1,4 @@
3280+-
3281+ discovery_cmd: autopilot-list customization_tests
3282+ test_cmd: autopilot-run {}
3283+
3284
3285=== added directory 'tests/default'
3286=== added directory 'tests/default/ifconfig'
3287=== added file 'tests/default/ifconfig/tc_control'
3288--- tests/default/ifconfig/tc_control 1970-01-01 00:00:00 +0000
3289+++ tests/default/ifconfig/tc_control 2014-02-13 16:08:19 +0000
3290@@ -0,0 +1,13 @@
3291+description: make sure wlan0 exists
3292+dependencies: none
3293+action: |
3294+ 1. ifconfig
3295+ 2. Make sure there is a wlan0
3296+expected_results: |
3297+ 1. there is a wlan0 on the system
3298+type: userland
3299+timeout: 60
3300+command: $TARGET_PREFIX ifconfig | grep wlan0
3301+#build_cmd:
3302+#tc_setup:
3303+#tc_cleanup:
3304
3305=== added directory 'tests/default/install'
3306=== added file 'tests/default/install/tc_control'
3307--- tests/default/install/tc_control 1970-01-01 00:00:00 +0000
3308+++ tests/default/install/tc_control 2014-02-13 16:08:19 +0000
3309@@ -0,0 +1,12 @@
3310+description: apt-get install works
3311+dependencies: openssh-server
3312+action: |
3313+ 1. apt-get install -y curl
3314+expected_results: |
3315+ 1. apt-get install can install a package and ends with exit code 0
3316+type: userland
3317+timeout: 120
3318+command: $TARGET_PREFIX apt-get install -y curl
3319+#build_cmd:
3320+#tc_setup:
3321+tc_cleanup: $TARGET_PREFIX apt-get remove -y curl
3322
3323=== added file 'tests/default/master.run'
3324--- tests/default/master.run 1970-01-01 00:00:00 +0000
3325+++ tests/default/master.run 2014-02-13 16:08:19 +0000
3326@@ -0,0 +1,5 @@
3327+---
3328+testsuites:
3329+ - name: smoke_touch
3330+ fetch_method: dev
3331+ fetch_location: ./
3332
3333=== added directory 'tests/default/netstat'
3334=== added file 'tests/default/netstat/tc_control'
3335--- tests/default/netstat/tc_control 1970-01-01 00:00:00 +0000
3336+++ tests/default/netstat/tc_control 2014-02-13 16:08:19 +0000
3337@@ -0,0 +1,12 @@
3338+description: netstat -l lists all the services
3339+dependencies: none
3340+action: |
3341+ 1. netstat -l
3342+expected_results: |
3343+ 1. netstat shows output and exists cleanly
3344+type: userland
3345+timeout: 60
3346+command: $TARGET_PREFIX netstat -l
3347+#build_cmd:
3348+#tc_setup:
3349+#tc_cleanup:
3350
3351=== added directory 'tests/default/ping'
3352=== added file 'tests/default/ping/pingtest.sh'
3353--- tests/default/ping/pingtest.sh 1970-01-01 00:00:00 +0000
3354+++ tests/default/ping/pingtest.sh 2014-02-13 16:08:19 +0000
3355@@ -0,0 +1,12 @@
3356+#!/bin/sh
3357+
3358+set -e
3359+
3360+if [ -z $TARGET_PREFIX ] ; then
3361+ echo "RUNNING ON TARGET"
3362+ ping -c 5 $(ip route show | head -n1 |cut -d" " -f3)
3363+else
3364+ echo "RUNNING FROM HOST"
3365+ ${TARGET_PREFIX} ping -c 5 '$(ip route show | head -n1 |cut -d" " -f3)'
3366+fi
3367+
3368
3369=== added file 'tests/default/ping/tc_control'
3370--- tests/default/ping/tc_control 1970-01-01 00:00:00 +0000
3371+++ tests/default/ping/tc_control 2014-02-13 16:08:19 +0000
3372@@ -0,0 +1,12 @@
3373+description: ping -c 5 $gateway
3374+dependencies: None
3375+action: |
3376+ 1. ping -c 5 $gateway
3377+expected_results: |
3378+ 1. ping ends without an error
3379+type: userland
3380+timeout: 60
3381+command: ./pingtest.sh
3382+#build_cmd:
3383+#tc_setup:
3384+#tc_cleanup:
3385
3386=== added directory 'tests/default/pwd'
3387=== added file 'tests/default/pwd/tc_control'
3388--- tests/default/pwd/tc_control 1970-01-01 00:00:00 +0000
3389+++ tests/default/pwd/tc_control 2014-02-13 16:08:19 +0000
3390@@ -0,0 +1,9 @@
3391+description: pwd works
3392+dependencies: none
3393+action: |
3394+ 1. cd to /tmp and verify which is the current directory
3395+expected_results: |
3396+ 1. Current directory is /tmp
3397+type: userland
3398+timeout: 60
3399+command: ./test.sh
3400
3401=== added file 'tests/default/pwd/test.sh'
3402--- tests/default/pwd/test.sh 1970-01-01 00:00:00 +0000
3403+++ tests/default/pwd/test.sh 2014-02-13 16:08:19 +0000
3404@@ -0,0 +1,18 @@
3405+#!/bin/sh
3406+
3407+set -e
3408+
3409+if [ -n "$TARGET_PREFIX" ] ; then
3410+ echo "RUNNING FROM HOST"
3411+else
3412+ echo "RUNNING ON TARGET"
3413+ TARGET_PREFIX="/bin/sh -c"
3414+fi
3415+
3416+#when it comes from adb we get a \r\n that should be removed
3417+dir=$($TARGET_PREFIX "cd /tmp; pwd" | head -n1 | tr -d '\r\n')
3418+if [ $dir != "/tmp" ] ; then
3419+ echo "failed to change directory"
3420+ exit 1
3421+fi
3422+exit 0
3423
3424=== added directory 'tests/default/route'
3425=== added file 'tests/default/route/tc_control'
3426--- tests/default/route/tc_control 1970-01-01 00:00:00 +0000
3427+++ tests/default/route/tc_control 2014-02-13 16:08:19 +0000
3428@@ -0,0 +1,12 @@
3429+description: route shows wlan0 routes
3430+dependencies: none
3431+action: |
3432+ 1. route -n | grep wlan0
3433+expected_results: |
3434+ 1. route shows wlan0 routes
3435+type: userland
3436+timeout: 60
3437+command: $TARGET_PREFIX route -n | grep wlan0
3438+#build_cmd:
3439+#tc_setup:
3440+#tc_cleanup:
3441
3442=== added directory 'tests/default/systemsettle'
3443=== added file 'tests/default/systemsettle/systemsettle.sh'
3444--- tests/default/systemsettle/systemsettle.sh 1970-01-01 00:00:00 +0000
3445+++ tests/default/systemsettle/systemsettle.sh 2014-02-13 16:08:19 +0000
3446@@ -0,0 +1,120 @@
3447+#!/bin/bash
3448+
3449+# Configuration variables:
3450+# TARGET_PREFIX - Allows this to be run from the host, by providings something
3451+# like TARGET_PREFIX="adb shell"
3452+# UTAH_PROBE_DIR - optionally where to save log files so utah will grab them
3453+
3454+set -e
3455+
3456+[ -z $UTAH_PROBE_DIR ] && UTAH_PROBE_DIR="/tmp"
3457+
3458+# default exit code storage
3459+dump_error=1
3460+
3461+calc () { awk "BEGIN{ print $* }" ;}
3462+
3463+function show_usage() {
3464+ echo "Usage:"
3465+ echo " $0 [options]"
3466+ echo "Options:"
3467+ echo " -r run forever without exiting"
3468+ echo " -p minimum idle percent to wait for (Default: 99)"
3469+ echo " -c number of times to run top at each iteration (Default: 10)"
3470+ echo " -d seconds to delay between each top iteration (Default: 6)"
3471+ echo " -i top measurements to ignore from each loop (Default: 1)"
3472+ echo " -m maximum loops of top before giving up if minimum idle"
3473+ echo " percent is not reached (Default: 10)"
3474+ echo " -l label to include for the top_log file"
3475+ exit 129
3476+}
3477+
3478+while getopts "h?rp:c:d:i:m:l:" opt; do
3479+ case "$opt" in
3480+ h|\?) show_usage
3481+ ;;
3482+ r) settle_prefix='-'
3483+ ;;
3484+ p) idle_avg_min=$OPTARG
3485+ ;;
3486+ c) top_repeat=$OPTARG
3487+ ;;
3488+ d) top_wait=$OPTARG
3489+ ;;
3490+ i) top_ignore=$OPTARG
3491+ ;;
3492+ m) settle_max=$OPTARG
3493+ ;;
3494+ l) top_log_label=$OPTARG
3495+ ;;
3496+ esac
3497+done
3498+
3499+# minimum average idle level required to succeed
3500+idle_avg_min=${idle_avg_min:-99}
3501+# measurement details: top $top_wait $top_repeat
3502+top_repeat=${top_repeat:-10}
3503+top_wait=${top_wait:-6}
3504+# how many samples to ignore
3505+top_ignore=${top_ignore:-1}
3506+# how many total attempts to settle the system
3507+settle_max=${settle_max:-10}
3508+
3509+top_log="$UTAH_PROBE_DIR/top$top_log_label.log"
3510+
3511+# set and calc more runtime values
3512+top_tail=`calc $top_repeat - $top_ignore`
3513+settle_count=0
3514+idle_avg=0
3515+
3516+echo "System Settle run - quiesce the system"
3517+echo "--------------------------------------"
3518+echo
3519+echo " idle_avg_min = '$idle_avg_min'"
3520+echo " top_repeat = '$top_repeat'"
3521+echo " top_wait = '$top_wait'"
3522+echo " top_ignore = '$top_ignore'"
3523+echo " settle_max = '$settle_max'"
3524+echo " run_forever = '$settle_prefix' (- = yes)"
3525+echo " log files = $top_log $top_log.reduced"
3526+echo " target prefix = $TARGET_PREFIX"
3527+echo
3528+
3529+while test `calc $idle_avg '<' $idle_avg_min` = 1 -a "$settle_prefix$settle_count" -lt "$settle_max"; do
3530+ echo -n "Starting system idle measurement (run: $settle_count) ... "
3531+
3532+ # get top
3533+ echo "TOP DUMP (after settle run: $settle_count)" >> $top_log
3534+ echo "========================" >> $top_log
3535+ ${TARGET_PREFIX} top -b -d $top_wait -n $top_repeat >> $top_log
3536+ cat $top_log | grep '.Cpu.*' | tail -n $top_tail > $top_log.reduced
3537+ echo >> $top_log
3538+
3539+ # calc average of idle field for this measurement
3540+ sum=0
3541+ count=0
3542+ while read line; do
3543+ idle=`echo $line | sed -e 's/.* \([0-9\.]*\) id.*/\1/'`
3544+ sum=`calc $sum + $idle`
3545+ count=`calc $count + 1`
3546+ done < $top_log.reduced
3547+
3548+ idle_avg=`calc $sum / $count`
3549+ settle_count=`calc $settle_count + 1`
3550+
3551+ echo " DONE."
3552+ echo
3553+ echo "Measurement:"
3554+ echo " + idle level: $idle_avg"
3555+ echo " + idle sum: $sum / count: $count"
3556+ echo
3557+done
3558+
3559+if test `calc $idle_avg '<' $idle_avg_min` = 1; then
3560+ echo "system not settled. FAIL"
3561+ exit 1
3562+else
3563+ echo "system settled. SUCCESS"
3564+ exit 0
3565+fi
3566+
3567
3568=== added file 'tests/default/systemsettle/tc_control'
3569--- tests/default/systemsettle/tc_control 1970-01-01 00:00:00 +0000
3570+++ tests/default/systemsettle/tc_control 2014-02-13 16:08:19 +0000
3571@@ -0,0 +1,9 @@
3572+description: check if system settles to idle average > 99.25%
3573+dependencies: none
3574+action: |
3575+ 1. Take CPU load samples for 10 minutes and fail if average idle never goes above 99.25% percent
3576+expected_results: |
3577+ 1. When doing nothing, system calms down to at least 99.25% idle level
3578+type: userland
3579+timeout: 720
3580+command: ./systemsettle.sh -c5 -d6 -p 97.5
3581
3582=== added file 'tests/default/ts_control'
3583--- tests/default/ts_control 1970-01-01 00:00:00 +0000
3584+++ tests/default/ts_control 2014-02-13 16:08:19 +0000
3585@@ -0,0 +1,3 @@
3586+#build_cmd: echo building
3587+#ts_setup: echo setting up
3588+#ts_cleanup: echo cleaning up
3589
3590=== added file 'tests/default/tslist.run'
3591--- tests/default/tslist.run 1970-01-01 00:00:00 +0000
3592+++ tests/default/tslist.run 2014-02-13 16:08:19 +0000
3593@@ -0,0 +1,10 @@
3594+- test: pwd
3595+- test: uname
3596+- test: vmstat
3597+- test: systemsettle
3598+- test: netstat
3599+- test: ifconfig
3600+- test: route
3601+- test: ping
3602+- test: install
3603+- test: unity8
3604
3605=== added directory 'tests/default/uname'
3606=== added file 'tests/default/uname/tc_control'
3607--- tests/default/uname/tc_control 1970-01-01 00:00:00 +0000
3608+++ tests/default/uname/tc_control 2014-02-13 16:08:19 +0000
3609@@ -0,0 +1,12 @@
3610+description: uname shows an ubuntu-phablet image installed
3611+dependencies: CHANGE_ME
3612+action: |
3613+ 1. uname -a|grep ubuntu-phablet
3614+expected_results: |
3615+ 1. ubuntu-phablet is installed as expected
3616+type: userland
3617+timeout: 60
3618+command: $TARGET_PREFIX uname -a | grep ubuntu-phablet
3619+#build_cmd:
3620+#tc_setup:
3621+#tc_cleanup:
3622
3623=== added directory 'tests/default/unity8'
3624=== added file 'tests/default/unity8/tc_control'
3625--- tests/default/unity8/tc_control 1970-01-01 00:00:00 +0000
3626+++ tests/default/unity8/tc_control 2014-02-13 16:08:19 +0000
3627@@ -0,0 +1,9 @@
3628+description: check for unity8 process
3629+dependencies: None
3630+action: |
3631+ 1. pgrep unity8
3632+expected_results: |
3633+ 1. pgrep exits with 0, indicating it found a matching process
3634+type: userland
3635+timeout: 60
3636+command: $TARGET_PREFIX /usr/bin/pgrep unity8
3637
3638=== added directory 'tests/default/vmstat'
3639=== added file 'tests/default/vmstat/tc_control'
3640--- tests/default/vmstat/tc_control 1970-01-01 00:00:00 +0000
3641+++ tests/default/vmstat/tc_control 2014-02-13 16:08:19 +0000
3642@@ -0,0 +1,12 @@
3643+description: Execute vmstat and capture output
3644+dependencies: none
3645+action: |
3646+ 1. vmstat
3647+expected_results: |
3648+ 1. vmstat finishes without an error
3649+type: userland
3650+timeout: 60
3651+command: $TARGET_PREFIX vmstat
3652+#build_cmd:
3653+#tc_setup:
3654+#tc_cleanup:
3655
3656=== added directory 'tests/eventstat'
3657=== added directory 'tests/eventstat/eventstat'
3658=== added file 'tests/eventstat/eventstat/eventstat.sh'
3659--- tests/eventstat/eventstat/eventstat.sh 1970-01-01 00:00:00 +0000
3660+++ tests/eventstat/eventstat/eventstat.sh 2014-02-13 16:08:19 +0000
3661@@ -0,0 +1,5 @@
3662+#!/bin/bash
3663+: ${DURATION:=60}
3664+: ${COUNT:=10}
3665+${TARGET_PREFIX} "echo \"{\\\"duration\\\": $DURATION, \\\"count\\\": $COUNT}\" > /tmp/results/options.json"
3666+${TARGET_PREFIX} "eventstat -CSl -r /tmp/results/eventstat.csv $DURATION $COUNT > /tmp/results/eventstat.log"
3667
3668=== added file 'tests/eventstat/eventstat/setup.sh'
3669--- tests/eventstat/eventstat/setup.sh 1970-01-01 00:00:00 +0000
3670+++ tests/eventstat/eventstat/setup.sh 2014-02-13 16:08:19 +0000
3671@@ -0,0 +1,7 @@
3672+#!/bin/bash
3673+
3674+${TARGET_PREFIX} mkdir -p /tmp/results
3675+NO_UNLOCK=1 PKGS="eventstat" prepare-autopilot-test.sh
3676+#Let the system quiet down for a little while
3677+sleep 300
3678+
3679
3680=== added file 'tests/eventstat/eventstat/tc_control'
3681--- tests/eventstat/eventstat/tc_control 1970-01-01 00:00:00 +0000
3682+++ tests/eventstat/eventstat/tc_control 2014-02-13 16:08:19 +0000
3683@@ -0,0 +1,12 @@
3684+description: Gather memory usage information
3685+dependencies: none
3686+action: |
3687+ 1. gather eventstat data
3688+expected_results: |
3689+ 1. eventstat.log should be saved to /tmp/results
3690+type: userland
3691+timeout: 800
3692+command: ./eventstat.sh
3693+#build_cmd:
3694+tc_setup: ./setup.sh
3695+#tc_cleanup:
3696
3697=== added file 'tests/eventstat/master.run'
3698--- tests/eventstat/master.run 1970-01-01 00:00:00 +0000
3699+++ tests/eventstat/master.run 2014-02-13 16:08:19 +0000
3700@@ -0,0 +1,5 @@
3701+---
3702+testsuites:
3703+ - name: eventstat
3704+ fetch_method: dev
3705+ fetch_location: ./
3706
3707=== added file 'tests/eventstat/tslist.run'
3708--- tests/eventstat/tslist.run 1970-01-01 00:00:00 +0000
3709+++ tests/eventstat/tslist.run 2014-02-13 16:08:19 +0000
3710@@ -0,0 +1,1 @@
3711+- test: eventstat
3712
3713=== added directory 'tests/memevent'
3714=== added file 'tests/memevent/master.run'
3715--- tests/memevent/master.run 1970-01-01 00:00:00 +0000
3716+++ tests/memevent/master.run 2014-02-13 16:08:19 +0000
3717@@ -0,0 +1,5 @@
3718+---
3719+testsuites:
3720+ - name: memevent
3721+ fetch_method: dev
3722+ fetch_location: ./
3723
3724=== added file 'tests/memevent/setup.sh'
3725--- tests/memevent/setup.sh 1970-01-01 00:00:00 +0000
3726+++ tests/memevent/setup.sh 2014-02-13 16:08:19 +0000
3727@@ -0,0 +1,8 @@
3728+#!/bin/sh
3729+
3730+set -e
3731+
3732+# copy the autopilot scripts over if needed
3733+[ -z $ANDROID_SERIAL ] || adb push ./ubuntu_test_cases /home/phablet/autopilot/ubuntu_test_cases
3734+
3735+PKGS="camera-app-autopilot gallery-app-autopilot mediaplayer-app-autopilot webbrowser-app-autopilot" prepare-autopilot-test.sh
3736
3737=== added file 'tests/memevent/ts_control'
3738--- tests/memevent/ts_control 1970-01-01 00:00:00 +0000
3739+++ tests/memevent/ts_control 2014-02-13 16:08:19 +0000
3740@@ -0,0 +1,1 @@
3741+ts_setup: ./setup.sh
3742
3743=== added file 'tests/memevent/tslist.auto'
3744--- tests/memevent/tslist.auto 1970-01-01 00:00:00 +0000
3745+++ tests/memevent/tslist.auto 2014-02-13 16:08:19 +0000
3746@@ -0,0 +1,7 @@
3747+---
3748+# The discovery command is run from a directory containing the whole suite
3749+# The test command is run from a sub directory in that
3750+# We need to run each command in the directory that contains ubuntu_test_cases
3751+# so that autopilot can find our python modules
3752+- discovery_cmd: cd memevent; autopilot-list ubuntu_test_cases.memory_usage_measurement
3753+ test_cmd: cd ..; autopilot-run ubuntu_test_cases.memory_usage_measurement.tests.{}
3754
3755=== added directory 'tests/memevent/ubuntu_test_cases'
3756=== added file 'tests/memevent/ubuntu_test_cases/__init__.py'
3757--- tests/memevent/ubuntu_test_cases/__init__.py 1970-01-01 00:00:00 +0000
3758+++ tests/memevent/ubuntu_test_cases/__init__.py 2014-02-13 16:08:19 +0000
3759@@ -0,0 +1,6 @@
3760+"""ubuntu_test_cases package
3761+
3762+Warning: This python package is installed by a different debian package in a
3763+different branch. It's been been created here only because it's convenient to
3764+run autopilot directly from the branch.
3765+"""
3766
3767=== added directory 'tests/memevent/ubuntu_test_cases/memory_usage_measurement'
3768=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/__init__.py'
3769--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/__init__.py 1970-01-01 00:00:00 +0000
3770+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/__init__.py 2014-02-13 16:08:19 +0000
3771@@ -0,0 +1,1 @@
3772+"""Event based memory usage measurements test cases."""
3773
3774=== added directory 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps'
3775=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/__init__.py'
3776--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/__init__.py 1970-01-01 00:00:00 +0000
3777+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/__init__.py 2014-02-13 16:08:19 +0000
3778@@ -0,0 +1,21 @@
3779+"""Application classes used to drive them easily with autopilot."""
3780+
3781+from autopilot.input import Keyboard, Mouse, Pointer, Touch
3782+from autopilot.platform import model
3783+
3784+
3785+class App(object):
3786+
3787+ """Application class with common code."""
3788+
3789+ def __init__(self, tc):
3790+ self.tc = tc
3791+ self.app = None
3792+ self.window = None
3793+
3794+ self.keyboard = Keyboard.create()
3795+ if model() == 'Desktop':
3796+ input_device = Mouse.create()
3797+ else:
3798+ input_device = Touch.create()
3799+ self.pointer = Pointer(input_device)
3800
3801=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/browser.py'
3802--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/browser.py 1970-01-01 00:00:00 +0000
3803+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/browser.py 2014-02-13 16:08:19 +0000
3804@@ -0,0 +1,103 @@
3805+"""Browser application to write autopilot test cases easily."""
3806+
3807+from testtools.matchers import Contains, Equals
3808+
3809+from ubuntuuitoolkit.emulators import UbuntuUIToolkitEmulatorBase
3810+from webbrowser_app.emulators.browser import Browser
3811+
3812+from ubuntu_test_cases.memory_usage_measurement.apps import App
3813+from ubuntu_test_cases.memory_usage_measurement.matchers import (
3814+ DoesNotChange,
3815+ Eventually,
3816+)
3817+
3818+
3819+class BrowserApp(App):
3820+
3821+ """Browser application."""
3822+
3823+ TYPING_DELAY = 0.01
3824+
3825+ def assert_chrome_eventually_hidden(self):
3826+ """Make sure chrome is eventually hidden."""
3827+
3828+ toolbar = self.window.get_toolbar()
3829+ self.tc.assertThat(toolbar.opened, Eventually(Equals(False)))
3830+ self.tc.assertThat(toolbar.animating, Eventually(Equals(False)))
3831+
3832+ def assert_page_eventually_loaded(self, url):
3833+ """Make sure page is eventually loaded."""
3834+ webview = self.window.get_current_webview()
3835+ self.tc.assertThat(webview.url, Eventually(Equals(url)))
3836+ # loadProgress == 100 ensures that a page has actually loaded
3837+ self.tc.assertThat(webview.loadProgress, Eventually(Equals(100)))
3838+ self.tc.assertThat(webview.loading, Eventually(Equals(False)))
3839+
3840+ def clear_address_bar(self):
3841+ """Clear address bar."""
3842+ self.focus_address_bar()
3843+ clear_button = self.window.get_address_bar_clear_button()
3844+ self.tc.assertThat(lambda: (clear_button.x,
3845+ clear_button.y,
3846+ clear_button.y),
3847+ Eventually(DoesNotChange()))
3848+ self.pointer.move_to_object(clear_button)
3849+ self.pointer.click()
3850+ text_field = self.window.get_address_bar_text_field()
3851+ self.tc.assertThat(text_field.text, Eventually(Equals("")))
3852+
3853+ def ensure_chrome_is_hidden(self):
3854+ """Make sure chrome is hidden."""
3855+ webview = self.window.get_current_webview()
3856+ self.pointer.move_to_object(webview)
3857+ self.pointer.click()
3858+ self.assert_chrome_eventually_hidden()
3859+
3860+ def focus_address_bar(self):
3861+ """Make sure address bar is focused."""
3862+ address_bar = self.window.get_address_bar()
3863+ self.pointer.move_to_object(address_bar)
3864+ self.pointer.click()
3865+ self.tc.assertThat(address_bar.activeFocus, Eventually(Equals(True)))
3866+
3867+ def go_to_url(self, url):
3868+ """Go to given url.
3869+
3870+ :param url: URL that should be loaded in the browser
3871+ :type url: str
3872+
3873+ """
3874+ self.ensure_chrome_is_hidden()
3875+ self.window.open_toolbar()
3876+ self.clear_address_bar()
3877+ self.type_in_address_bar(url)
3878+ self.keyboard.press_and_release("Enter")
3879+
3880+ def launch(self):
3881+ """Launch application."""
3882+ args = [
3883+ 'webbrowser-app',
3884+ '--fullscreen',
3885+ ('--desktop_file_hint='
3886+ '/usr/share/applications/webbrowser-app.desktop'),
3887+ ]
3888+ self.app = self.tc.launch_test_application(
3889+ *args,
3890+ app_type='qt',
3891+ emulator_base=UbuntuUIToolkitEmulatorBase
3892+ )
3893+ self.window = self.app.select_single(Browser)
3894+ self.window.visible.wait_for(True)
3895+
3896+ def type_in_address_bar(self, text):
3897+ """Type text in address bar.
3898+
3899+ :param text: Text to be typed in the address bar.
3900+ :type text: str
3901+
3902+ """
3903+ address_bar = self.window.get_address_bar()
3904+ self.tc.assertThat(address_bar.activeFocus, Eventually(Equals(True)))
3905+ self.keyboard.type(text, delay=self.TYPING_DELAY)
3906+ text_field = self.window.get_address_bar_text_field()
3907+ self.tc.assertThat(text_field.text, Eventually(Contains(text)))
3908
3909=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/camera.py'
3910--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/camera.py 1970-01-01 00:00:00 +0000
3911+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/camera.py 2014-02-13 16:08:19 +0000
3912@@ -0,0 +1,58 @@
3913+"""Camera application to write autopilot test cases easily."""
3914+
3915+import logging
3916+import os
3917+
3918+from glob import glob
3919+
3920+from camera_app.emulators.main_window import MainWindow as CameraWindow
3921+from testtools.matchers import Equals, HasLength
3922+
3923+from ubuntu_test_cases.memory_usage_measurement.apps import App
3924+from ubuntu_test_cases.memory_usage_measurement.matchers import Eventually
3925+
3926+LOGGER = logging.getLogger(__file__)
3927+
3928+
3929+class CameraApp(App):
3930+
3931+ """Camera application."""
3932+
3933+ IMAGE_FILENAME_PATTERN = os.path.expanduser('~/Pictures/image*')
3934+
3935+ def _remove_image_files(self):
3936+ """Remove all image files.
3937+
3938+ This is useful not only to cleanup, but also to check that just one
3939+ image file has been written to disk.
3940+
3941+ """
3942+ for filename in glob(self.IMAGE_FILENAME_PATTERN):
3943+ LOGGER.debug('Removing image file: {}'.format(filename))
3944+ os.remove(filename)
3945+
3946+ def launch(self):
3947+ """Launch application."""
3948+ args = [
3949+ 'camera-app',
3950+ '--fullscreen',
3951+ ('--desktop_file_hint='
3952+ '/usr/share/applications/camera-app.desktop'),
3953+ ]
3954+ self.app = self.tc.launch_test_application(*args, app_type='qt')
3955+ self.window = CameraWindow(self.app)
3956+
3957+ def take_picture(self):
3958+ """Click on the exposure button to take picture."""
3959+ self._remove_image_files()
3960+
3961+ exposure_button = self.window.get_exposure_button()
3962+ self.tc.assertThat(exposure_button.enabled, Eventually(Equals(True)))
3963+
3964+ LOGGER.debug('Taking picture...')
3965+ self.pointer.move_to_object(exposure_button)
3966+ self.pointer.click()
3967+
3968+ # Wait for image file to be produced before memory usage measurement
3969+ self.tc.assertThat(lambda: glob(self.IMAGE_FILENAME_PATTERN),
3970+ Eventually(HasLength(1)))
3971
3972=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/gallery.py'
3973--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/gallery.py 1970-01-01 00:00:00 +0000
3974+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/gallery.py 2014-02-13 16:08:19 +0000
3975@@ -0,0 +1,41 @@
3976+"""Gallery application to write autopilot test cases easily."""
3977+
3978+import os
3979+import shutil
3980+import tempfile
3981+
3982+from ubuntu_test_cases.memory_usage_measurement.apps import App
3983+
3984+
3985+class GalleryApp(App):
3986+
3987+ """Gallery application."""
3988+
3989+ SAMPLE_DIR = '/usr/lib/python2.7/dist-packages/gallery_app/data/default'
3990+
3991+ def __init__(self, tc):
3992+ super(GalleryApp, self).__init__(tc)
3993+ self.temp_sample_dir = None
3994+ self._copy_sample_dir()
3995+
3996+ def _copy_sample_dir(self):
3997+ """Copy sample directory to a temporary location.
3998+
3999+ This is useful to provide some default content.
4000+
4001+ """
4002+ self.temp_sample_dir = tempfile.mkdtemp(prefix='gallery-app-test-')
4003+ self.tc.addCleanup(shutil.rmtree, self.temp_sample_dir)
4004+ self.temp_sample_dir = os.path.join(self.temp_sample_dir, 'data')
4005+ shutil.copytree(self.SAMPLE_DIR, self.temp_sample_dir)
4006+
4007+ def launch(self):
4008+ """Launch the application."""
4009+ args = [
4010+ 'gallery-app',
4011+ ('--desktop_file_hint='
4012+ '/usr/share/applications/gallery-app.desktop'),
4013+ self.temp_sample_dir,
4014+ ]
4015+ self.app = self.tc.launch_test_application(*args,
4016+ app_type='qt')
4017
4018=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/media_player.py'
4019--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/media_player.py 1970-01-01 00:00:00 +0000
4020+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/apps/media_player.py 2014-02-13 16:08:19 +0000
4021@@ -0,0 +1,53 @@
4022+"""Media player application to write autopilot test cases easily."""
4023+
4024+import os
4025+
4026+from testtools.matchers import Equals, GreaterThan
4027+
4028+from mediaplayer_app.emulators.main_window import (
4029+ MainWindow as MediaPlayerWindow)
4030+
4031+from ubuntu_test_cases.memory_usage_measurement.apps import App
4032+from ubuntu_test_cases.memory_usage_measurement.matchers import Eventually
4033+
4034+
4035+class MediaPlayerApp(App):
4036+
4037+ """Media player application."""
4038+
4039+ # Default content
4040+ VIDEOS_DIR = 'file:///usr/share/mediaplayer-app/videos/'
4041+
4042+ def assert_playback_finished(self):
4043+ """Media player memory usage after playing a file."""
4044+ time_line = self.window.get_slider()
4045+
4046+ # Time line value isn't set to maximum value after playback is finished
4047+ # (LP: #1190555)
4048+ maximum_value = time_line.maximumValue - 2.0
4049+ self.tc.assertThat(time_line.value,
4050+ Eventually(GreaterThan(maximum_value)))
4051+
4052+ def launch(self, movie_file=None):
4053+ """Launch application.
4054+
4055+ :param movie_file:
4056+ Relative path to movie file (uses default content directory as
4057+ root).
4058+ :type movie_file: str
4059+
4060+ """
4061+ binary = 'mediaplayer-app'
4062+ args = [
4063+ binary,
4064+ '--fullscreen',
4065+ ('--desktop_file_hint='
4066+ '/usr/share/applications/mediaplayer-app.desktop'),
4067+ ]
4068+ if movie_file:
4069+ args.insert(1, os.path.join(self.VIDEOS_DIR, movie_file))
4070+
4071+ self.app = self.tc.launch_test_application(*args, app_type='qt')
4072+ self.window = MediaPlayerWindow(self.app)
4073+ self.tc.assertThat(self.window.get_qml_view().visible,
4074+ Eventually(Equals(True)))
4075
4076=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/matchers.py'
4077--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/matchers.py 1970-01-01 00:00:00 +0000
4078+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/matchers.py 2014-02-13 16:08:19 +0000
4079@@ -0,0 +1,37 @@
4080+"""Custom matchers used by the tests."""
4081+
4082+from autopilot.matchers import Eventually as EventuallyMatcher
4083+from testtools.matchers import Mismatch
4084+
4085+
4086+def Eventually(matcher):
4087+ """Wrapper around autopilot.matchers.Eventually.
4088+
4089+ The aim of the wrapper is just use a different timeout default
4090+
4091+ :param matcher: A testools-like matcher that tests the desired condition
4092+ :type matcher: object
4093+ :returns: A value depending on the matcher protocol
4094+ :rtype: None | a mismatch object with information about the mismatch
4095+
4096+ """
4097+ return EventuallyMatcher(matcher, timeout=40)
4098+
4099+
4100+class DoesNotChange(object):
4101+ """Match if two consecutive values are equal."""
4102+ def __init__(self):
4103+ self.old_value = None
4104+ self.value = None
4105+
4106+ def __str__(self):
4107+ return 'DoesNotChange()'
4108+
4109+ def match(self, value):
4110+ self.old_value = self.value
4111+ self.value = value
4112+ if self.value != self.old_value:
4113+ return Mismatch('Current value ({}) does not match old value ({})'
4114+ .format(self.value, self.old_value))
4115+
4116+ return None
4117
4118=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/probes.py'
4119--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/probes.py 1970-01-01 00:00:00 +0000
4120+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/probes.py 2014-02-13 16:08:19 +0000
4121@@ -0,0 +1,170 @@
4122+"""Probes used to take measurements4 while the tests are executed."""
4123+
4124+import itertools
4125+import logging
4126+import os
4127+import re
4128+import subprocess
4129+
4130+from contextlib import contextmanager
4131+from time import time
4132+
4133+LOGGER = logging.getLogger(__file__)
4134+
4135+
4136+class SmemProbe(object):
4137+
4138+ """Memory usage probe."""
4139+
4140+ BINARY = os.path.join(os.path.dirname(__file__), 'smem-tabs')
4141+ THRESHOLDS = {
4142+ 'foreground': 262144, # 256MB
4143+ 'background': 131072, # 128MB
4144+ }
4145+
4146+ def __init__(self):
4147+ self.pids = [] # List of pids that should be monitored
4148+ self.readings = [] # List of readings
4149+ self.current_reading = None
4150+ self.threshold_exceeded_summary = []
4151+
4152+ @contextmanager
4153+ def probe(self, event):
4154+ """Run start and stop methods in a contex manager."""
4155+ self.start(event)
4156+ yield
4157+ self.stop(event)
4158+
4159+ def start(self, event):
4160+ """Start measurement.
4161+
4162+ This method is not actually used, but defined to be consistent with the
4163+ probes API.
4164+
4165+ :param event: Event name
4166+ :type event: str
4167+ :returns: None
4168+
4169+ """
4170+ LOGGER.debug('smem start: {}'.format(event))
4171+ self.current_reading = {
4172+ 'event': event,
4173+ 'start_time': time(),
4174+ }
4175+
4176+ def stop(self, event):
4177+ """Stop measurement.
4178+
4179+ Run smem and get memory usage for a given set PIDs.
4180+
4181+ :param event: Event name
4182+ :type event: str
4183+ :returns: None
4184+
4185+ """
4186+ LOGGER.debug('smem stop: {}'.format(event))
4187+ LOGGER.debug('Running {!r}...'.format(self.BINARY))
4188+ output = subprocess.check_output(self.BINARY)
4189+ parser = SmemParser()
4190+ pids_info = parser.parse(output)
4191+ threshold_exceeded_pids = self._calculate_threshold_exceeded(pids_info)
4192+ print '{:-^72}'.format(event)
4193+ for pid in self.pids:
4194+ print('PID: {pid}, command: {command}, PSS: {pss}, USS: {uss}'
4195+ .format(**pids_info[pid]))
4196+
4197+ self.current_reading['stop_time'] = time()
4198+ self.current_reading['data'] = pids_info
4199+ self.readings.append(self.current_reading)
4200+ if threshold_exceeded_pids:
4201+ self.threshold_exceeded_summary.append(
4202+ (event, threshold_exceeded_pids))
4203+ self.current_reading = None
4204+
4205+ def _calculate_threshold_exceeded(self, pids_info):
4206+ """Calculate thresholds for the given set of pids.
4207+
4208+ :param pids_info:
4209+ Memory usage data for a give set of pids.
4210+
4211+ ..note::
4212+ This parameter is modified in place to add a delta of the PSS
4213+ and the threshold.
4214+ :type pids_info: list(dict)
4215+
4216+ """
4217+ # It's assumed that the pid for the foreground application is the one
4218+ # that was last added to the probe
4219+ foreground_pid = self.pids[-1]
4220+ foreground_threshold = self.THRESHOLDS['foreground']
4221+ background_pids = self.pids[:-1]
4222+ background_threshold = self.THRESHOLDS['background']
4223+
4224+ pids_and_thresholds = itertools.chain(
4225+ [(foreground_pid, foreground_threshold)],
4226+ itertools.product(background_pids, [background_threshold]))
4227+
4228+ threshold_exceeded_pids = []
4229+ for pid, threshold in pids_and_thresholds:
4230+ if pid in pids_info:
4231+ pid_info = pids_info[pid]
4232+ delta = pid_info['pss'] - threshold
4233+ pid_info['threshold_exceeded'] = delta
4234+ if delta > 0:
4235+ threshold_exceeded_pids.append(pid)
4236+ return threshold_exceeded_pids
4237+
4238+ @property
4239+ def report(self):
4240+ """Return report with all the readings that have been made."""
4241+ return {'pids': self.pids,
4242+ 'thresholds': self.THRESHOLDS,
4243+ 'readings': self.readings}
4244+
4245+
4246+class SmemParser(object):
4247+
4248+ """Parser object to map smem output to data structure."""
4249+
4250+ SMEM_LINE = re.compile(
4251+ r'\s*(?P<pid>\d+)'
4252+ r'\s+(?P<user>\w+)'
4253+ r'\s+(?P<command>.+?)'
4254+ r'\s+(?P<swap>\d+)'
4255+ r'\s+(?P<uss>\d+)'
4256+ r'\s+(?P<pss>\d+)'
4257+ r'\s+(?P<rss>\d+)')
4258+
4259+ def parse(self, output):
4260+ """Parse smem output.
4261+
4262+ :param output: The report printed to stdout by smem
4263+ :type output: str
4264+ :returns: Data structure that can be serialized
4265+ :rtype: dict(dict)
4266+
4267+ """
4268+ pids_info = filter(None,
4269+ [self._parse_line(line)
4270+ for line in output.splitlines()[1:]])
4271+ return {pid_info['pid']: pid_info for pid_info in pids_info}
4272+
4273+ def _parse_line(self, line):
4274+ """Parse a single smem output line.
4275+
4276+ :param line:
4277+ A single line containing all the fields to parse for a process.
4278+ :type line: str
4279+ :returns: Data structure with the parsed fields
4280+ :rtype: dict
4281+
4282+ """
4283+ match = self.SMEM_LINE.match(line)
4284+ if not match:
4285+ LOGGER.warning('Unable to parse smem output: {}'.format(line))
4286+ return None
4287+
4288+ pid_info = match.groupdict()
4289+ for key in ('pid', 'swap', 'uss', 'pss', 'rss'):
4290+ pid_info[key] = int(pid_info[key])
4291+ return pid_info
4292
4293=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/smem-tabs'
4294--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/smem-tabs 1970-01-01 00:00:00 +0000
4295+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/smem-tabs 2014-02-13 16:08:19 +0000
4296@@ -0,0 +1,687 @@
4297+#!/usr/bin/env python
4298+#
4299+# smem - a tool for meaningful memory reporting
4300+#
4301+# Copyright 2008-2009 Matt Mackall <mpm@selenic.com>
4302+#
4303+# This software may be used and distributed according to the terms of
4304+# the GNU General Public License version 2 or later, incorporated
4305+# herein by reference.
4306+
4307+import re, os, sys, pwd, optparse, errno, tarfile
4308+
4309+warned = False
4310+
4311+class procdata(object):
4312+ def __init__(self, source):
4313+ self._ucache = {}
4314+ self._gcache = {}
4315+ self.source = source and source or ""
4316+ self._memdata = None
4317+ def _list(self):
4318+ return os.listdir(self.source + "/proc")
4319+ def _read(self, f):
4320+ return file(self.source + '/proc/' + f).read()
4321+ def _readlines(self, f):
4322+ return self._read(f).splitlines(True)
4323+ def _stat(self, f):
4324+ return os.stat(self.source + "/proc/" + f)
4325+
4326+ def pids(self):
4327+ '''get a list of processes'''
4328+ return [int(e) for e in self._list()
4329+ if e.isdigit() and not iskernel(e)]
4330+ def mapdata(self, pid):
4331+ return self._readlines('%s/smaps' % pid)
4332+ def memdata(self):
4333+ if self._memdata is None:
4334+ self._memdata = self._readlines('meminfo')
4335+ return self._memdata
4336+ def version(self):
4337+ return self._readlines('version')[0]
4338+ def pidname(self, pid):
4339+ try:
4340+ l = self.pidcmd(pid).split(' ')[0]
4341+ return os.path.basename(l)
4342+ except:
4343+ return '?'
4344+ def pidcmd(self, pid):
4345+ try:
4346+ c = self._read('%s/cmdline' % pid)[:-1]
4347+ return c.replace('\0', ' ')
4348+ except:
4349+ return '?'
4350+ def piduser(self, pid):
4351+ try:
4352+ return self._stat('%d' % pid).st_uid
4353+ except:
4354+ return -1
4355+ def pidgroup(self, pid):
4356+ try:
4357+ return self._stat('%d' % pid).st_gid
4358+ except:
4359+ return -1
4360+ def username(self, uid):
4361+ if uid == -1:
4362+ return '?'
4363+ if uid not in self._ucache:
4364+ try:
4365+ self._ucache[uid] = pwd.getpwuid(uid)[0]
4366+ except KeyError:
4367+ self._ucache[uid] = str(uid)
4368+ return self._ucache[uid]
4369+ def groupname(self, gid):
4370+ if gid == -1:
4371+ return '?'
4372+ if gid not in self._gcache:
4373+ try:
4374+ self._gcache[gid] = pwd.getgrgid(gid)[0]
4375+ except KeyError:
4376+ self._gcache[gid] = str(gid)
4377+ return self._gcache[gid]
4378+
4379+class tardata(procdata):
4380+ def __init__(self, source):
4381+ procdata.__init__(self, source)
4382+ self.tar = tarfile.open(source)
4383+ def _list(self):
4384+ for ti in self.tar:
4385+ if ti.name.endswith('/smaps'):
4386+ d,f = ti.name.split('/')
4387+ yield d
4388+ def _read(self, f):
4389+ return self.tar.extractfile(f).read()
4390+ def _readlines(self, f):
4391+ return self.tar.extractfile(f).readlines()
4392+ def piduser(self, p):
4393+ t = self.tar.getmember("%d" % p)
4394+ if t.uname:
4395+ self._ucache[t.uid] = t.uname
4396+ return t.uid
4397+ def pidgroup(self, p):
4398+ t = self.tar.getmember("%d" % p)
4399+ if t.gname:
4400+ self._gcache[t.gid] = t.gname
4401+ return t.gid
4402+ def username(self, u):
4403+ return self._ucache.get(u, str(u))
4404+ def groupname(self, g):
4405+ return self._gcache.get(g, str(g))
4406+
4407+_totalmem = 0
4408+def totalmem():
4409+ global _totalmem
4410+ if not _totalmem:
4411+ if options.realmem:
4412+ _totalmem = fromunits(options.realmem) / 1024
4413+ else:
4414+ _totalmem = memory()['memtotal']
4415+ return _totalmem
4416+
4417+_kernelsize = 0
4418+def kernelsize():
4419+ global _kernelsize
4420+ if not _kernelsize and options.kernel:
4421+ try:
4422+ d = os.popen("size %s" % options.kernel).readlines()[1]
4423+ _kernelsize = int(d.split()[3]) / 1024
4424+ except:
4425+ try:
4426+ # try some heuristic to find gzipped part in kernel image
4427+ packedkernel = open(options.kernel).read()
4428+ pos = packedkernel.find('\x1F\x8B')
4429+ if pos >= 0 and pos < 25000:
4430+ sys.stderr.write("Maybe uncompressed kernel can be extracted by the command:\n"
4431+ " dd if=%s bs=1 skip=%d | gzip -d >%s.unpacked\n\n" % (options.kernel, pos, options.kernel))
4432+ except:
4433+ pass
4434+ sys.stderr.write("Parameter '%s' should be an original uncompressed compiled kernel file.\n\n" % options.kernel)
4435+ return _kernelsize
4436+
4437+def pidmaps(pid):
4438+ global warned
4439+ maps = {}
4440+ start = None
4441+ seen = False
4442+ for l in src.mapdata(pid):
4443+ f = l.split()
4444+ if f[-1] == 'kB':
4445+ if f[0].startswith('Pss'):
4446+ seen = True
4447+ maps[start][f[0][:-1].lower()] = int(f[1])
4448+ elif f[0] == 'VmFlags:':
4449+ continue
4450+ else:
4451+ start, end = f[0].split('-')
4452+ start = int(start, 16)
4453+ name = "<anonymous>"
4454+ if len(f) > 5:
4455+ name = f[5]
4456+ maps[start] = dict(end=int(end, 16), mode=f[1],
4457+ offset=int(f[2], 16),
4458+ device=f[3], inode=f[4], name=name)
4459+
4460+ if not seen and not warned:
4461+ sys.stderr.write('warning: kernel does not appear to support PSS measurement\n')
4462+ warned = True
4463+ if not options.sort:
4464+ options.sort = 'rss'
4465+
4466+ if options.mapfilter:
4467+ f = {}
4468+ for m in maps:
4469+ if not filter(options.mapfilter, m, lambda x: maps[x]['name']):
4470+ f[m] = maps[m]
4471+ return f
4472+
4473+ return maps
4474+
4475+def sortmaps(totals, key):
4476+ l = []
4477+ for pid in totals:
4478+ l.append((totals[pid][key], pid))
4479+ l.sort()
4480+ return [pid for pid,key in l]
4481+
4482+def iskernel(pid):
4483+ return src.pidcmd(pid) == ""
4484+
4485+def memory():
4486+ t = {}
4487+ f = re.compile('(\\S+):\\s+(\\d+) kB')
4488+ for l in src.memdata():
4489+ m = f.match(l)
4490+ if m:
4491+ t[m.group(1).lower()] = int(m.group(2))
4492+ return t
4493+
4494+def units(x):
4495+ s = ''
4496+ if x == 0:
4497+ return '0'
4498+ for s in ('', 'K', 'M', 'G', 'T'):
4499+ if x < 1024:
4500+ break
4501+ x /= 1024.0
4502+ return "%.1f%s" % (x, s)
4503+
4504+def fromunits(x):
4505+ s = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
4506+ M=2**20, MB=2**20, G=2**30, GB=2**30,
4507+ T=2**40, TB=2**40)
4508+ for k,v in s.items():
4509+ if x.endswith(k):
4510+ return int(float(x[:-len(k)])*v)
4511+ sys.stderr.write("Memory size should be written with units, for example 1024M\n")
4512+ sys.exit(-1)
4513+
4514+def pidusername(pid):
4515+ return src.username(src.piduser(pid))
4516+
4517+def showamount(a, total):
4518+ if options.abbreviate:
4519+ return units(a * 1024)
4520+ elif options.percent:
4521+ return "%.2f%%" % (100.0 * a / total)
4522+ return a
4523+
4524+def filter(opt, arg, *sources):
4525+ if not opt:
4526+ return False
4527+
4528+ for f in sources:
4529+ if re.search(opt, f(arg)):
4530+ return False
4531+ return True
4532+
4533+def pidtotals(pid):
4534+ maps = pidmaps(pid)
4535+ t = dict(size=0, rss=0, pss=0, shared_clean=0, shared_dirty=0,
4536+ private_clean=0, private_dirty=0, referenced=0, swap=0)
4537+ for m in maps.iterkeys():
4538+ for k in t:
4539+ t[k] += maps[m].get(k, 0)
4540+
4541+ t['uss'] = t['private_clean'] + t['private_dirty']
4542+ t['maps'] = len(maps)
4543+
4544+ return t
4545+
4546+def processtotals(pids):
4547+ totals = {}
4548+ for pid in pids:
4549+ if (filter(options.processfilter, pid, src.pidname, src.pidcmd) or
4550+ filter(options.userfilter, pid, pidusername)):
4551+ continue
4552+ try:
4553+ p = pidtotals(pid)
4554+ if p['maps'] != 0:
4555+ totals[pid] = p
4556+ except:
4557+ continue
4558+ return totals
4559+
4560+def showpids():
4561+ p = src.pids()
4562+ pt = processtotals(p)
4563+
4564+ def showuser(p):
4565+ if options.numeric:
4566+ return src.piduser(p)
4567+ return pidusername(p)
4568+
4569+ fields = dict(
4570+ pid=('PID', lambda n: n, '% 5s', lambda x: len(pt),
4571+ 'process ID'),
4572+ user=('User', showuser, '%-8s', lambda x: len(dict.fromkeys(x)),
4573+ 'owner of process'),
4574+ name=('Name', src.pidname, '%s', None,
4575+ 'name of process'),
4576+ command=('Command', src.pidcmd, '%s', None,
4577+ 'process command line'),
4578+ maps=('Maps',lambda n: pt[n]['maps'], '% 5s', sum,
4579+ 'total number of mappings'),
4580+ swap=('Swap',lambda n: pt[n]['swap'], '% 8a', sum,
4581+ 'amount of swap space consumed (ignoring sharing)'),
4582+ uss=('USS', lambda n: pt[n]['uss'], '%a', sum,
4583+ 'unique set size'),
4584+ rss=('RSS', lambda n: pt[n]['rss'], '%a', sum,
4585+ 'resident set size (ignoring sharing)'),
4586+ pss=('PSS', lambda n: pt[n]['pss'], '%a', sum,
4587+ 'proportional set size (including sharing)'),
4588+ vss=('VSS', lambda n: pt[n]['size'], '%a', sum,
4589+ 'virtual set size (total virtual memory mapped)'),
4590+ )
4591+ columns = options.columns or 'pid user command swap uss pss rss'
4592+
4593+ showtable(pt.keys(), fields, columns.split(), options.sort or 'pss')
4594+
4595+def maptotals(pids):
4596+ totals = {}
4597+ for pid in pids:
4598+ if (filter(options.processfilter, pid, src.pidname, src.pidcmd) or
4599+ filter(options.userfilter, pid, pidusername)):
4600+ continue
4601+ try:
4602+ maps = pidmaps(pid)
4603+ seen = {}
4604+ for m in maps.iterkeys():
4605+ name = maps[m]['name']
4606+ if name not in totals:
4607+ t = dict(size=0, rss=0, pss=0, shared_clean=0,
4608+ shared_dirty=0, private_clean=0, count=0,
4609+ private_dirty=0, referenced=0, swap=0, pids=0)
4610+ else:
4611+ t = totals[name]
4612+
4613+ for k in t:
4614+ t[k] += maps[m].get(k, 0)
4615+ t['count'] += 1
4616+ if name not in seen:
4617+ t['pids'] += 1
4618+ seen[name] = 1
4619+ totals[name] = t
4620+ except EnvironmentError:
4621+ continue
4622+ return totals
4623+
4624+def showmaps():
4625+ p = src.pids()
4626+ pt = maptotals(p)
4627+
4628+ fields = dict(
4629+ map=('Map', lambda n: n, '%-40.40s', len,
4630+ 'mapping name'),
4631+ count=('Count', lambda n: pt[n]['count'], '% 5s', sum,
4632+ 'number of mappings found'),
4633+ pids=('PIDs', lambda n: pt[n]['pids'], '% 5s', sum,
4634+ 'number of PIDs using mapping'),
4635+ swap=('Swap',lambda n: pt[n]['swap'], '% 8a', sum,
4636+ 'amount of swap space consumed (ignoring sharing)'),
4637+ uss=('USS', lambda n: pt[n]['private_clean']
4638+ + pt[n]['private_dirty'], '% 8a', sum,
4639+ 'unique set size'),
4640+ rss=('RSS', lambda n: pt[n]['rss'], '% 8a', sum,
4641+ 'resident set size (ignoring sharing)'),
4642+ pss=('PSS', lambda n: pt[n]['pss'], '% 8a', sum,
4643+ 'proportional set size (including sharing)'),
4644+ vss=('VSS', lambda n: pt[n]['size'], '% 8a', sum,
4645+ 'virtual set size (total virtual address space mapped)'),
4646+ avgpss=('AVGPSS', lambda n: int(1.0 * pt[n]['pss']/pt[n]['pids']),
4647+ '% 8a', sum,
4648+ 'average PSS per PID'),
4649+ avguss=('AVGUSS', lambda n: int(1.0 * pt[n]['uss']/pt[n]['pids']),
4650+ '% 8a', sum,
4651+ 'average USS per PID'),
4652+ avgrss=('AVGRSS', lambda n: int(1.0 * pt[n]['rss']/pt[n]['pids']),
4653+ '% 8a', sum,
4654+ 'average RSS per PID'),
4655+ )
4656+ columns = options.columns or 'map pids avgpss pss'
4657+
4658+ showtable(pt.keys(), fields, columns.split(), options.sort or 'pss')
4659+
4660+def usertotals(pids):
4661+ totals = {}
4662+ for pid in pids:
4663+ if (filter(options.processfilter, pid, src.pidname, src.pidcmd) or
4664+ filter(options.userfilter, pid, pidusername)):
4665+ continue
4666+ try:
4667+ maps = pidmaps(pid)
4668+ if len(maps) == 0:
4669+ continue
4670+ except EnvironmentError:
4671+ continue
4672+ user = src.piduser(pid)
4673+ if user not in totals:
4674+ t = dict(size=0, rss=0, pss=0, shared_clean=0,
4675+ shared_dirty=0, private_clean=0, count=0,
4676+ private_dirty=0, referenced=0, swap=0)
4677+ else:
4678+ t = totals[user]
4679+
4680+ for m in maps.iterkeys():
4681+ for k in t:
4682+ t[k] += maps[m].get(k, 0)
4683+
4684+ t['count'] += 1
4685+ totals[user] = t
4686+ return totals
4687+
4688+def showusers():
4689+ p = src.pids()
4690+ pt = usertotals(p)
4691+
4692+ def showuser(u):
4693+ if options.numeric:
4694+ return u
4695+ return src.username(u)
4696+
4697+ fields = dict(
4698+ user=('User', showuser, '%-8s', None,
4699+ 'user name or ID'),
4700+ count=('Count', lambda n: pt[n]['count'], '% 5s', sum,
4701+ 'number of processes'),
4702+ swap=('Swap',lambda n: pt[n]['swap'], '% 8a', sum,
4703+ 'amount of swapspace consumed (ignoring sharing)'),
4704+ uss=('USS', lambda n: pt[n]['private_clean']
4705+ + pt[n]['private_dirty'], '% 8a', sum,
4706+ 'unique set size'),
4707+ rss=('RSS', lambda n: pt[n]['rss'], '% 8a', sum,
4708+ 'resident set size (ignoring sharing)'),
4709+ pss=('PSS', lambda n: pt[n]['pss'], '% 8a', sum,
4710+ 'proportional set size (including sharing)'),
4711+ vss=('VSS', lambda n: pt[n]['pss'], '% 8a', sum,
4712+ 'virtual set size (total virtual memory mapped)'),
4713+ )
4714+ columns = options.columns or 'user count swap uss pss rss'
4715+
4716+ showtable(pt.keys(), fields, columns.split(), options.sort or 'pss')
4717+
4718+def showsystem():
4719+ t = totalmem()
4720+ ki = kernelsize()
4721+ m = memory()
4722+
4723+ mt = m['memtotal']
4724+ f = m['memfree']
4725+
4726+ # total amount used by hardware
4727+ fh = max(t - mt - ki, 0)
4728+
4729+ # total amount mapped into userspace (ie mapped an unmapped pages)
4730+ u = m['anonpages'] + m['mapped']
4731+
4732+ # total amount allocated by kernel not for userspace
4733+ kd = mt - f - u
4734+
4735+ # total amount in kernel caches
4736+ kdc = m['buffers'] + m['sreclaimable'] + (m['cached'] - m['mapped'])
4737+
4738+ l = [("firmware/hardware", fh, 0),
4739+ ("kernel image", ki, 0),
4740+ ("kernel dynamic memory", kd, kdc),
4741+ ("userspace memory", u, m['mapped']),
4742+ ("free memory", f, f)]
4743+
4744+ fields = dict(
4745+ order=('Order', lambda n: n, '% 1s', lambda x: '',
4746+ 'hierarchical order'),
4747+ area=('Area', lambda n: l[n][0], '%-24s', lambda x: '',
4748+ 'memory area'),
4749+ used=('Used', lambda n: l[n][1], '%10a', sum,
4750+ 'area in use'),
4751+ cache=('Cache', lambda n: l[n][2], '%10a', sum,
4752+ 'area used as reclaimable cache'),
4753+ noncache=('Noncache', lambda n: l[n][1] - l[n][2], '%10a', sum,
4754+ 'area not reclaimable'))
4755+
4756+ columns = options.columns or 'area used cache noncache'
4757+ showtable(range(len(l)), fields, columns.split(), options.sort or 'order')
4758+
4759+def showfields(fields, f):
4760+ if f != list:
4761+ print "unknown field", f
4762+ print "known fields:"
4763+ for l in sorted(fields.keys()):
4764+ print "%-8s %s" % (l, fields[l][-1])
4765+
4766+def showtable(rows, fields, columns, sort):
4767+ header = ""
4768+ format = ""
4769+ formatter = []
4770+
4771+ if sort not in fields:
4772+ showfields(fields, sort)
4773+ sys.exit(-1)
4774+
4775+ if options.pie:
4776+ columns.append(options.pie)
4777+ if options.bar:
4778+ columns.append(options.bar)
4779+
4780+ mt = totalmem()
4781+ st = memory()['swaptotal']
4782+
4783+ for n in columns:
4784+ if n not in fields:
4785+ showfields(fields, n)
4786+ sys.exit(-1)
4787+
4788+ f = fields[n][2]
4789+ if 'a' in f:
4790+ if n == 'swap':
4791+ formatter.append(lambda x: showamount(x, st))
4792+ else:
4793+ formatter.append(lambda x: showamount(x, mt))
4794+ f = f.replace('a', 's')
4795+ else:
4796+ formatter.append(lambda x: x)
4797+ format += f + "\t"
4798+ header += f % fields[n][0] + "\t"
4799+
4800+ l = []
4801+ for n in rows:
4802+ r = [fields[c][1](n) for c in columns]
4803+ l.append((fields[sort][1](n), r))
4804+
4805+ l.sort(reverse=bool(options.reverse))
4806+
4807+ if options.pie:
4808+ showpie(l, sort)
4809+ return
4810+ elif options.bar:
4811+ showbar(l, columns, sort)
4812+ return
4813+
4814+ if not options.no_header:
4815+ print header
4816+
4817+ for k,r in l:
4818+ print format % tuple([f(v) for f,v in zip(formatter, r)])
4819+
4820+ if options.totals:
4821+ # totals
4822+ t = []
4823+ for c in columns:
4824+ f = fields[c][3]
4825+ if f:
4826+ t.append(f([fields[c][1](n) for n in rows]))
4827+ else:
4828+ t.append("")
4829+
4830+ print "-" * len(header)
4831+ print format % tuple([f(v) for f,v in zip(formatter, t)])
4832+
4833+def showpie(l, sort):
4834+ try:
4835+ import pylab
4836+ except ImportError:
4837+ sys.stderr.write("pie chart requires matplotlib\n")
4838+ sys.exit(-1)
4839+
4840+ if (l[0][0] < l[-1][0]):
4841+ l.reverse()
4842+
4843+ labels = [r[1][-1] for r in l]
4844+ values = [r[0] for r in l] # sort field
4845+
4846+ tm = totalmem()
4847+ s = sum(values)
4848+ unused = tm - s
4849+ t = 0
4850+ while values and (t + values[-1] < (tm * .02) or
4851+ values[-1] < (tm * .005)):
4852+ t += values.pop()
4853+ labels.pop()
4854+
4855+ if t:
4856+ values.append(t)
4857+ labels.append('other')
4858+
4859+ explode = [0] * len(values)
4860+ if unused > 0:
4861+ values.insert(0, unused)
4862+ labels.insert(0, 'unused')
4863+ explode.insert(0, .05)
4864+
4865+ pylab.figure(1, figsize=(6,6))
4866+ ax = pylab.axes([0.1, 0.1, 0.8, 0.8])
4867+ pylab.pie(values, explode = explode, labels=labels,
4868+ autopct="%.2f%%", shadow=True)
4869+ pylab.title('%s by %s' % (options.pie, sort))
4870+ pylab.show()
4871+
4872+def showbar(l, columns, sort):
4873+ try:
4874+ import pylab, numpy
4875+ except ImportError:
4876+ sys.stderr.write("bar chart requires matplotlib\n")
4877+ sys.exit(-1)
4878+
4879+ if (l[0][0] < l[-1][0]):
4880+ l.reverse()
4881+
4882+ rc = []
4883+ key = []
4884+ for n in range(len(columns) - 1):
4885+ try:
4886+ if columns[n] in 'pid user group'.split():
4887+ continue
4888+ float(l[0][1][n])
4889+ rc.append(n)
4890+ key.append(columns[n])
4891+ except:
4892+ pass
4893+
4894+ width = 1.0 / (len(rc) + 1)
4895+ offset = width / 2
4896+
4897+ def gc(n):
4898+ return 'bgrcmyw'[n % 7]
4899+
4900+ pl = []
4901+ ind = numpy.arange(len(l))
4902+ for n in xrange(len(rc)):
4903+ pl.append(pylab.bar(ind + offset + width * n,
4904+ [x[1][rc[n]] for x in l], width, color=gc(n)))
4905+
4906+ #plt.xticks(ind + .5, )
4907+ pylab.gca().set_xticks(ind + .5)
4908+ pylab.gca().set_xticklabels([x[1][-1] for x in l], rotation=45)
4909+ pylab.legend([p[0] for p in pl], key)
4910+ pylab.show()
4911+
4912+
4913+parser = optparse.OptionParser("%prog [options]")
4914+parser.add_option("-H", "--no-header", action="store_true",
4915+ help="disable header line")
4916+parser.add_option("-c", "--columns", type="str",
4917+ help="columns to show")
4918+parser.add_option("-t", "--totals", action="store_true",
4919+ help="show totals")
4920+
4921+parser.add_option("-R", "--realmem", type="str",
4922+ help="amount of physical RAM")
4923+parser.add_option("-K", "--kernel", type="str",
4924+ help="path to kernel image")
4925+
4926+parser.add_option("-m", "--mappings", action="store_true",
4927+ help="show mappings")
4928+parser.add_option("-u", "--users", action="store_true",
4929+ help="show users")
4930+parser.add_option("-w", "--system", action="store_true",
4931+ help="show whole system")
4932+
4933+parser.add_option("-P", "--processfilter", type="str",
4934+ help="process filter regex")
4935+parser.add_option("-M", "--mapfilter", type="str",
4936+ help="map filter regex")
4937+parser.add_option("-U", "--userfilter", type="str",
4938+ help="user filter regex")
4939+
4940+parser.add_option("-n", "--numeric", action="store_true",
4941+ help="numeric output")
4942+parser.add_option("-s", "--sort", type="str",
4943+ help="field to sort on")
4944+parser.add_option("-r", "--reverse", action="store_true",
4945+ help="reverse sort")
4946+
4947+parser.add_option("-p", "--percent", action="store_true",
4948+ help="show percentage")
4949+parser.add_option("-k", "--abbreviate", action="store_true",
4950+ help="show unit suffixes")
4951+
4952+parser.add_option("", "--pie", type='str',
4953+ help="show pie graph")
4954+parser.add_option("", "--bar", type='str',
4955+ help="show bar graph")
4956+
4957+parser.add_option("-S", "--source", type="str",
4958+ help="/proc data source")
4959+
4960+
4961+defaults = {}
4962+parser.set_defaults(**defaults)
4963+(options, args) = parser.parse_args()
4964+
4965+try:
4966+ src = tardata(options.source)
4967+except:
4968+ src = procdata(options.source)
4969+
4970+try:
4971+ if options.mappings:
4972+ showmaps()
4973+ elif options.users:
4974+ showusers()
4975+ elif options.system:
4976+ showsystem()
4977+ else:
4978+ showpids()
4979+except IOError, e:
4980+ if e.errno == errno.EPIPE:
4981+ pass
4982+except KeyboardInterrupt:
4983+ pass
4984
4985=== added directory 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests'
4986=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/__init__.py'
4987--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/__init__.py 1970-01-01 00:00:00 +0000
4988+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/__init__.py 2014-02-13 16:08:19 +0000
4989@@ -0,0 +1,1 @@
4990+"""Event based memory usage measurements test cases."""
4991
4992=== added file 'tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/test_memory_usage.py'
4993--- tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/test_memory_usage.py 1970-01-01 00:00:00 +0000
4994+++ tests/memevent/ubuntu_test_cases/memory_usage_measurement/tests/test_memory_usage.py 2014-02-13 16:08:19 +0000
4995@@ -0,0 +1,79 @@
4996+"""Measure touch applications memory usage."""
4997+
4998+import json
4999+import logging
5000+
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches