~pwlars/ubuntu-test-cases/krillin-recovery

« back to all changes in this revision

Viewing changes to scripts/run-smoke

  • Committer: Paul Larson
  • Date: 2014-10-31 03:04:55 UTC
  • Revision ID: paul.larson@canonical.com-20141031030455-12euilmxkqb3g0b1
Convert the devel-proposed jobs to run as vivid

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
import argparse
 
4
import datetime
 
5
import logging
 
6
import os
 
7
import shutil
 
8
import subprocess
 
9
 
 
10
import yaml
 
11
 
 
12
from phabletutils.environment import detect_device
 
13
 
 
14
import dashboard
 
15
import statsd
 
16
 
 
17
EMULATOR = os.environ.get('USE_EMULATOR')
 
18
if EMULATOR:
 
19
    def fake_detect(serial, device=None):
 
20
        log.info('faking detect device for emulator')
 
21
        return 'emulator'
 
22
 
 
23
    # detect_device doesn't support the emulator
 
24
    globals()['detect_device'] = fake_detect
 
25
    if 'ANDROID_SERIAL' not in os.environ:
 
26
        # we need something here or "serial required" logic fails
 
27
        os.environ['ANDROID_SERIAL'] = 'emulator-5554'
 
28
 
 
29
log = logging.getLogger()
 
30
script_dir = os.path.dirname(__file__)
 
31
res_dir = os.path.join(os.getcwd(), 'clientlogs')
 
32
 
 
33
dashboard_api = dashboard.API()
 
34
 
 
35
 
 
36
class SerialAction(argparse.Action):
 
37
    def __call__(self, parser, namespace, values, option_string=None):
 
38
        log.info('android serial: %s', values[0])
 
39
        os.environ['ANDROID_SERIAL'] = values[0]
 
40
 
 
41
 
 
42
class DebugAction(argparse.Action):
 
43
    def __call__(self, parser, namespace, values, option_string=None):
 
44
        log.setLevel(level=logging.DEBUG)
 
45
        log.debug('debug logging enabled')
 
46
 
 
47
 
 
48
def _serial_required():
 
49
    required = 'ANDROID_SERIAL' not in os.environ
 
50
    if required:
 
51
        try:
 
52
            out = subprocess.check_output(['adb', 'devices'])
 
53
            required = (len(out.decode().split('\n')) != 4)
 
54
        except subprocess.CalledProcessError as e:
 
55
            logging.debug('error getting adb devices: %s', e)
 
56
    return required
 
57
 
 
58
 
 
59
def _get_parser():
 
60
    parser = argparse.ArgumentParser(
 
61
        description='Run the complete test-execution-service suite.')
 
62
 
 
63
    parser.add_argument('-s', '--serial', action=SerialAction, nargs=1,
 
64
                        required=_serial_required(),
 
65
                        help='Android serial if more than one device present')
 
66
    parser.add_argument('--debug', action=DebugAction, nargs=0,
 
67
                        help='''Enable debug logging.''')
 
68
 
 
69
    parser.add_argument('--install-url',
 
70
                        help='''Flash with image from previous jenkins job.
 
71
                        This option will check if the device already has image
 
72
                        noted from this URL and will skip provisioning. The URL
 
73
                        should be the path the job like:
 
74
                        http://q-jenkins:8080/job/<your job>/<build number>''')
 
75
    parser.add_argument('-p', '--package', action='append',
 
76
                        help='Additional packages to install on target.')
 
77
    parser.add_argument('-P', '--ppa', action='append',
 
78
                        help='Additional PPA to configure on target.')
 
79
    parser.add_argument('-a', '--app', action='append',
 
80
                        help='Autopilot tests tor run.')
 
81
    parser.add_argument('-t', '--test', action='append',
 
82
                        help='UTAH tests tor run.')
 
83
    parser.add_argument('-r', '--revision', help='Image revision to install.')
 
84
    parser.add_argument('-n', '--no-provision', action='store_true',
 
85
                        help='Skip provisioning of the target device')
 
86
    parser.add_argument('--hooks-dir',
 
87
                        help='''A directory containing scripts to be run after
 
88
                        the target has been provisioned and before testing.''')
 
89
    parser.add_argument('--image-opt',
 
90
                        help='Options to pass to phablet-flash')
 
91
    parser.add_argument('--image-type', default='touch',
 
92
                        help='''Image type being tested. This can be changed
 
93
                        to 'touch_sf4p' so that SurfaceFlinger will be used
 
94
                        instead of Mir. default=%(default)s''')
 
95
    parser.add_argument('--num-workers', type=int, default=1,
 
96
                        help='''The total number of workers available for
 
97
                        running tests.''')
 
98
    parser.add_argument('--worker-idx', type=int, default=0,
 
99
                        help='The worker to allocate testing work to.')
 
100
    return parser
 
101
 
 
102
 
 
103
def _arg_from_env(args, attr, envkey, array):
 
104
    val = os.environ.get(envkey, False)
 
105
    if val:
 
106
        if array:
 
107
            setattr(args, attr, val.split())
 
108
        else:
 
109
            setattr(args, attr, val)
 
110
        del os.environ[envkey]
 
111
 
 
112
 
 
113
def _merge_env(args):
 
114
    '''When run in Jenkins everything comes as environment variables.
 
115
 
 
116
    Its makes a much simpler job this way. While command line args are
 
117
    much easier for a user.
 
118
    '''
 
119
    _arg_from_env(args, 'app', 'APPS', True)
 
120
    _arg_from_env(args, 'test', 'TESTS', True)
 
121
    _arg_from_env(args, 'package', 'PACKAGES', True)
 
122
    _arg_from_env(args, 'ppa', 'PPAS', True)
 
123
    _arg_from_env(args, 'image_opt', 'IMAGE_OPT', False)
 
124
    _arg_from_env(args, 'image_type', 'IMAGE_TYPE', False)
 
125
    _arg_from_env(args, 'install_url', 'INSTALL_URL', False)
 
126
    _arg_from_env(args, 'revision', 'REVISION', False)
 
127
    _arg_from_env(args, 'num_workers', 'workers', False)
 
128
    _arg_from_env(args, 'worker_idx', 'worker_idx', False)
 
129
 
 
130
 
 
131
def _assert_args(args):
 
132
    if args.install_url:
 
133
        # this means you shouldn't specify packages, ppas, or image options
 
134
        if args.package or args.ppa or args.image_opt:
 
135
            msg = 'ERROR: --install-url can\'t be used with ' \
 
136
                  '--package, -ppa, or --image_opt'
 
137
            print(msg)
 
138
            return False
 
139
 
 
140
    # don't bother the install_url check, a user might be copy/pasting and
 
141
    # doesn't hurt. Its just good to not encourage it.
 
142
    _merge_env(args)
 
143
 
 
144
    script = os.path.join(script_dir, '../jenkins/testconfig.py')
 
145
    if args.package and args.package[0] == 'ALL':
 
146
        logging.info('Discovering all required dependencies')
 
147
        out = subprocess.check_output(
 
148
            [script, 'packages', '-i', args.image_type])
 
149
        args.package = [x for x in out.decode().split()]
 
150
 
 
151
    if args.app and args.app[0] == 'ALL':
 
152
        logging.info('Discovering all autopilot tests')
 
153
        out = subprocess.check_output(
 
154
            [script, 'apps', '-i', args.image_type,
 
155
             '-t', str(args.num_workers), '-w', str(args.worker_idx)])
 
156
        args.app = [x for x in out.decode().split()]
 
157
        logging.info('Autopilot test list: {}'.format(' '.join(args.app)))
 
158
 
 
159
    if args.test and args.test[0].startswith('ALL'):
 
160
        logging.info('Discovering all UTAH tests')
 
161
        argv = [script, 'utah', '-i', args.image_type,
 
162
                '-t', str(args.num_workers), '-w', str(args.worker_idx)]
 
163
        if args.test[0] == 'ALL_INCLUDING_AUTOPILOT':
 
164
            argv.append('-a')
 
165
        out = subprocess.check_output(argv)
 
166
        args.test = [x for x in out.decode().split()]
 
167
        logging.info('Utah test list: {}'.format(' '.join(args.test)))
 
168
 
 
169
    logging.debug('ARGS: %r', args)
 
170
 
 
171
    statsd.gauge_it('PACKAGES', args.package)
 
172
    statsd.gauge_it('APPS', args.app)
 
173
    statsd.gauge_it('TESTS', args.test)
 
174
 
 
175
    return True
 
176
 
 
177
 
 
178
def _run(args, ignore_error=False):
 
179
    try:
 
180
        logging.info('Running: %s', ' '.join(args))
 
181
        subprocess.check_call(args)
 
182
    except subprocess.CalledProcessError:
 
183
        if ignore_error:
 
184
            logging.error('failed to run %r, continuing', args)
 
185
        else:
 
186
            exit(1)
 
187
 
 
188
 
 
189
def _image_info():
 
190
    info = subprocess.check_output(['adb', 'shell', 'sudo',
 
191
                                    'system-image-cli', '-i'])
 
192
    v_ver = u_ver = d_ver = channel = None
 
193
    for line in info.split('\n'):
 
194
        if not line.strip():
 
195
            continue
 
196
        key, val = line.split(':', 1)
 
197
        if key == 'version version':
 
198
            v_ver = val.strip()
 
199
        elif key == 'version ubuntu':
 
200
            u_ver = val.strip()
 
201
        elif key == 'version device':
 
202
            d_ver = val.strip()
 
203
        elif key == 'channel':
 
204
            channel = val.strip()
 
205
    ver = '%s:%s:%s' % (v_ver, u_ver, d_ver)
 
206
    # required for the jenkins job's build description
 
207
    print('= TOUCH IMAGE VERSION:' + ver)
 
208
    return ver, channel
 
209
 
 
210
 
 
211
def _assert_image(args):
 
212
    log.info('checking if device has proper image ...')
 
213
    os.environ['INSTALL_URL'] = args.install_url
 
214
    _run([os.path.join(script_dir, 'assert-image')])
 
215
 
 
216
 
 
217
def _write_utah(start, end, passed):
 
218
    passes = failures = rc = 0
 
219
    if passed:
 
220
        passes = 1
 
221
    else:
 
222
        rc = failures = 1
 
223
 
 
224
    delta = '%s' % (end - start)
 
225
    start = start.strftime('%Y-%m-%d %H:%M:%S')
 
226
    data = {
 
227
        'name': 'install-and-boot',
 
228
        'errors': 0,
 
229
        'failures': failures,
 
230
        'passes': passes,
 
231
        'fetch_errors': 0,
 
232
        'uname': 'n/a',
 
233
        'media-info': 'n/a',
 
234
        'install_type': 'n/a',
 
235
        'arch': 'n/a',
 
236
        'release': 'n/a',
 
237
        'build_number': 'n/a',
 
238
        'runlist': 'n/a',
 
239
        'ran_at': start,
 
240
        'commands': [{
 
241
            'cmd_type': 'testcase_test',
 
242
            'command': 'provision',
 
243
            'returncode': rc,
 
244
            'start_time': start,
 
245
            'time_delta': delta,
 
246
            'stderr': '',
 
247
            'stdout': '',
 
248
            'testcase': 'boot',
 
249
            'testsuite': 'install-and-boot',
 
250
        }]
 
251
    }
 
252
    path = os.path.join(res_dir, 'install-and-boot')
 
253
    if not os.path.exists(path):
 
254
        os.mkdir(path)
 
255
    with open(os.path.join(path, 'utah.yaml'), 'w') as f:
 
256
        f.write(yaml.safe_dump(data, default_flow_style=False))
 
257
 
 
258
 
 
259
def _post_install_hooks(args):
 
260
    if not args.hooks_dir:
 
261
        return
 
262
    log.info('running post install hooks ...')
 
263
    if not os.path.isdir(args.hooks_dir):
 
264
        log.warn('hooks directory (%s) does not exist ... skipping',
 
265
                 args.hooks_dir)
 
266
    for hook in sorted(os.listdir(args.hooks_dir)):
 
267
        s = os.stat(os.path.join(args.hooks_dir, hook))
 
268
        if s.st_mode & os.path.stat.S_IXUSR == 0:
 
269
            log.warn('skipping hook (%s) - not executable', hook)
 
270
            continue
 
271
        log.info('executing hook: %s', hook)
 
272
        hook = os.path.join(args.hooks_dir, hook)
 
273
        subprocess.check_call([hook])
 
274
 
 
275
 
 
276
def _provision(args):
 
277
    log.info('provisioning device ...')
 
278
    if args.image_opt:
 
279
        log.debug('overriding IMAGE_OPT with: %s', args.image_opt)
 
280
        os.environ['IMAGE_OPT'] = args.image_opt
 
281
 
 
282
    cargs = [os.path.join(script_dir, 'provision.sh'), '-i', args.image_type]
 
283
 
 
284
    if args.package:
 
285
        for p in args.package:
 
286
            cargs.extend(['-p', p])
 
287
    if args.ppa:
 
288
        for p in args.ppa:
 
289
            cargs.extend(['-P', p])
 
290
    if not args.ppa and not args.package:
 
291
        # All tests require a writeable system the -p and -P args
 
292
        # implicitly create a writable system. so we have to ensure here:
 
293
        cargs.append('-w')
 
294
    if args.revision:
 
295
        cargs.extend(['-r', args.revision])
 
296
 
 
297
    with statsd.time_it('provision'):
 
298
        start = datetime.datetime.utcnow()
 
299
        passed = False
 
300
        try:
 
301
            _run(cargs)
 
302
            _post_install_hooks(args)
 
303
            passed = True
 
304
        finally:
 
305
            end = datetime.datetime.utcnow()
 
306
            _write_utah(start, end, passed)
 
307
 
 
308
 
 
309
def _test_autopilot(args, build, image):
 
310
    if args.app:
 
311
        if build:
 
312
            os.environ['DASHBOARD_BUILD'] = build
 
313
        if image:
 
314
            os.environ['DASHBOARD_IMAGE'] = image
 
315
        cargs = [os.path.join(script_dir, 'run-autopilot-tests.sh')]
 
316
        for app in args.app:
 
317
            cargs.extend(['-a', app])
 
318
        with statsd.time_it('APPS'):
 
319
            _run(cargs)
 
320
 
 
321
 
 
322
def _sync_results(build, image, test, fname):
 
323
    with open(fname) as f:
 
324
        d = yaml.safe_load(f)
 
325
        dashboard_api.result_syncing(image, build, test, d)
 
326
 
 
327
 
 
328
def _test_utah(args, build, image):
 
329
    if args.test:
 
330
        cargs = [os.path.join(script_dir, 'jenkins.sh')]
 
331
        with statsd.time_it('TESTS'):
 
332
            for test in args.test:
 
333
                os.environ['RESDIR'] = os.path.join(res_dir, test)
 
334
                dashboard_api.result_running(image, build, test)
 
335
                _run(cargs + ['-a', test, '-p', '/tmp/results'],
 
336
                     ignore_error=True)
 
337
                fname = os.path.join(res_dir, test, 'utah.yaml')
 
338
                _sync_results(build, image, test, fname)
 
339
 
 
340
 
 
341
def _image_add(args):
 
342
    build_number, channel = _image_info()
 
343
    # get the release series (ex. trusty, utopic)
 
344
    # this is set in the job environment variable IMAGE_SERIES
 
345
    release = os.environ.get('IMAGE_SERIES')
 
346
    if release:
 
347
        return dashboard_api.image_add(build_number, release, args.image_type,
 
348
                                       detect_device(None), 'ubuntu')
 
349
 
 
350
 
 
351
def main(args):
 
352
    with statsd.time_it('main'):
 
353
        if os.path.exists(res_dir):
 
354
            logging.info('deleting old result directory: %s', res_dir)
 
355
            shutil.rmtree(res_dir)
 
356
        os.mkdir(res_dir)
 
357
 
 
358
        job_name = os.environ.get('JOB_NAME', '')
 
359
        job_number = os.environ.get('BUILD_NUMBER', '')
 
360
        build = dashboard_api.build_add(job_name, job_number)
 
361
 
 
362
        if args.no_provision:
 
363
            logging.info('Skipping the provisioning step as requested')
 
364
        elif args.install_url:
 
365
            _assert_image(args)
 
366
        else:
 
367
            _provision(args)
 
368
 
 
369
        # TODO - this should be incororated into provision and assert_image
 
370
        # so that the status is updated *before* flashing rather than after
 
371
        image = _image_add(args)
 
372
 
 
373
        if args.test:
 
374
            for x in args.test:
 
375
                dashboard_api.result_queue(image, build, x)
 
376
        if args.app:
 
377
            for x in args.app:
 
378
                dashboard_api.result_queue(image, build, x)
 
379
 
 
380
        _test_utah(args, build, image)
 
381
        _test_autopilot(args, build, image)
 
382
 
 
383
    return 0
 
384
 
 
385
 
 
386
if __name__ == '__main__':
 
387
    logging.basicConfig(level=logging.INFO)
 
388
    log.name = 'run-smoke'
 
389
    dashboard.log = logging.getLogger('dashboard')
 
390
 
 
391
    args = _get_parser().parse_args()
 
392
    if not _assert_args(args):
 
393
        exit(1)
 
394
 
 
395
    exit(main(args))