xref: /aosp_15_r20/external/autotest/utils/emulator_manager.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li"""Utility to run a Brillo emulator programmatically.
5*9c5db199SXin Li
6*9c5db199SXin LiRequires system.img, userdata.img and kernel to be in imagedir. If running an
7*9c5db199SXin Liarm emulator kernel.dtb (or another dtb file) must also be in imagedir.
8*9c5db199SXin Li
9*9c5db199SXin LiWARNING: Processes created by this utility may not die unless
10*9c5db199SXin LiEmulatorManager.stop is called. Call EmulatorManager.verify_stop to
11*9c5db199SXin Liconfirm process has stopped and port is free.
12*9c5db199SXin Li"""
13*9c5db199SXin Li
14*9c5db199SXin Liimport os
15*9c5db199SXin Liimport time
16*9c5db199SXin Li
17*9c5db199SXin Liimport common
18*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
19*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
20*9c5db199SXin Li
21*9c5db199SXin Li
22*9c5db199SXin Liclass EmulatorManagerException(Exception):
23*9c5db199SXin Li    """Bad port, missing artifact or non-existant imagedir."""
24*9c5db199SXin Li    pass
25*9c5db199SXin Li
26*9c5db199SXin Li
27*9c5db199SXin Liclass EmulatorManager(object):
28*9c5db199SXin Li    """Manage an instance of a device emulator.
29*9c5db199SXin Li
30*9c5db199SXin Li    @param imagedir: directory of emulator images.
31*9c5db199SXin Li    @param port: Port number for emulator's adbd. Note this port is one higher
32*9c5db199SXin Li                 than the port in the emulator's serial number.
33*9c5db199SXin Li    @param run: Function used to execute shell commands.
34*9c5db199SXin Li    """
35*9c5db199SXin Li    def __init__(self, imagedir, port, run=utils.run):
36*9c5db199SXin Li        if not port % 2 or port < 5555 or port > 5585:
37*9c5db199SXin Li             raise EmulatorManagerException('Port must be an odd number '
38*9c5db199SXin Li                                            'between 5555 and 5585.')
39*9c5db199SXin Li        try:
40*9c5db199SXin Li            run('test -f %s' % os.path.join(imagedir, 'system.img'))
41*9c5db199SXin Li        except error.GenericHostRunError:
42*9c5db199SXin Li            raise EmulatorManagerException('Image directory must exist and '
43*9c5db199SXin Li                                           'contain emulator images.')
44*9c5db199SXin Li
45*9c5db199SXin Li        self.port = port
46*9c5db199SXin Li        self.imagedir = imagedir
47*9c5db199SXin Li        self.run = run
48*9c5db199SXin Li
49*9c5db199SXin Li
50*9c5db199SXin Li    def verify_stop(self, timeout_secs=3):
51*9c5db199SXin Li        """Wait for emulator on our port to stop.
52*9c5db199SXin Li
53*9c5db199SXin Li        @param timeout_secs: Max seconds to wait for the emulator to stop.
54*9c5db199SXin Li
55*9c5db199SXin Li        @return: Bool - True if emulator stops.
56*9c5db199SXin Li        """
57*9c5db199SXin Li        cycles = 0
58*9c5db199SXin Li        pid = self.find()
59*9c5db199SXin Li        while pid:
60*9c5db199SXin Li            cycles += 1
61*9c5db199SXin Li            time.sleep(0.1)
62*9c5db199SXin Li            pid = self.find()
63*9c5db199SXin Li            if cycles >= timeout_secs*10 and pid:
64*9c5db199SXin Li                return False
65*9c5db199SXin Li        return True
66*9c5db199SXin Li
67*9c5db199SXin Li
68*9c5db199SXin Li    def _find_dtb(self):
69*9c5db199SXin Li        """Detect a dtb file in the image directory
70*9c5db199SXin Li
71*9c5db199SXin Li        @return: Path to dtb file or None.
72*9c5db199SXin Li        """
73*9c5db199SXin Li        cmd_result = self.run('find "%s" -name "*.dtb"' % self.imagedir)
74*9c5db199SXin Li        dtb = cmd_result.stdout.split('\n')[0]
75*9c5db199SXin Li        return dtb.strip() or None
76*9c5db199SXin Li
77*9c5db199SXin Li
78*9c5db199SXin Li    def start(self):
79*9c5db199SXin Li        """Start an emulator with the images and port specified.
80*9c5db199SXin Li
81*9c5db199SXin Li        If an emulator is already running on the port it will be killed.
82*9c5db199SXin Li        """
83*9c5db199SXin Li        self.force_stop()
84*9c5db199SXin Li        time.sleep(1) # Wait for port to be free
85*9c5db199SXin Li        # TODO(jgiorgi): Add support for x86 / x64 emulators
86*9c5db199SXin Li        args = [
87*9c5db199SXin Li            '-dmS', 'emulator-%s' % self.port, 'qemu-system-arm',
88*9c5db199SXin Li            '-M', 'vexpress-a9',
89*9c5db199SXin Li            '-m', '1024M',
90*9c5db199SXin Li            '-kernel', os.path.join(self.imagedir, 'kernel'),
91*9c5db199SXin Li            '-append', ('"console=ttyAMA0 ro root=/dev/sda '
92*9c5db199SXin Li                        'androidboot.hardware=qemu qemu=1 rootwait noinitrd '
93*9c5db199SXin Li                        'init=/init androidboot.selinux=enforcing"'),
94*9c5db199SXin Li            '-nographic',
95*9c5db199SXin Li            '-device', 'virtio-scsi-device,id=scsi',
96*9c5db199SXin Li            '-device', 'scsi-hd,drive=system',
97*9c5db199SXin Li            '-drive', ('file="%s,if=none,id=system,format=raw"'
98*9c5db199SXin Li                       % os.path.join(self.imagedir, 'system.img')),
99*9c5db199SXin Li            '-device', 'scsi-hd,drive=userdata',
100*9c5db199SXin Li            '-drive', ('file="%s,if=none,id=userdata,format=raw"'
101*9c5db199SXin Li                       % os.path.join(self.imagedir, 'userdata.img')),
102*9c5db199SXin Li            '-redir', 'tcp:%s::5555' % self.port,
103*9c5db199SXin Li        ]
104*9c5db199SXin Li
105*9c5db199SXin Li        # DTB file produced and required for arm but not x86 emulators
106*9c5db199SXin Li        dtb = self._find_dtb()
107*9c5db199SXin Li        if dtb:
108*9c5db199SXin Li            args += ['-dtb', dtb]
109*9c5db199SXin Li        else:
110*9c5db199SXin Li            raise EmulatorManagerException('DTB file missing. Required for arm '
111*9c5db199SXin Li                                           'emulators.')
112*9c5db199SXin Li
113*9c5db199SXin Li        self.run(' '.join(['screen'] + args))
114*9c5db199SXin Li
115*9c5db199SXin Li
116*9c5db199SXin Li    def find(self):
117*9c5db199SXin Li        """Detect the PID of a qemu process running on our port.
118*9c5db199SXin Li
119*9c5db199SXin Li        @return: PID or None
120*9c5db199SXin Li        """
121*9c5db199SXin Li        running = self.run('netstat -nlpt').stdout
122*9c5db199SXin Li        for proc in running.split('\n'):
123*9c5db199SXin Li            if ':%s' % self.port in proc:
124*9c5db199SXin Li                process = proc.split()[-1]
125*9c5db199SXin Li                if '/' in process: # Program identified, we started and can kill
126*9c5db199SXin Li                    return process.split('/')[0]
127*9c5db199SXin Li
128*9c5db199SXin Li
129*9c5db199SXin Li    def stop(self, kill=False):
130*9c5db199SXin Li        """Send signal to stop emulator process.
131*9c5db199SXin Li
132*9c5db199SXin Li        Signal is sent to any running qemu process on our port regardless of how
133*9c5db199SXin Li        it was started. Silent no-op if no running qemu processes on the port.
134*9c5db199SXin Li
135*9c5db199SXin Li        @param kill: Send SIGKILL signal instead of SIGTERM.
136*9c5db199SXin Li        """
137*9c5db199SXin Li        pid = self.find()
138*9c5db199SXin Li        if pid:
139*9c5db199SXin Li            cmd = 'kill -9 %s' if kill else 'kill %s'
140*9c5db199SXin Li            self.run(cmd % pid)
141*9c5db199SXin Li
142*9c5db199SXin Li
143*9c5db199SXin Li    def force_stop(self):
144*9c5db199SXin Li        """Attempt graceful shutdown, kill if not dead after 3 seconds.
145*9c5db199SXin Li        """
146*9c5db199SXin Li        self.stop()
147*9c5db199SXin Li        if not self.verify_stop(timeout_secs=3):
148*9c5db199SXin Li            self.stop(kill=True)
149*9c5db199SXin Li        if not self.verify_stop():
150*9c5db199SXin Li            raise RuntimeError('Emulator running on port %s failed to stop.'
151*9c5db199SXin Li                               % self.port)
152*9c5db199SXin Li
153