• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--
2   Copyright 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
17# Tutorial
18
19This tutorial guides you through the construction of a "hello world" Trade Federation test
20configuration, and gives you a hands-on introduction to the Trade Federation framework.  Starting
21from the Tf development environment, it guides you through the process of creating a simple Trade
22Federation config and gradually adding more features to it.
23
24The tutorial presents the TF test development process as a set of exercises, each consisting of
25several steps.  The exercises demonstrate how to gradually build and refine your configuration, and
26provide all the sample code you need to complete the test configuration.
27
28When you are finished with the tutorial, you will have created a functioning TF configuration and
29will have learned many of the most important concepts in the TF framework.
30
31
32## Set up TradeFederation development environment
33
34See (FIXME: link) for how to setup the development environment. The rest of this tutorial assumes you have a shell open that has been initialized to the TradeFederation environment.
35
36For simplicity, this tutorial will illustrate adding a configuration and its classes to the TradeFederation framework core library. Later tutorials/documentation will show how to create your own library that extends TradeFederation.
37
38
39## Creating a test class
40
41Lets create a hello world test that just dumps a message to stdout. A TradeFederation test must
42implement the (FIXME: link) IRemoteTest interface.
43
44Here's an implementation for the HelloWorldTest:
45
46    package com.android.tradefed.example;
47
48    import com.android.tradefed.device.DeviceNotAvailableException;
49    import com.android.tradefed.result.ITestInvocationListener;
50    import com.android.tradefed.testtype.IRemoteTest;
51
52
53    public class HelloWorldTest implements IRemoteTest {
54        @Override
55        public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
56            System.out.println("Hello, TF World!");
57        }
58    }
59
60FIXME: prod-tests
61Save this sample code to
62`<git home>/tools/tradefederation/prod-tests/src/com/android/tradefed/example/HelloWorldTest.java`
63and rebuild tradefed from your shell:
64
65    m -j6
66
67If the build does not succeed, please consult the (FIXME: link)Development Environment page to
68ensure you did not miss any steps.
69
70
71## Creating a configuration
72
73Trade Federation tests are defined in a "Configuration". A Configuration is an XML file that
74instructs tradefed which test (or set of tests) to run.
75
76Lets create a new Configuration for our HelloWorldTest.
77
78    <configuration description="Runs the hello world test">
79        <test class="com.android.tradefed.example.HelloWorldTest" />
80    </configuration>
81
82TF will parse the Configuration XML file, load the specified class using reflection, instantiate it,
83cast it to a IRemoteTest, and call its 'run' method.
84
85Note that we've specified the full class name of the HelloWorldTest. Save this data to a
86`helloworld.xml` file anywhere on your local filesystem (eg `/tmp/helloworld.xml`).
87
88
89## Running the configuration
90
91From your shell, launch the tradefed console
92
93    $ ./tradefed.sh
94
95Ensure a device is connected to the host machine that is visible to tradefed
96
97    tf> list devices
98
99Configurations can be run using the `run <config>` console command.  Try this now
100
101FIXME: redo this
102
103    tf> run /tmp/helloworld.xml
104    05-12 13:19:36 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC
105    Hello, TF World!
106
107You should see "Hello, TF World!" outputted on the terminal.
108
109
110## Adding the configuration to the classpath
111FIXME: prod-tests
112For convenience of deployment, you can also bundle configuration files into the TradeFederation jars
113themselves. Tradefed will automatically recognize all configurations placed in 'config' folders on
114the classpath.
115
116Lets illustrate this now by moving the helloworld.xml into the tradefed core library.
117
118Move the `helloworld.xml` file into
119`<git root>/tools/tradefederation/prod-tests/res/config/example/helloworld.xml`.
120
121Rebuild tradefed, and restart the tradefed console.
122
123Ask tradefed to display the list of configurations on the classpath:
124
125    tf> list configs
126    […]
127    example/helloworld: Runs the hello world test
128
129You can now run the helloworld config via the following command
130
131    tf >run example/helloworld
132    05-12 13:21:21 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC
133    Hello, TF World!
134
135
136## Interacting with a device
137
138So far our hello world test isn't doing anything interesting. Tradefed is intended to run tests using Android devices, so lets add an Android device to the test.
139
140Tests can get a reference to an Android device by implementing the IDeviceTest interface.
141
142Here's a sample implementation of what this looks like:
143
144    public class HelloWorldTest implements IRemoteTest, IDeviceTest {
145        private ITestDevice mDevice;
146        @Override
147        public void setDevice(ITestDevice device) {
148            mDevice = device;
149        }
150
151        @Override
152        public ITestDevice getDevice() {
153            return mDevice;
154        }
155156    }
157
158The TradeFederation framework will inject the ITestDevice reference into your test via the
159IDeviceTest#setDevice method, before the IRemoteTest#run method is called.
160
161Lets add an additional print message to the HelloWorldTest displaying the serial number of the
162device.
163
164    @Override
165    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
166        System.out.println("Hello, TF World! I have a device " + getDevice().getSerialNumber());
167    }
168
169Now rebuild tradefed, and do (FIXME: update)
170
171    $ tradefed.sh
172    tf> list devices
173    Available devices:   [30315E38655500EC]
174175
176Take note of the serial number listed in Available devices above. That is the device that should be allocated to HelloWorld.
177
178    tf >run example/helloworld
179    05-12 13:26:18 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC
180    Hello world, TF! I have a device 30315E38655500EC
181
182You should see the new print message displaying the serial number of the device.
183
184
185## Sending test results
186
187IRemoteTests report results by calling methods on the ITestInvocationListener instance provided to
188their `#run` method.
189
190The TradeFederation framework is responsible for reporting the start and end of an Invocation (via
191the ITestInvocationListener#invocationStarted and ITestInvocationListener#invocationEnded methods
192respectively).
193
194A `test run` is a logical collection of tests. To report test results, IRemoteTests are responsible
195for reporting the start of a test run, the start and end of each test, and the end of the test run.
196
197Here's what the HelloWorldTest implementation looks like with a single failed test result.
198
199    @SuppressWarnings("unchecked")
200    @Override
201    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
202        System.out.println("Hello, TF World! I have a device " + getDevice().getSerialNumber());
203
204        TestIdentifier testId = new TestIdentifier("com.example.MyTestClassName", "sampleTest");
205        listener.testRunStarted("helloworldrun", 1);
206        listener.testStarted(testId);
207        listener.testFailed(TestFailure.FAILURE, testId, "oh noes, test failed");
208        listener.testEnded(testId, Collections.EMPTY_MAP);
209        listener.testRunEnded(0, Collections.EMPTY_MAP);
210    }
211
212Note that TradeFederation also includes several IRemoteTest implementations that you can reuse
213instead of writing your own from scratch. (such as InstrumentationTest, which can run an Android
214application's tests remotely on an Android device, parse the results, and forward them to the
215ITestInvocationListener). See the Test Types documentation for more details.
216
217
218## Storing test results
219
220By default, a TradeFederation configuration will use the TextResultReporter as the test listener
221implementation for the configuration.  TextResultReporter will dump the results of an invocation to
222stdout. To illustrate, try running the hello-world config from previous section now:
223
224    $ ./tradefed.sh
225    tf >run example/helloworld
226    05-16 20:03:15 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC
227    Hello world, TF! I have a device 30315E38655500EC
228    05-16 20:03:15 I/InvocationToJUnitResultForwarder: run helloworldrun started: 1 tests
229    Test FAILURE: com.example.MyTestClassName#sampleTest
230     stack: oh noes, test failed
231    05-16 20:03:15 I/InvocationToJUnitResultForwarder: run ended 0 ms
232
233If you want to store the results of an invocation elsewhere, say to a file, you would need to
234specify a custom "result_reporter" in your configuration, that specifies the custom
235ITestInvocationListener class you want to use.
236
237The TradeFederation framework includes a result_reporter (XmlResultReporter)  that will write test
238results to an XML file, in a format similar to the ant JUnit XML writer.
239
240Lets specify the result_reporter in the configuration now. Edit the
241`tools/tradefederation/res/config/example/helloworld.xml` like this:
242
243    <configuration description="Runs the hello world test">
244        <test class="com.android.tradefed.example.HelloWorldTest" />
245        <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
246    </configuration>
247
248Now rebuild tradefed and re-run the hello world sample:
249FIXME: paths
250
251    tf >run example/helloworld
252    05-16 21:07:07 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC
253    Hello world, TF! I have a device 30315E38655500EC
254    05-16 21:07:07 I/XmlResultReporter: Saved device_logcat log to /var/folders/++/++2Pz+++6+0++4RjPqRgNE+-4zk/-Tmp-/0/inv_2991649128735283633/device_logcat_6999997036887173857.txt
255    05-16 21:07:07 I/XmlResultReporter: Saved host_log log to /var/folders/++/++2Pz+++6+0++4RjPqRgNE+-4zk/-Tmp-/0/inv_2991649128735283633/host_log_6307746032218561704.txt
256    05-16 21:07:07 I/XmlResultReporter: XML test result file generated at /var/folders/++/++2Pz+++6+0++4RjPqRgNE+-4zk/-Tmp-/0/inv_2991649128735283633/test_result_536358148261684076.xml. Total tests 1, Failed 1, Error 0
257
258Notice the log message stating an XML file has been generated. The generated file should look like this:
259
260    <?xml version='1.0' encoding='UTF-8' ?>
261    <testsuite name="stub" tests="1" failures="1" errors="0" time="9" timestamp="2011-05-17T04:07:07" hostname="localhost">
262      <properties />
263      <testcase name="sampleTest" classname="com.example.MyTestClassName" time="0">
264        <failure>oh noes, test failed
265        </failure>
266      </testcase>
267    </testsuite>
268
269Note that you can write your own custom result_reporter. It just needs to implement the
270ITestInvocationListener interface.
271
272Also note that Tradefed supports multiple result_reporters, meaning that you can send test results
273to multiple independent destinations. Just specify multiple <result_reporter> tags in your config to
274do this.
275
276
277## Logging
278
279TradeFederation includes two logging facilities:
280
2811. ability to capture logs from the device (aka device logcat)
2822. ability to record logs from the TradeFederation framework running on the host machine (aka the
283    host log)
284
285Lets focus on 2 for now. Trade Federation's host logs are reported using the CLog wrapper for the
286ddmlib Log class.
287
288Lets convert the previous System.out.println call in HelloWorldTest to a CLog call:
289
290    @Override
291    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
292        CLog.i("Hello world, TF! I have a device " + getDevice().getSerialNumber());
293
294Now rebuild and rerun. You should see the log message on stdout.
295
296    tf> run example/helloworld
297298    05-16 21:30:46 I/HelloWorldTest: Hello world, TF! I have a device 30315E38655500EC
299300
301By default, TradeFederation will output host log messages to stdout. TradeFederation also includes a
302log implementation that will write messages to a file: FileLogger. To add file logging, add a
303'logger' tag to the configuration xml, specifying the full class name of FileLogger.
304
305    <configuration description="Runs the hello world test">
306        <test class="com.android.tradefed.example.HelloWorldTest" />
307        <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
308        <logger class="com.android.tradefed.log.FileLogger" />
309    </configuration>
310
311Now rebuild and run the helloworld example again.
312
313    tf >run example/helloworld
314315    05-16 21:38:21 I/XmlResultReporter: Saved device_logcat log to /var/folders/++/++2Pz+++6+0++4RjPqRgNE+-4zk/-Tmp-/0/inv_6390011618174565918/device_logcat_1302097394309452308.txt
316    05-16 21:38:21 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt
317318
319Note the log message indicating the path of the host log. View the contents of that file, and you
320should see your HelloWorldTest log message
321
322    $ more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt
323324    05-16 21:38:21 I/HelloWorldTest: Hello world, TF! I have a device 30315E38655500EC
325
326The TradeFederation framework will also automatically capture the logcat from the allocated device,
327and send it the the result_reporter for processing. XmlResultReporter will save the captured device
328logcat as a file.
329
330
331## Command line options
332Objects loaded from a TradeFederation Configuration (aka "Configuration objects") also have the
333ability to receive data from command line arguments.
334
335This is accomplished via the `@Option` annotation. To participate, a Configuration object class
336would apply the `@Option` annotation to a member field, and provide it a unique name. This would
337allow that member field's value to be populated via a command line option, and would also
338automatically add that option to the configuration help system (Note: not all field types are
339supported: see the OptionSetter javadoc for a description of supported types).
340
341Lets add an Option to the HelloWorldTest.
342
343    @Option(name="my_option",
344            shortName='m',
345            description="this is the option's help text",
346            // always display this option in the default help text
347            importance=Importance.ALWAYS)
348    private String mMyOption = "thisisthedefault";
349
350And lets add a log message to display the value of the option in HelloWorldTest, so we can
351demonstrate that it was received correctly.
352
353    @SuppressWarnings("unchecked")
354    @Override
355    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
356357        Log.logAndDisplay(LogLevel.INFO, "HelloWorldTest", "I received this option " + mMyOption);
358
359Rebuild TF and run helloworld: you should see a log message with the my_option's default value.
360
361    tf> run example/helloworld
362363    05-24 18:30:05 I/HelloWorldTest: I received this option thisisthedefault
364
365Now pass in a value for my_option: you should see my_option getting populated with that value
366
367    tf> run example/helloworld --my_option foo
368369    05-24 18:33:44 I/HelloWorldTest: I received this option foo
370
371TF configurations also include a help system, which automatically displays help text for @Option
372fields. Try it now, and you should see the help text for 'my_option':
373
374    tf> run --help example/helloworld
375    Printing help for only the important options. To see help for all options, use the --help-all flag
376
377      cmd_options options:
378        --[no-]help          display the help text for the most important/critical options. Default: false.
379        --[no-]help-all      display the full help text for all options. Default: false.
380        --[no-]loop          keep running continuously. Default: false.
381
382      test options:
383        -m, --my_option      this is the option's help text Default: thisisthedefault.
384
385      'file' logger options:
386        --log-level-display  the minimum log level to display on stdout. Must be one of verbose, debug, info, warn, error, assert. Default: error.
387FIXME: redo with enum help
388
389Note the message at the top about 'printing only the important options'. To reduce option help
390clutter, TF uses the Option#importance attribute to determine whether to show an Option's help text
391when '--help' is specified. '--help-all' will always show all options' help regardless of
392importance. See Option.Importance javadoc for details.
393
394You can also specify an Option's value within the configuration xml by adding a
395`<option name="" value="">` element. Lets see how this looks in the helloworld.xml:
396
397    <test class="com.android.tradefed.example.HelloWorldTest" >
398        <option name="my_option" value="fromxml" />
399    </test>
400
401Re-building and running helloworld should now produce this output:
402
403    05-24 20:38:25 I/HelloWorldTest: I received this option fromxml
404
405The configuration help should also be updated to indicate my_option's new default value:
406
407    tf> run --help example/helloworld
408      test options:
409        -m, --my_option      this is the option's help text Default: fromxml.
410
411Also note that other configuration objects included in the helloworld config, like FileLogger, also have options. '--log-level-display' is of interest because it filters the logs that show up on stdout. You may have noticed from earlier in the tutorial the 'Hello world, TF! I have a device ..' log message stopped getting displayed on stdout once we switched to using FileLogger. You can increase the verbosity of logging to stdout by passing in log-level-display arg.
412
413Try this now, and you should see the 'I have a device' log message reappear on stdout, in addition to getting logged to a file.
414
415    tf >run --log-level-display info example/helloworld
416417    05-24 18:53:50 I/HelloWorldTest: Hello world, TF! I have a device XXXXXX
418
419<!-- To make future debugging in this tutorial easier, edit the helloworld.xml to default log-level-display to debug:
420
421    <logger class="com.android.tradefed.log.FileLogger" >
422        <option name="log-level-display" value="debug" />
423    </logger>
424-->
425