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 } 155 … 156 } 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] 174 … 175 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 297 … 298 05-16 21:30:46 I/HelloWorldTest: Hello world, TF! I have a device 30315E38655500EC 299 … 300 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 314 … 315 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 317 … 318 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 323 … 324 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 { 356 … 357 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 362 … 363 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 368 … 369 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 416 … 417 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