~thomir-deactivatedaccount/autopilot/trunk-fix-eventually-matcher-docs

« back to all changes in this revision

Viewing changes to autopilot/testcase.py

Refactor of the application launching code (incl. tests).

Approved by PS Jenkins bot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
 
51
51
import logging
52
52
import os
53
 
import psutil
54
 
import signal
55
 
import subprocess
56
53
 
57
54
from testscenarios import TestWithScenarios
58
55
from testtools import TestCase
59
 
from testtools.content import text_content, content_from_file
60
56
from testtools.matchers import Equals
61
57
 
 
58
from autopilot.application import (
 
59
    ClickApplicationLauncher,
 
60
    get_application_launcher_wrapper,
 
61
    NormalApplicationLauncher,
 
62
)
62
63
from autopilot.process import ProcessManager
63
64
from autopilot.input import Keyboard, Mouse
64
65
from autopilot.introspection import (
65
 
    get_application_launcher,
66
 
    get_application_launcher_from_string_hint,
67
 
    get_autopilot_proxy_object_for_process,
68
66
    get_proxy_object_for_existing_process,
69
 
    launch_application,
70
67
)
71
 
from autopilot.introspection.utilities import _get_click_app_id
72
68
from autopilot.display import Display
73
69
from autopilot.utilities import on_test_started, sleep
74
70
from autopilot.keybindings import KeybindingsHelper
252
248
         data is retrievable via this object.
253
249
 
254
250
        """
255
 
        app_path = subprocess.check_output(['which', application],
256
 
                                           universal_newlines=True).strip()
257
 
        # Get a launcher, tests can override this if they need:
258
 
        launcher_hint = kwargs.pop('app_type', '')
259
 
        launcher = None
260
 
        if launcher_hint != '':
261
 
            launcher = get_application_launcher_from_string_hint(launcher_hint)
262
 
        if launcher is None:
263
 
            try:
264
 
                launcher = self.pick_app_launcher(app_path)
265
 
            except RuntimeError:
266
 
                pass
267
 
        if launcher is None:
268
 
            raise RuntimeError(
269
 
                "Autopilot could not determine the correct introspection type "
270
 
                "to use. You can specify one by overriding the "
271
 
                "AutopilotTestCase.pick_app_launcher method.")
272
 
        emulator_base = kwargs.pop('emulator_base', None)
273
 
        dbus_bus = kwargs.pop('dbus_bus', 'session')
274
 
 
275
 
        if dbus_bus != 'session':
276
 
            self.patch_environment("DBUS_SESSION_BUS_ADDRESS", dbus_bus)
277
 
 
278
 
        process = launch_application(launcher, app_path, *arguments, **kwargs)
279
 
        self.addCleanup(self._kill_process_and_attach_logs, process)
280
 
        return get_autopilot_proxy_object_for_process(
281
 
            process,
282
 
            emulator_base,
283
 
            dbus_bus
 
251
        launcher = self.useFixture(
 
252
            NormalApplicationLauncher(self.addDetail, **kwargs)
284
253
        )
285
254
 
 
255
        return self._launch_test_application(launcher, application, *arguments)
 
256
 
286
257
    def launch_click_package(self, package_id, app_name=None, **kwargs):
287
258
        """Launch a click package application with introspection enabled.
288
259
 
312
283
            the specified click package.
313
284
 
314
285
        """
315
 
        app_id = _get_click_app_id(package_id, app_name)
316
 
        # sadly, we cannot re-use the existing launch_test_application
317
 
        # since upstart is a little odd.
318
 
        # set the qt testability env:
319
 
        subprocess.call([
320
 
            "/sbin/initctl",
321
 
            "set-env",
322
 
            "QT_LOAD_TESTABILITY=1",
323
 
        ])
324
 
        log_dir = os.path.expanduser('~/.cache/upstart/')
325
 
        log_name = 'application-click-{}.log'.format(app_id)
326
 
        log_path = os.path.join(log_dir, log_name)
327
 
        self.addCleanup(
328
 
            lambda: self.addDetail(
329
 
                "Application Log",
330
 
                content_from_file(log_path)
331
 
            )
332
 
        )
333
 
 
334
 
        # launch the application:
335
 
        subprocess.check_output([
336
 
            "/sbin/start",
337
 
            "application",
338
 
            "APP_ID={}".format(app_id),
339
 
        ])
340
 
        # perhaps we should do this with a regular expression instead?
341
 
        for i in range(10):
342
 
            try:
343
 
                list_output = subprocess.check_output([
344
 
                    "/sbin/initctl",
345
 
                    "status",
346
 
                    "application-click",
347
 
                    "APP_ID={}".format(app_id)
348
 
                ])
349
 
            except subprocess.CalledProcessError:
350
 
                # application not started yet.
351
 
                pass
352
 
            else:
353
 
                for line in list_output.split('\n'):
354
 
                    if app_id in line and "start/running" in line:
355
 
                        target_pid = int(line.split()[-1])
356
 
 
357
 
                        self.addCleanup(self._kill_pid, target_pid)
358
 
                        logger.info(
359
 
                            "Click package %s has been launched with PID %d",
360
 
                            app_id,
361
 
                            target_pid
362
 
                        )
363
 
 
364
 
                        emulator_base = kwargs.pop('emulator_base', None)
365
 
                        proxy = get_proxy_object_for_existing_process(
366
 
                            pid=target_pid,
367
 
                            emulator_base=emulator_base
368
 
                        )
369
 
                        # reset the upstart env, and hope no one else launched,
370
 
                        # or they'll have introspection enabled as well,
371
 
                        # although this isn't the worth thing in the world.
372
 
                        subprocess.call([
373
 
                            "/sbin/initctl",
374
 
                            "unset-env",
375
 
                            "QT_LOAD_TESTABILITY",
376
 
                        ])
377
 
                        return proxy
378
 
            # give the app time to launch - maybe this is not needed?:
379
 
            sleep(1)
380
 
        else:
381
 
            raise RuntimeError(
382
 
                "Could not find autopilot interface for click package"
383
 
                " '{}' after 10 seconds.".format(app_id)
384
 
            )
 
286
        launcher = self.useFixture(
 
287
            ClickApplicationLauncher(self.addDetail, **kwargs)
 
288
        )
 
289
        return self._launch_test_application(launcher, package_id, app_name)
 
290
 
 
291
    # Wrapper function tying the newer ApplicationLauncher behaviour with the
 
292
    # previous (to be depreciated) behaviour
 
293
    def _launch_test_application(self, launcher_instance, application, *args):
 
294
 
 
295
        dbus_bus = launcher_instance.dbus_bus
 
296
        if dbus_bus != 'session':
 
297
            self.patch_environment("DBUS_SESSION_BUS_ADDRESS", dbus_bus)
 
298
 
 
299
        pid = launcher_instance.launch(application, *args)
 
300
        process = getattr(launcher_instance, 'process', None)
 
301
 
 
302
        proxy_obj = get_proxy_object_for_existing_process(
 
303
            pid=pid,
 
304
            process=process,
 
305
            dbus_bus=dbus_bus,
 
306
            emulator_base=launcher_instance.emulator_base,
 
307
        )
 
308
        proxy_obj.set_process(process)
 
309
 
 
310
        return proxy_obj
385
311
 
386
312
    def _compare_system_with_app_snapshot(self):
387
313
        """Compare the currently running application with the last snapshot.
529
455
        own implemetnation.
530
456
 
531
457
        The default implementation calls
532
 
        :py:func:`autopilot.introspection.get_application_launcher`
 
458
        :py:func:`autopilot.application.get_application_launcher_wrapper`
533
459
 
534
460
        """
535
 
        # default implementation is in autopilot.introspection:
536
 
        return get_application_launcher(app_path)
537
 
 
538
 
    def _kill_pid(self, pid):
539
 
        """Kill the process with the specified pid."""
540
 
        logger.info("waiting for process to exit.")
541
 
        try:
542
 
            logger.info("Killing process %d", pid)
543
 
            os.killpg(pid, signal.SIGTERM)
544
 
        except OSError:
545
 
            logger.info("Appears process has already exited.")
546
 
        for i in range(10):
547
 
            if not _is_process_running(pid):
548
 
                break
549
 
            if i == 9:
550
 
                logger.info(
551
 
                    "Killing process group, since it hasn't exited after "
552
 
                    "10 seconds."
553
 
                )
554
 
                os.killpg(pid, signal.SIGKILL)
555
 
            sleep(1)
556
 
 
557
 
    def _kill_process(self, process):
558
 
        """Kill the process, and return the stdout, stderr and return code."""
559
 
        stdout = ""
560
 
        stderr = ""
561
 
        logger.info("waiting for process to exit.")
562
 
        try:
563
 
            logger.info("Killing process %d", process.pid)
564
 
            os.killpg(process.pid, signal.SIGTERM)
565
 
        except OSError:
566
 
            logger.info("Appears process has already exited.")
567
 
        for i in range(10):
568
 
            tmp_out, tmp_err = process.communicate()
569
 
            stdout += tmp_out
570
 
            stderr += tmp_err
571
 
            if not _is_process_running(process.pid):
572
 
                break
573
 
            if i == 9:
574
 
                logger.info(
575
 
                    "Killing process group, since it hasn't exited after "
576
 
                    "10 seconds."
577
 
                )
578
 
                os.killpg(process.pid, signal.SIGKILL)
579
 
            sleep(1)
580
 
        return stdout, stderr, process.returncode
581
 
 
582
 
    def _kill_process_and_attach_logs(self, process):
583
 
        stdout, stderr, return_code = self._kill_process(process)
584
 
        self.addDetail('process-return-code', text_content(str(return_code)))
585
 
        self.addDetail('process-stdout', text_content(stdout))
586
 
        self.addDetail('process-stderr', text_content(stderr))
587
 
 
588
 
 
589
 
def _is_process_running(pid):
590
 
    return psutil.pid_exists(pid)
 
461
        # default implementation is in autopilot.application:
 
462
        return get_application_launcher_wrapper(app_path)
591
463
 
592
464
 
593
465
def _get_process_snapshot():