• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Autotest Best Practices
2When the Chrome OS team started using autotest, we tried our best to figure out
3how to fit our code and our tests into the upstream style with little guidance
4and poor documentation.  This went poorly.  With the benefit of hindsight,
5we’re going to lay out some best-practices that we’d like to enforce going
6forward.  In many cases, there is legacy code that contradicts this style; we
7should go through and refactor that code to fit these guidelines as time
8allows.
9
10## Upstream Documentation
11
12There is a sizeable volume of general Autotest documentation available on
13github:
14https://github.com/autotest/autotest/wiki
15
16## Coding style
17
18Basically PEP-8.  See [docs/coding-style.md](docs/coding-style.md)
19
20## Where should my code live?
21
22| Type of Code              | Relative Path           |
23|---------------------------|-------------------------|
24| client-side tests         | client/site_tests/      |
25| server-side tests         | server/site_tests       |
26| common library code       | client/common_lib/cros/ |
27| server-only library code  | server/cros             |
28
29
30## Writing tests
31
32An autotest is really defined by its control file.  A control file contains
33important metadata about the test (name, author, description, duration, what
34suite it’s in, etc) and then pulls in and executes the actual test code.  This
35test code can be shared among multiple distinct test cases by parameterizing it
36and passing those parameters in from separate control files.
37
38Autotests *must*:
39
40 * Be self-contained: assume nothing about the condition of the device
41 * Be hermetic: requiring the Internet to be reachable in order for your test
42   to succeed is unacceptable.
43 * Be automatic: avoid user interaction and run-time specification of input
44   values.
45 * Be integration tests: if you can test the feature in a unit test (or a
46   chrome browser test), do so.
47 * Prefer object composition to inheritance: avoid subclassing test.test to
48   implement common functionality for multiple tests.  Instead, create a class
49   that your tests can instantiate to perform common operations.  This enables
50   us to write tests that use both PyAuto and Servo without dealing with
51   multiple inheritance, for example.
52 * Be deterministic: a test should not validate the timing of some operation.
53   Instead, write a test that records the timing in performance keyvals so that
54   we can track the numbers over time.
55
56Autotests *must not*:
57
58 * Put significant logic in the control file: control files are really just
59   python, so one can put arbitrary logic in there.  Don’t.  Run your test
60   code, perhaps with some parameters.
61
62Autotests *may*:
63
64 * Share parameterized fixtures: a test is defined by a control file.  Control
65   files import and run test code, and can pass simple parameters to the code
66   they run through a well-specified interface.
67
68Autotest has a notion of both client-side tests and server-side tests.  Code in
69a client-side test runs only on the device under test (DUT), and as such isn’t
70capable of maintaining state across reboots or handling a failed suspend/resume
71and the like.  If possible, an autotest should be written as a client-side
72test.  A ‘server’ test runs on the autotest server, but gets assigned a DUT
73just like a client-side test.  It can use various autotest primitives (and
74library code written by the CrOS team) to manipulate that device.  Most, if not
75all, tests that use Servo or remote power management should be server-side
76tests, as an example.
77
78Adding a test involves putting a control file and a properly-written test
79wrapper in the right place in the source tree.  There are conventions that must
80be followed, and a variety of primitives available for use.  When writing any
81code, whether client-side test, server-side test, or library, have a strong
82bias towards using autotest utility code.  This keeps the codebase consistent.
83
84
85## Writing a test
86
87This section explains considerations and requirements for any autotest, whether
88client or server.
89
90### Control files
91
92Upstream documentation
93Our local conventions for autotest control files deviate from the above a bit,
94but the indication about which fields are mandatory still holds.
95
96| Variable     | Required | Value                                                                                                                                                                                                                                                                                                                                    |
97|--------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
98| AUTHOR       | Yes      | A comma-delimited string of at least one responsible engineer and a backup engineer -- or at worst a backup mailing list. i.e. AUTHOR = ‘msb, snanda’                                                                                                                                                                                    |
99| DEPENDENCIES | No       | list of tags known to the HW test lab.                                                                                                                                                                                                                                                                                                   |
100| DOC          | Yes      | Long description of the test, pass/fail criteria                                                                                                                                                                                                                                                                                         |
101| NAME         | Yes      | Display name of the test. Generally this is the directory where your test lives e.g. hardware_TPMCheck. If you are using multiple run_test calls in the same control file or multiple control files with one test wrapper in the same suite, problems arise with the displaying of your test name. crosbug.com/35795. When in doubt ask. |
102| SYNC\_COUNT  | No       | Integer >= 1.  Number of simultaneous devices needed for a test run.                                                                                                                                                                                                                                                                     |
103| TIME         | Yes      | Test duration: 'FAST' (<1m), 'MEDIUM' (<10m), 'LONG' (<20m), 'LENGTHY' (>30m)                                                                                                                                                                                                                                                            |
104| TEST\_TYPE   | Yes      | Client or Server                                                                                                                                                                                                                                                                                                                         |
105| SUITE        | No       | A comma-delimited string of suite names that this test should be a part of.                                                                                                                                                                                                                                                              |
106
107### Choosing a Suite
108
109Currently existing suites are defined in the test\_suites/ subdirectory at the
110top level of the autotest repo.  Read the docstrings there to see if your new
111test fits into one that’s already defined.
112
113When first adding a test, it should not go into the BVT suite.   A test should
114only be added to the BVT after it has been running in some non-BVT suite long
115enough to establish a track record showing that the test does not fail when run
116against working software.  A suite named experimental exists for tests intended
117for the BVT, and for which there is no more convenient home.
118
119### Pure python
120
121Lie, cheat and steal to keep your tests in pure python.  It will be easier to
122debug failures, it will be easier to generate meaningful error output, it will
123be simpler to get your tests installed and run, and it will be simpler for the
124lab team to build tools that allow you to quickly iterate.
125
126Shelling out to existing command-line tools is done fairly often, and isn’t a
127terrible thing.  The test author can wind up having to do a lot of output
128parsing, which is often brittle, but this can be a decent tradeoff in lieu of
129having to reimplement large pieces of functionality in python.
130
131Note that you will need to be sure that any commands you use are installed on
132the host.  For a client-side test, “the host” means “the DUT”.  For a
133server-side test, “the host” typically means “the system running autoserv”;
134however, if you use SiteHost.run(), the command will run on the DUT.  On the
135server, your tests will have access to all tools common to both a typical CrOS
136chroot environment and standard Goobuntu.
137
138If you want to use a tool on the DUT, it may be appropriate to include it as a
139dependency of the chromeos-base/chromeos-test package.  This ensures that the
140tool is pre-installed on every test image for every device, and will always be
141available for use.  Otherwise, the tool must be installed as an autotest “dep”.
142
143_Never install your own shell scripts and call them._  Anything you can do in
144shell, you can do in python.
145
146### Reporting failures
147
148Autotest supports several kinds of failure statuses:
149
150| Status   | Exception         | Reason                                                                                                                                                                                                                                                                                                                   |
151|----------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
152| WARN     | error.TestWarn    | error.TestWarn should be used when side effects to the test running are encountered but are not directly related to the test running. For example, if you are testing Wifi and powerd crashes. *Currently* there are not any clear usecases for this and error.TestWarn should be generally avoided until further notice. |
153| TEST\_NA | error.TestNAError | This test does not apply in the current environment.                                                                                                                                                                                                                                                                     |
154| ERROR    | error.TestError   | The test was unable to validate the desired behavior.                                                                                                                                                                                                                                                                    |
155| FAIL     | error.TestFail    | The test determined the desired behavior failed to occur.                                                                                                                                                                                                                                                                |
156
157
158### Considerations when writing client-side tests
159
160All client-side tests authored at Google must live in the client/site\_tests sub-directory of the autotest source tree.
161
162###Compiling and executing binaries
163
164It is possible to compile source that’s included with your test and use the
165products at test runtime.  The build infrastructure will compile this code for
166the appropriate target architecture and package it up along with the rest of
167your test’s resources, but this increases your development iteration time as
168you need to actually re-build and re-package your test to deploy it to the
169device.  While we hope to improve tooling support for this use case in the
170future, avoiding this issue is the ideal.
171
172If you can’t avoid this, here’s how to get your code compiled and installed as
173a part of your test:
1741. Create a src/ directory next to your control file.
1752. Put your source, including its Makefile, in src/
1763. define a method in your test class called “setup(self)” that takes no arguments.
1774. setup(self) should perform all tasks necessary to build your tool.  There are some helpful utility functions in client/common_lib/base_utils.py.  Trivial example:
178
179```
180    def setup(self):
181        os.chdir(self.srcdir)
182        utils.make('OUT_DIR=.')
183```
184
185### Reusing code (“fixtures”)
186
187Any autotest is, essentially, a single usage of a re-usable test fixture.  This
188is because run\_once() in your test wrapper can take any arguments you want.  As
189such, multiple control files can re-use the same wrapper -- and should, where
190it makes sense.
191
192### Considerations when writing server-side tests
193
194All server-side tests authored at Google must live in the server/site\_tests
195sub-directory of the autotest source tree.
196
197It should be even easier to keep the server-side of a test in pure python, as
198you should simply be driving the DUT and verifying state.
199
200### When/why to write a server-side test
201
202Server-side tests are appropriate when some operation in the test can't be
203executed on the DUT.  The prototypical example is rebooting the DUT.  Other
204examples include tests that manipulate the network around the DUT (e.g. WiFi
205tests), tests that power off the DUT, and tests that rely on a Servo attached
206to the DUT.
207
208One simple criterion for whether to write a server-side test is this:  Is the
209DUT an object that the test must manipulate?  If the answer is “yes”, then a
210server-side test makes sense.
211
212### Control files for server-side tests
213
214Server-side tests commonly operate on the DUT as an object.  Autotest
215represents the DUT with an instance of class Host; the instance is constructed
216and passed to the test from the control file.  Creating the host object in the
217control file can be done using certain definitions present in the global
218environment of every control file:
219
220 * Function hosts.create\_host() will create a host object from a string with
221   the name of the host (an IP address as a string is also acceptable).
222 * Variable machines is a list of the host names available to the test.
223
224Below is a sample fragment for a control file that runs a simple server side test in parallel on all the hosts specified for the test.  The fragment is a complete control file, except for the missing boilerplate comments and documentation definitions required in all control files.
225
226```
227def run(machine):
228    host = hosts.create_host(machine)
229    job.run_test("platform_ServerTest", host=host)
230
231parallel_simple(run, machines)
232```
233
234Note:  The sample above relies on a common convention that the run\_once()
235method of a server-side test defines an argument named host with a default
236value, e.g.
237
238```
239def run_once(self, host=None):
240    # … test code goes here.
241```
242
243### Operations on Host objects
244
245A Host object supports various methods to operate on a DUT.  Below is a short list of important methods supported by instances of Host:
246
247 * run(command) - run a shell command on the host
248 * reboot() - reboot the host, and wait for it to be back on the network
249 * wait_up() - wait for the host to be active on the network
250 * wait_down() - wait until the host is no longer on the network, or until it is known to have rebooted.
251
252More details, including a longer list of available methods, and more about how
253they work can be found in the Autotest documentation for autoserv and Autotest
254documentation for Host.
255
256### Servo-based tests
257
258For server-side tests that use a servo-attached DUT, the host object has a
259servo attribute.  If Autotest determines that the DUT has a Servo attached, the
260servo attribute will be a valid instance of a Servo client object; otherwise
261the attribute will be None.
262
263For a DUT in the lab, Autotest will automatically determine whether there is a
264servo available; however, if a test requires Servo, its control file must have
265additional code to guarantee a properly initialized servo object on the host.
266
267Below is a code snippet outlining the requirements; portions of the control file have been omitted for brevity:
268
269```
270# ... Standard boilerplate variable assignments...
271DEPENDENCIES = "servo"
272# ... more standard boilerplate...
273
274args_dict = utils.args_to_dict(args)
275servo_args = hosts.SiteHost.get_servo_arguments(args_dict)
276
277def run(machine):
278    host = hosts.create_host(machine, servo_args=servo_args)
279    job.run_test("platform_SampleServoTest", host=host)
280
281parallel_simple(run, machines)
282```
283
284The `DEPENDENCIES` setting guarantees that if the test is scheduled in the lab,
285it will be assigned to a DUT that has a servo.
286
287The setting of `servo_args` guarantees two distinct things:  First, it forces
288checks that will make sure that the Servo is functioning properly; this
289guarantees that the host's `servo` attribute will not be None.  Second, the code
290allows you to pass necessary servo specific command-line arguments to
291`test_that`.
292
293If the test control file follows the formula above, the test can be reliably called in a variety of ways:
294 * When used for hosts in the lab, the host’s servo object will use the servo attached to the host, and the test can assume that the servo object is not None.
295 * If you start servod manually on your desktop using the default port, you can use test_that without any special options.
296 * If you need to specify a non-default host or port number (e.g. because servod is remote, or because you have more than one servo board), you can specify them with commands like these:
297
298```
299test_that --args=”servo_host=...” …
300test_that --args=”servo_port=...” …
301test_that --args=”servo_host=... servo_port=...” ...
302```
303
304### Calling client-side tests from a server-side test
305
306Commonly, server-side tests need to do more on the DUT than simply run short
307shell commands.  In those cases, a client-side test should be written and
308invoked from the server-side test.  In particular, a client side test allows
309the client side code to be written in Python that uses standard Autotest
310infrastructure, such as various utility modules or the logging infrastructure.
311
312Below is a short snippet showing the standard form for calling a client-side
313test from server-side code:
314
315```
316from autotest_lib.server import autotest
317
318    # ... inside some function, e.g. in run_once()
319    client_at = autotest.Autotest(host)
320    client_at.run_test("platform_ClientTest")
321```
322
323### Writing library code
324
325There is a large quantity of Chromium OS specific code in the autotest
326codebase.  Much of this exists to provide re-usable modules that enable tests
327to talk to system services.  The guidelines from above apply here as well.
328This code should be as pure python as possible, though it is reasonable to
329shell out to command line tools from time to time.  In some cases we’ve done
330this where we could (now) use the service’s DBus APIs directly.  If you’re
331adding code to allow tests to communicate with your service, it is strongly
332recommended that you use DBus where possible, instead of munging config files
333directly or using command-line tools.
334
335Currently, our library code lives in a concerning variety of places in the
336autotest tree.  This is due to a poor initial understanding of how to do
337things, and new code should follow the following conventions instead:
338
339 * Used only in server-side tests: server/cros
340 * Used in both server- and client-side tests, or only client:
341   client/common\_lib/cros
342
343### Adding test deps
344
345This does not refer to the optional `DEPENDENCIES` field in test control files.
346Rather, this section discusses how and when to use code/data/tools that are not
347pre-installed on test images, and should (or can) not be included right in with
348the test source.
349
350Unfortunately, there is no hard-and-fast rule here.  Generally, if this is some
351small tool or blob of data you need for a single test, you should include it as
352discussed above in Writing client-side tests.  If you’re writing the tool, and
353it has use for developers as well as in one or more tests that you’re writing,
354then make it a first-class CrOS project.  Write an ebuild, write unit tests,
355and then add it to the test image by default.  This can be done by RDEPENDing
356on your new test package from the chromeos-test ebuild.
357
358If your code/data falls in the middle (useful to several tests, not to devs),
359and/or is large (hundreds of megabytes as opposed to tens) then using an
360autotest ‘dep’ may be the right choice.  Conceptually, an autotest test dep is
361simply another kind of archive that the autotest infrastructure knows how to
362fetch and unpack.  There are two components to including a dependency from an
363autotest test -- setup during build time and installing it on your DUT when
364running a test.  The setup phase must be run from your tests setup() method
365like so:
366
367```
368def setup(self):
369  self.job.setup_dep([‘mydep’])
370  logging.debug(‘mydep is at %s’ % (os.path.join(self.autodir,
371deps/mydep’))
372```
373
374The above gets run when you “build” the test.
375
376The other half of this equation is actually installing the dependency so you
377can use it while running a test.  To do this, add the following to either your
378run\_once or initialize methods:
379
380```
381        dep = dep_name
382        dep_dir = os.path.join(self.autodir, 'deps', dep=dep)
383        self.job.install_pkg(dep, 'dep', dep_dir)
384```
385
386
387You can now reference the content of your dep using dep_dir.
388
389Now that you know how to include a dep, the next question is how to write one.
390Before you read further, you should check out client/deps/\* for many examples
391of deps in our autotest tree.
392
393### Create a dep from a third-party package
394
395There are many examples of how to do this in the client/deps directory already.
396The key component is to check in a tarball of the version of the dependency
397you’d like to include under client/deps/your\_dep.
398
399All deps require a control file and an actual python module by the same name.
400They will also need a copy of common.py to import utils.update\_version. Both
401the control and common are straightforward, the python module does all the
402magic.
403
404The deps python module follows a standard convention: a setup function and a
405call to utils.update\_version.  update\_version is used instead of directly
406calling setup as it maintains additional versioning logic ensuring setup is
407only done 1x per dep. The following is its method signature:
408
409```
410def update_version(srcdir, preserve_srcdir, new_version, install,
411                   *args, **dargs)
412```
413
414
415Notably, install should be a pointer to your setup function and `*args` should
416be filled in with params to said setup function.
417
418If you are using a tarball, your setup function should look something like:
419
420```
421def setup(tarball, my_dir)
422    utils.extract_tarball_to_dir(tarball, my_dir)
423    os.chdir(my_dir)
424    utils.make() # this assumes your tarball has a Makefile.
425```
426
427And you would invoke this with:
428
429```
430utils.update_version(os.getcwd(), True, version, setup, tarball_path,
431                     os.getcwd())
432```
433
434
435Note: The developer needs to call this because def setup is a function they are
436defining that can take any number of arguments or install the dep in any way
437they see fit. The above example uses tarballs but some are distributed as
438straight source under the src dir so their setup function only takes a top
439level path. We could avoid this by forcing a convention but that would be
440artificially constraining the deps mechanism.
441
442Once you’ve created the dep, you will also have to add the dep to the
443autotest-deps package in chromiumos-overlay/chromeos-base/autotest-deps,
444‘cros\_workon start’ it, and re-emerge it.
445
446### Create a dep from other chrome-os packages
447
448One can also create autotest deps from code that lives in other CrOS packages,
449or from build products generated by other packages.  This is similar as above
450but you can reference code using the `CHROMEOS_ROOT` env var that points to the
451root of the CrOS source checkout, or the SYSROOT env var (which points to
452/build/<board>) to refer to build products.  Again, read the above. Here’s an
453example of the former with the files I want in
454chromeos\_tree/chromite/my\_dep/\* where this will be the python code in
455autotest/files/client/deps/my\_dep/my\_dep.py module.
456
457```
458import common, os, shutil
459from autotest_lib.client.bin import utils
460
461version = 1
462
463def setup(setup_dir):
464    my_dep_dir = os.path.join(os.environ['CHROMEOS_ROOT'], 'chromite',
465                              'buildbot')
466    shutil.copytree(my_dep_dir, setup_dir)
467
468
469work_dir = os.path.join(os.getcwd(), 'src')
470utils.update_version(os.getcwd(), True, version, setup, work_dir)
471```
472