1993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley#!/usr/bin/env python
2993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
3993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley"""
4993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyTest that aidl generates functional code by running it on an Android device.
5993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley"""
6993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
7993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyimport argparse
8993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyimport pipes
9993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyimport subprocess
10993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyimport shlex
11993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
12993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyJAVA_OUTPUT_READER = 'aidl_test_sentinel_searcher'
13993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyNATIVE_TEST_CLIENT = 'aidl_test_client'
14993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyNATIVE_TEST_SERVICE = 'aidl_test_service'
15993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
16993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyTEST_FILTER_ALL = 'all'
17993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyTEST_FILTER_JAVA = 'java'
18993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyTEST_FILTER_NATIVE = 'native'
19993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
20993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyJAVA_CLIENT_TIMEOUT_SECONDS = 30
21993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyJAVA_LOG_FILE = '/data/data/android.aidl.tests/files/test-client.log'
22993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyJAVA_SUCCESS_SENTINEL = '>>> Java Client Success <<<'
23993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher WileyJAVA_FAILURE_SENTINEL = '>>> Java Client Failure <<<'
24993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
25993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyclass TestFail(Exception):
26993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    """Raised on test failures."""
27993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    pass
28993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
29993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
30993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyclass ShellResult(object):
31993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    """Represents the result of running a shell command."""
32993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
33993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    def __init__(self, exit_status, stdout, stderr):
34993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """Construct an instance.
35993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
36993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Args:
37993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            exit_status: integer exit code of shell command
38993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            stdout: string stdout of shell command
39993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            stderr: string stderr of shell command
40993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """
41993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        self.stdout = stdout
42993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        self.stderr = stderr
43993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        self.exit_status = exit_status
44993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
45993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    def printable_string(self):
46993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """Get a string we could print to the logs and understand."""
47993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        output = []
48993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        output.append('stdout:')
49993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        for line in self.stdout.splitlines():
50993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            output.append('  > %s' % line)
51993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        output.append('stderr:')
52993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        for line in self.stderr.splitlines():
53993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            output.append('  > %s' % line)
54993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        return '\n'.join(output)
55993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
56993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
57993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyclass AdbHost(object):
58993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    """Represents a device connected via ADB."""
59993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
60993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    def __init__(self, device_serial=None, verbose=None):
61993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """Construct an instance.
62993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
63993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Args:
64993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            device_serial: options string serial number of attached device.
65993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            verbose: True iff we should print out ADB commands we run.
66993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """
67993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        self._device_serial = device_serial
68993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        self._verbose = verbose
69993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
70993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    def run(self, command, background=False, ignore_status=False):
71993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """Run a command on the device via adb shell.
72993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
73993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Args:
74993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            command: string containing a shell command to run.
75993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            background: True iff we should run this command in the background.
76993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            ignore_status: True iff we should ignore the command's exit code.
77993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
78993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Returns:
79993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            instance of ShellResult.
80993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
81993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Raises:
82993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            subprocess.CalledProcessError on command exit != 0.
83993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """
84993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        if background:
85993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            command = '( %s ) </dev/null >/dev/null 2>&1 &' % command
86993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        return self.adb('shell %s' % pipes.quote(command),
87993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                        ignore_status=ignore_status)
88993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
89993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    def mktemp(self):
90993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """Make a temp file on the device.
91993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
92993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Returns:
93993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            path to created file as a string
94993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
95993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Raises:
96993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            subprocess.CalledProcessError on failure.
97993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """
98993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        # Work around b/19635681
99993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        result = self.run('source /system/etc/mkshrc && mktemp')
100993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        return result.stdout.strip()
101993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
102993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    def adb(self, command, ignore_status=False):
103993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """Run an ADB command (e.g. `adb sync`).
104993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
105993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Args:
106993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            command: string containing command to run
107993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            ignore_status: True iff we should ignore the command's exit code.
108993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
109993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Returns:
110993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            instance of ShellResult.
111993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
112993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        Raises:
113993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            subprocess.CalledProcessError on command exit != 0.
114993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        """
115993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        command = 'adb %s' % command
116993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        if self._verbose:
117993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            print(command)
118993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        p = subprocess.Popen(command, shell=True, close_fds=True,
119993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
120993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                             universal_newlines=True)
121993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        stdout, stderr = p.communicate()
122993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        if not ignore_status and p.returncode:
123993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            raise subprocess.CalledProcessError(p.returncode, command)
124993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        return ShellResult(p.returncode, stdout, stderr)
125993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
126993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
127cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wileydef run_test(host, test_native, test_java):
128993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    """Body of the test.
129993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
130993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    Args:
131cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley        host: AdbHost object to run tests on
132993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        test_native: True iff we should test native Binder clients.
133993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        test_java: True iff we shoudl test Java Binder clients.
134993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    """
135993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
136993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    print('Starting aidl integration testing...')
137cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley
138993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    # Kill any previous test context
139e7c4ea5abb2224ad570de982b93568481af2f538Christopher Wiley    host.run('rm -f %s' % JAVA_LOG_FILE, ignore_status=True)
140993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    host.run('pkill %s' % NATIVE_TEST_SERVICE, ignore_status=True)
141993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
142993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    # Start up a native server
143993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    host.run(NATIVE_TEST_SERVICE, background=True)
144993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
145993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    # Start up clients
146993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    if test_native:
147d03d72656dca38e85d8187237f8369177b7b9262Christopher Wiley        host.run('pkill %s' % NATIVE_TEST_CLIENT, ignore_status=True)
148993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        result = host.run(NATIVE_TEST_CLIENT, ignore_status=True)
149993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        if result.exit_status:
150993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            print(result.printable_string())
151993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            raise TestFail('%s returned status code %d' %
152993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                           (NATIVE_TEST_CLIENT, result.exit_status))
153993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
154993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    if test_java:
155993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        host.run('am start -S -a android.intent.action.MAIN '
156993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                 '-n android.aidl.tests/.TestServiceClient '
157993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                 '--es sentinel.success "%s" '
158993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                 '--es sentinel.failure "%s"' %
159993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                 (JAVA_SUCCESS_SENTINEL, JAVA_FAILURE_SENTINEL))
160993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        result = host.run('%s %d %s "%s" "%s"' %
161993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                          (JAVA_OUTPUT_READER, JAVA_CLIENT_TIMEOUT_SECONDS,
162993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                           JAVA_LOG_FILE, JAVA_SUCCESS_SENTINEL,
163993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                           JAVA_FAILURE_SENTINEL),
164993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley                          ignore_status=True)
165993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley        if result.exit_status:
166993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            print(result.printable_string())
167993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            raise TestFail('Java client did not complete successfully.')
168993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
169993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    print('Success!')
170993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
171993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
172993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileydef main():
173993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    """Main entry point."""
174993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    parser = argparse.ArgumentParser(description=__doc__)
175993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    parser.add_argument(
176993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            '--test-filter', default=TEST_FILTER_ALL,
177993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley            choices=[TEST_FILTER_ALL, TEST_FILTER_JAVA, TEST_FILTER_NATIVE])
178993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    parser.add_argument('--verbose', '-v', action='store_true', default=False)
179993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    args = parser.parse_args()
180cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley    host = AdbHost(verbose=args.verbose)
181cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley    try:
182cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley        # Tragically, SELinux interferes with our testing
183cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley        host.run('setenforce 0')
184cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley        run_test(host,
185cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley                 args.test_filter in (TEST_FILTER_ALL, TEST_FILTER_NATIVE),
186cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley                 args.test_filter in (TEST_FILTER_ALL, TEST_FILTER_JAVA))
187cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley    finally:
188cce25d35d2ee5a80568eec4d504f65fa790473d4Christopher Wiley        host.run('setenforce 1')
189993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
190993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley
191993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wileyif __name__ == '__main__':
192993ca4ad6ca2b8ad6c3f9c46a06e934c19b95e8eChristopher Wiley    main()
193