# Tutorial This tutorial guides you through the construction of a "hello world" Trade Federation test configuration, and gives you a hands-on introduction to the Trade Federation framework. Starting from the Tf development environment, it guides you through the process of creating a simple Trade Federation config and gradually adding more features to it. The tutorial presents the TF test development process as a set of exercises, each consisting of several steps. The exercises demonstrate how to gradually build and refine your configuration, and provide all the sample code you need to complete the test configuration. When you are finished with the tutorial, you will have created a functioning TF configuration and will have learned many of the most important concepts in the TF framework. ## Set up TradeFederation development environment See (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. For 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. ## Creating a test class Lets create a hello world test that just dumps a message to stdout. A TradeFederation test must implement the (FIXME: link) IRemoteTest interface. Here's an implementation for the HelloWorldTest: package com.android.tradefed.example; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.testtype.IRemoteTest; public class HelloWorldTest implements IRemoteTest { @Override public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { System.out.println("Hello, TF World!"); } } FIXME: prod-tests Save this sample code to `/tools/tradefederation/prod-tests/src/com/android/tradefed/example/HelloWorldTest.java` and rebuild tradefed from your shell: m -j6 If the build does not succeed, please consult the (FIXME: link)Development Environment page to ensure you did not miss any steps. ## Creating a configuration Trade Federation tests are defined in a "Configuration". A Configuration is an XML file that instructs tradefed which test (or set of tests) to run. Lets create a new Configuration for our HelloWorldTest. TF will parse the Configuration XML file, load the specified class using reflection, instantiate it, cast it to a IRemoteTest, and call its 'run' method. Note that we've specified the full class name of the HelloWorldTest. Save this data to a `helloworld.xml` file anywhere on your local filesystem (eg `/tmp/helloworld.xml`). ## Running the configuration From your shell, launch the tradefed console $ ./tradefed.sh Ensure a device is connected to the host machine that is visible to tradefed tf> list devices Configurations can be run using the `run ` console command. Try this now FIXME: redo this tf> run /tmp/helloworld.xml 05-12 13:19:36 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC Hello, TF World! You should see "Hello, TF World!" outputted on the terminal. ## Adding the configuration to the classpath FIXME: prod-tests For convenience of deployment, you can also bundle configuration files into the TradeFederation jars themselves. Tradefed will automatically recognize all configurations placed in 'config' folders on the classpath. Lets illustrate this now by moving the helloworld.xml into the tradefed core library. Move the `helloworld.xml` file into `/tools/tradefederation/prod-tests/res/config/example/helloworld.xml`. Rebuild tradefed, and restart the tradefed console. Ask tradefed to display the list of configurations on the classpath: tf> list configs […] example/helloworld: Runs the hello world test You can now run the helloworld config via the following command tf >run example/helloworld 05-12 13:21:21 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC Hello, TF World! ## Interacting with a device So 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. Tests can get a reference to an Android device by implementing the IDeviceTest interface. Here's a sample implementation of what this looks like: public class HelloWorldTest implements IRemoteTest, IDeviceTest { private ITestDevice mDevice; @Override public void setDevice(ITestDevice device) { mDevice = device; } @Override public ITestDevice getDevice() { return mDevice; } … } The TradeFederation framework will inject the ITestDevice reference into your test via the IDeviceTest#setDevice method, before the IRemoteTest#run method is called. Lets add an additional print message to the HelloWorldTest displaying the serial number of the device. @Override public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { System.out.println("Hello, TF World! I have a device " + getDevice().getSerialNumber()); } Now rebuild tradefed, and do (FIXME: update) $ tradefed.sh tf> list devices Available devices: [30315E38655500EC] … Take note of the serial number listed in Available devices above. That is the device that should be allocated to HelloWorld. tf >run example/helloworld 05-12 13:26:18 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC Hello world, TF! I have a device 30315E38655500EC You should see the new print message displaying the serial number of the device. ## Sending test results IRemoteTests report results by calling methods on the ITestInvocationListener instance provided to their `#run` method. The TradeFederation framework is responsible for reporting the start and end of an Invocation (via the ITestInvocationListener#invocationStarted and ITestInvocationListener#invocationEnded methods respectively). A `test run` is a logical collection of tests. To report test results, IRemoteTests are responsible for reporting the start of a test run, the start and end of each test, and the end of the test run. Here's what the HelloWorldTest implementation looks like with a single failed test result. @SuppressWarnings("unchecked") @Override public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { System.out.println("Hello, TF World! I have a device " + getDevice().getSerialNumber()); TestIdentifier testId = new TestIdentifier("com.example.MyTestClassName", "sampleTest"); listener.testRunStarted("helloworldrun", 1); listener.testStarted(testId); listener.testFailed(TestFailure.FAILURE, testId, "oh noes, test failed"); listener.testEnded(testId, Collections.EMPTY_MAP); listener.testRunEnded(0, Collections.EMPTY_MAP); } Note that TradeFederation also includes several IRemoteTest implementations that you can reuse instead of writing your own from scratch. (such as InstrumentationTest, which can run an Android application's tests remotely on an Android device, parse the results, and forward them to the ITestInvocationListener). See the Test Types documentation for more details. ## Storing test results By default, a TradeFederation configuration will use the TextResultReporter as the test listener implementation for the configuration. TextResultReporter will dump the results of an invocation to stdout. To illustrate, try running the hello-world config from previous section now: $ ./tradefed.sh tf >run example/helloworld 05-16 20:03:15 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC Hello world, TF! I have a device 30315E38655500EC 05-16 20:03:15 I/InvocationToJUnitResultForwarder: run helloworldrun started: 1 tests Test FAILURE: com.example.MyTestClassName#sampleTest stack: oh noes, test failed 05-16 20:03:15 I/InvocationToJUnitResultForwarder: run ended 0 ms If you want to store the results of an invocation elsewhere, say to a file, you would need to specify a custom "result_reporter" in your configuration, that specifies the custom ITestInvocationListener class you want to use. The TradeFederation framework includes a result_reporter (XmlResultReporter) that will write test results to an XML file, in a format similar to the ant JUnit XML writer. Lets specify the result_reporter in the configuration now. Edit the `tools/tradefederation/res/config/example/helloworld.xml` like this: Now rebuild tradefed and re-run the hello world sample: FIXME: paths tf >run example/helloworld 05-16 21:07:07 I/TestInvocation: Starting invocation for target stub on build 0 on device 30315E38655500EC Hello world, TF! I have a device 30315E38655500EC 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 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 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 Notice the log message stating an XML file has been generated. The generated file should look like this: oh noes, test failed Note that you can write your own custom result_reporter. It just needs to implement the ITestInvocationListener interface. Also note that Tradefed supports multiple result_reporters, meaning that you can send test results to multiple independent destinations. Just specify multiple tags in your config to do this. ## Logging TradeFederation includes two logging facilities: 1. ability to capture logs from the device (aka device logcat) 2. ability to record logs from the TradeFederation framework running on the host machine (aka the host log) Lets focus on 2 for now. Trade Federation's host logs are reported using the CLog wrapper for the ddmlib Log class. Lets convert the previous System.out.println call in HelloWorldTest to a CLog call: @Override public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { CLog.i("Hello world, TF! I have a device " + getDevice().getSerialNumber()); Now rebuild and rerun. You should see the log message on stdout. tf> run example/helloworld … 05-16 21:30:46 I/HelloWorldTest: Hello world, TF! I have a device 30315E38655500EC … By default, TradeFederation will output host log messages to stdout. TradeFederation also includes a log implementation that will write messages to a file: FileLogger. To add file logging, add a 'logger' tag to the configuration xml, specifying the full class name of FileLogger. Now rebuild and run the helloworld example again. tf >run example/helloworld … 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 05-16 21:38:21 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt … Note the log message indicating the path of the host log. View the contents of that file, and you should see your HelloWorldTest log message $ more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt … 05-16 21:38:21 I/HelloWorldTest: Hello world, TF! I have a device 30315E38655500EC The TradeFederation framework will also automatically capture the logcat from the allocated device, and send it the the result_reporter for processing. XmlResultReporter will save the captured device logcat as a file. ## Command line options Objects loaded from a TradeFederation Configuration (aka "Configuration objects") also have the ability to receive data from command line arguments. This is accomplished via the `@Option` annotation. To participate, a Configuration object class would apply the `@Option` annotation to a member field, and provide it a unique name. This would allow that member field's value to be populated via a command line option, and would also automatically add that option to the configuration help system (Note: not all field types are supported: see the OptionSetter javadoc for a description of supported types). Lets add an Option to the HelloWorldTest. @Option(name="my_option", shortName='m', description="this is the option's help text", // always display this option in the default help text importance=Importance.ALWAYS) private String mMyOption = "thisisthedefault"; And lets add a log message to display the value of the option in HelloWorldTest, so we can demonstrate that it was received correctly. @SuppressWarnings("unchecked") @Override public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { … Log.logAndDisplay(LogLevel.INFO, "HelloWorldTest", "I received this option " + mMyOption); Rebuild TF and run helloworld: you should see a log message with the my_option's default value. tf> run example/helloworld … 05-24 18:30:05 I/HelloWorldTest: I received this option thisisthedefault Now pass in a value for my_option: you should see my_option getting populated with that value tf> run example/helloworld --my_option foo … 05-24 18:33:44 I/HelloWorldTest: I received this option foo TF configurations also include a help system, which automatically displays help text for @Option fields. Try it now, and you should see the help text for 'my_option': tf> run --help example/helloworld Printing help for only the important options. To see help for all options, use the --help-all flag cmd_options options: --[no-]help display the help text for the most important/critical options. Default: false. --[no-]help-all display the full help text for all options. Default: false. --[no-]loop keep running continuously. Default: false. test options: -m, --my_option this is the option's help text Default: thisisthedefault. 'file' logger options: --log-level-display the minimum log level to display on stdout. Must be one of verbose, debug, info, warn, error, assert. Default: error. FIXME: redo with enum help Note the message at the top about 'printing only the important options'. To reduce option help clutter, TF uses the Option#importance attribute to determine whether to show an Option's help text when '--help' is specified. '--help-all' will always show all options' help regardless of importance. See Option.Importance javadoc for details. You can also specify an Option's value within the configuration xml by adding a `