1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.commands.uiautomator;
18
19import android.os.Bundle;
20
21import com.android.commands.uiautomator.Launcher.Command;
22import com.android.uiautomator.testrunner.UiAutomatorTestRunner;
23
24import java.util.ArrayList;
25import java.util.List;
26
27/**
28 * Implementation of the runtest sub command
29 *
30 */
31public class RunTestCommand extends Command {
32
33    private static final String CLASS_PARAM = "class";
34    private static final String DEBUG_PARAM = "debug";
35    private static final String RUNNER_PARAM = "runner";
36    private static final String CLASS_SEPARATOR = ",";
37    private static final int ARG_OK = 0;
38    private static final int ARG_FAIL_INCOMPLETE_E = -1;
39    private static final int ARG_FAIL_INCOMPLETE_C = -2;
40    private static final int ARG_FAIL_NO_CLASS = -3;
41    private static final int ARG_FAIL_RUNNER = -4;
42    private static final int ARG_FAIL_UNSUPPORTED = -99;
43
44    private Bundle mParams = new Bundle();
45    private List<String> mTestClasses = new ArrayList<String>();
46    private boolean mDebug;
47    private String mRunner;
48
49    public RunTestCommand() {
50        super("runtest");
51    }
52
53    @Override
54    public void run(String[] args) {
55        int ret = parseArgs(args);
56        switch (ret) {
57            case ARG_FAIL_INCOMPLETE_C:
58                System.err.println("Incomplete '-c' parameter.");
59                System.exit(ARG_FAIL_INCOMPLETE_C);
60                break;
61            case ARG_FAIL_INCOMPLETE_E:
62                System.err.println("Incomplete '-e' parameter.");
63                System.exit(ARG_FAIL_INCOMPLETE_E);
64                break;
65            case ARG_FAIL_UNSUPPORTED:
66                System.err.println("Unsupported standalone parameter.");
67                System.exit(ARG_FAIL_UNSUPPORTED);
68                break;
69            default:
70                break;
71        }
72        if (mTestClasses.isEmpty()) {
73            System.err.println("Please specify at least one test class to run.");
74            System.exit(ARG_FAIL_NO_CLASS);
75        }
76        getRunner().run(mTestClasses, mParams, mDebug);
77    }
78
79    private int parseArgs(String[] args) {
80        // we are parsing for these parameters:
81        // -e <key> <value>
82        // key-value pairs
83        // special ones are:
84        // key is "class", parameter is passed onto JUnit as class name to run
85        // key is "debug", parameter will determine whether to wait for debugger
86        // to attach
87        // -c <class name>
88        // equivalent to -e class <class name>, i.e. passed onto JUnit
89        for (int i = 0; i < args.length; i++) {
90            if (args[i].equals("-e")) {
91                if (i + 2 < args.length) {
92                    String key = args[++i];
93                    String value = args[++i];
94                    if (CLASS_PARAM.equals(key)) {
95                        addTestClasses(value);
96                    } else if (DEBUG_PARAM.equals(key)) {
97                        mDebug = "true".equals(value) || "1".equals(value);
98                    } else if (RUNNER_PARAM.equals(key)) {
99                        mRunner = value;
100                    } else {
101                        mParams.putString(key, value);
102                    }
103                } else {
104                    return ARG_FAIL_INCOMPLETE_E;
105                }
106            } else if (args[i].equals("-c")) {
107                if (i + 1 < args.length) {
108                    addTestClasses(args[++i]);
109                } else {
110                    return ARG_FAIL_INCOMPLETE_C;
111                }
112            } else {
113                return ARG_FAIL_UNSUPPORTED;
114            }
115        }
116        return ARG_OK;
117    }
118
119    protected UiAutomatorTestRunner getRunner() {
120        if (mRunner == null) {
121            return new UiAutomatorTestRunner();
122        }
123        // use reflection to get the runner
124        Object o = null;
125        try {
126            Class<?> clazz = Class.forName(mRunner);
127            o = clazz.newInstance();
128        } catch (ClassNotFoundException cnfe) {
129            System.err.println("Cannot find runner: " + mRunner);
130            System.exit(ARG_FAIL_RUNNER);
131        } catch (InstantiationException ie) {
132            System.err.println("Cannot instantiate runner: " + mRunner);
133            System.exit(ARG_FAIL_RUNNER);
134        } catch (IllegalAccessException iae) {
135            System.err.println("Constructor of runner " + mRunner + " is not accessibile");
136            System.exit(ARG_FAIL_RUNNER);
137        }
138        try {
139            UiAutomatorTestRunner runner = (UiAutomatorTestRunner)o;
140            return runner;
141        } catch (ClassCastException cce) {
142            System.err.println("Specified runner is not subclass of "
143                    + UiAutomatorTestRunner.class.getSimpleName());
144            System.exit(ARG_FAIL_RUNNER);
145        }
146        // won't reach here
147        return null;
148    }
149
150    /**
151     * Add test classes from a potentially comma separated list
152     * @param classes
153     */
154    private void addTestClasses(String classes) {
155        String[] classArray = classes.split(CLASS_SEPARATOR);
156        for (String clazz : classArray) {
157            mTestClasses.add(clazz);
158        }
159    }
160
161    @Override
162    public String detailedOptions() {
163        return "    runtest <class spec> [options]\n"
164            + "    <class spec>: <JARS> [-c <CLASSES> | -e class <CLASSES>]\n"
165            + "      <JARS>: a list of jar file containing test classes and dependencies. If\n"
166            + "        the path is relative, it's assumed to be under /data/local/tmp. Use\n"
167            + "        absolute path if the file is elsewhere. Multiple files can be\n"
168            + "        specified, separated by space.\n"
169            + "      <CLASSES>: a list of test class names to run, separated by comma. To\n"
170            + "        a single method, use TestClass#testMethod format. The -e or -c option\n"
171            + "        may be repeated.\n"
172            + "    options:\n"
173            + "      --nohup: trap SIG_HUP, so test won't terminate even if parent process\n"
174            + "               is terminated, e.g. USB is disconnected.\n"
175            + "      -e debug [true|false]: wait for debugger to connect before starting.\n"
176            + "      -e runner [CLASS]: use specified test runner class instead. If\n"
177            + "        unspecified, framework default runner will be used.\n"
178            + "      -e <NAME> <VALUE>: other name-value pairs to be passed to test classes.\n"
179            + "        May be repeated.\n";
180    }
181
182    @Override
183    public String shortHelp() {
184        return "executes UI automation tests";
185    }
186
187}