| Index: docs/Testing.md
|
| diff --git a/docs/Testing.md b/docs/Testing.md
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..55205de29133cc5d3fbb8334b929e7e76cbbb153
|
| --- /dev/null
|
| +++ b/docs/Testing.md
|
| @@ -0,0 +1,459 @@
|
| +# GYP (Generate Your Projects) Tests
|
| +
|
| +--
|
| +
|
| +Status: Draft (as of 2009-08-18)
|
| +
|
| +Steven Knight <sgk@chromium.org>
|
| +_et al._
|
| +
|
| +Modified: 2009-08-18
|
| +
|
| +[TOC]
|
| +
|
| +## Introduction
|
| +
|
| +This document describes the GYP testing infrastructure,
|
| +as provided by the `TestGyp.py` module.
|
| +
|
| +These tests emphasize testing the _behavior_ of the
|
| +various GYP-generated build configurations:
|
| +Visual Studio, Xcode, SCons, Make, etc.
|
| +The goal is _not_ to test the output of the GYP generators by,
|
| +for example, comparing a GYP-generated Makefile
|
| +against a set of known "golden" Makefiles
|
| +(although the testing infrastructure could
|
| +be used to write those kinds of tests).
|
| +The idea is that the generated build configuration files
|
| +could be completely written to add a feature or fix a bug
|
| +so long as they continue to support the functional behaviors
|
| +defined by the tests: building programs, shared libraries, etc.
|
| +
|
| +## "Hello, world!" GYP test configuration
|
| +
|
| +Here is an actual test configuration,
|
| +a simple build of a C program to print `"Hello, world!"`.
|
| +
|
| +```
|
| + $ ls -l test/hello
|
| + total 20
|
| + -rw-r--r-- 1 knight knight 312 Jul 30 20:22 gyptest-all.py
|
| + -rw-r--r-- 1 knight knight 307 Jul 30 20:22 gyptest-default.py
|
| + -rwxr-xr-x 1 knight knight 326 Jul 30 20:22 gyptest-target.py
|
| + -rw-r--r-- 1 knight knight 98 Jul 30 20:22 hello.c
|
| + -rw-r--r-- 1 knight knight 142 Jul 30 20:22 hello.gyp
|
| + $
|
| +```
|
| +
|
| +The `gyptest-*.py` files are three separate tests (test scripts)
|
| +that use this configuration. The first one, `gyptest-all.py`,
|
| +looks like this:
|
| +
|
| +```
|
| + #!/usr/bin/env python
|
| +
|
| + """
|
| + Verifies simplest-possible build of a "Hello, world!" program
|
| + using an explicit build target of 'all'.
|
| + """
|
| +
|
| + import TestGyp
|
| +
|
| + test = TestGyp.TestGyp()
|
| +
|
| + test.run_gyp('hello.gyp')
|
| +
|
| + test.build_all('hello.gyp')
|
| +
|
| + test.run_built_executable('hello', stdout="Hello, world!\n")
|
| +
|
| + test.pass_test()
|
| +```
|
| +
|
| +The test script above runs GYP against the specified input file
|
| +(`hello.gyp`) to generate a build configuration.
|
| +It then tries to build the `'all'` target
|
| +(or its equivalent) using the generated build configuration.
|
| +Last, it verifies that the build worked as expected
|
| +by running the executable program (`hello`)
|
| +that was just presumably built by the generated configuration,
|
| +and verifies that the output from the program
|
| +matches the expected `stdout` string (`"Hello, world!\n"`).
|
| +
|
| +Which configuration is generated
|
| +(i.e., which build tool to test)
|
| +is specified when the test is run;
|
| +see the next section.
|
| +
|
| +Surrounding the functional parts of the test
|
| +described above are the header,
|
| +which should be basically the same for each test
|
| +(modulo a different description in the docstring):
|
| +
|
| +```
|
| + #!/usr/bin/env python
|
| +
|
| + """
|
| + Verifies simplest-possible build of a "Hello, world!" program
|
| + using an explicit build target of 'all'.
|
| + """
|
| +
|
| + import TestGyp
|
| +
|
| + test = TestGyp.TestGyp()
|
| +```
|
| +
|
| +Similarly, the footer should be the same in every test:
|
| +
|
| +```
|
| + test.pass_test()
|
| +```
|
| +
|
| +## Running tests
|
| +
|
| +Test scripts are run by the `gyptest.py` script.
|
| +You can specify (an) explicit test script(s) to run:
|
| +
|
| +```
|
| + $ python gyptest.py test/hello/gyptest-all.py
|
| + PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
|
| + TESTGYP_FORMAT=scons
|
| + /usr/bin/python test/hello/gyptest-all.py
|
| + PASSED
|
| + $
|
| +```
|
| +
|
| +If you specify a directory, all test scripts
|
| +(scripts prefixed with `gyptest-`) underneath
|
| +the directory will be run:
|
| +
|
| +```
|
| + $ python gyptest.py test/hello
|
| + PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
|
| + TESTGYP_FORMAT=scons
|
| + /usr/bin/python test/hello/gyptest-all.py
|
| + PASSED
|
| + /usr/bin/python test/hello/gyptest-default.py
|
| + PASSED
|
| + /usr/bin/python test/hello/gyptest-target.py
|
| + PASSED
|
| + $
|
| +```
|
| +
|
| +Or you can specify the `-a` option to run all scripts
|
| +in the tree:
|
| +
|
| +```
|
| + $ python gyptest.py -a
|
| + PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
|
| + TESTGYP_FORMAT=scons
|
| + /usr/bin/python test/configurations/gyptest-configurations.py
|
| + PASSED
|
| + /usr/bin/python test/defines/gyptest-defines.py
|
| + PASSED
|
| + .
|
| + .
|
| + .
|
| + .
|
| + /usr/bin/python test/variables/gyptest-commands.py
|
| + PASSED
|
| + $
|
| +```
|
| +
|
| +If any tests fail during the run,
|
| +the `gyptest.py` script will report them in a
|
| +summary at the end.
|
| +
|
| +## Debugging tests
|
| +
|
| +Tests that create intermediate output do so under the gyp/out/testworkarea
|
| +directory. On test completion, intermediate output is cleaned up. To preserve
|
| +this output, set the environment variable PRESERVE=1. This can be handy to
|
| +inspect intermediate data when debugging a test.
|
| +
|
| +You can also set PRESERVE\_PASS=1, PRESERVE\_FAIL=1 or PRESERVE\_NO\_RESULT=1
|
| +to preserve output for tests that fall into one of those categories.
|
| +
|
| +# Specifying the format (build tool) to use
|
| +
|
| +By default, the `gyptest.py` script will generate configurations for
|
| +the "primary" supported build tool for the platform you're on:
|
| +Visual Studio on Windows,
|
| +Xcode on Mac,
|
| +and (currently) SCons on Linux.
|
| +An alternate format (build tool) may be specified
|
| +using the `-f` option:
|
| +
|
| +```
|
| + $ python gyptest.py -f make test/hello/gyptest-all.py
|
| + PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
|
| + TESTGYP_FORMAT=make
|
| + /usr/bin/python test/hello/gyptest-all.py
|
| + PASSED
|
| + $
|
| +```
|
| +
|
| +Multiple tools may be specified in a single pass as
|
| +a comma-separated list:
|
| +
|
| +```
|
| + $ python gyptest.py -f make,scons test/hello/gyptest-all.py
|
| + PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
|
| + TESTGYP_FORMAT=make
|
| + /usr/bin/python test/hello/gyptest-all.py
|
| + PASSED
|
| + TESTGYP_FORMAT=scons
|
| + /usr/bin/python test/hello/gyptest-all.py
|
| + PASSED
|
| + $
|
| +```
|
| +
|
| +## Test script functions and methods
|
| +
|
| +The `TestGyp` class contains a lot of functionality
|
| +intended to make it easy to write tests.
|
| +This section describes the most useful pieces for GYP testing.
|
| +
|
| +(The `TestGyp` class is actually a subclass of more generic
|
| +`TestCommon` and `TestCmd` base classes
|
| +that contain even more functionality than is
|
| +described here.)
|
| +
|
| +### Initialization
|
| +
|
| +The standard initialization formula is:
|
| +
|
| +```
|
| + import TestGyp
|
| + test = TestGyp.TestGyp()
|
| +```
|
| +
|
| +This copies the contents of the directory tree in which
|
| +the test script lives to a temporary directory for execution,
|
| +and arranges for the temporary directory's removal on exit.
|
| +
|
| +By default, any comparisons of output or file contents
|
| +must be exact matches for the test to pass.
|
| +If you need to use regular expressions for matches,
|
| +a useful alternative initialization is:
|
| +
|
| +```
|
| + import TestGyp
|
| + test = TestGyp.TestGyp(match = TestGyp.match_re,
|
| + diff = TestGyp.diff_re)`
|
| +```
|
| +
|
| +### Running GYP
|
| +
|
| +The canonical invocation is to simply specify the `.gyp` file to be executed:
|
| +
|
| +```
|
| + test.run_gyp('file.gyp')
|
| +```
|
| +
|
| +Additional GYP arguments may be specified:
|
| +
|
| +```
|
| + test.run_gyp('file.gyp', arguments=['arg1', 'arg2', ...])
|
| +```
|
| +
|
| +To execute GYP from a subdirectory (where, presumably, the specified file
|
| +lives):
|
| +
|
| +```
|
| + test.run_gyp('file.gyp', chdir='subdir')
|
| +```
|
| +
|
| +### Running the build tool
|
| +
|
| +Running the build tool requires passing in a `.gyp` file, which may be used to
|
| +calculate the name of a specific build configuration file (such as a MSVS
|
| +solution file corresponding to the `.gyp` file).
|
| +
|
| +There are several different `.build_*()` methods for invoking different types
|
| +of builds.
|
| +
|
| +To invoke a build tool with an explicit `all` target (or equivalent):
|
| +
|
| +```
|
| + test.build_all('file.gyp')
|
| +```
|
| +
|
| +To invoke a build tool with its default behavior (for example, executing `make`
|
| +with no targets specified):
|
| +
|
| +```
|
| + test.build_default('file.gyp')
|
| +```
|
| +
|
| +To invoke a build tool with an explicit specified target:
|
| +
|
| +```
|
| + test.build_target('file.gyp', 'target')
|
| +```
|
| +
|
| +### Running executables
|
| +
|
| +The most useful method executes a program built by the GYP-generated
|
| +configuration:
|
| +
|
| +```
|
| + test.run_built_executable('program')
|
| +```
|
| +
|
| +The `.run_built_executable()` method will account for the actual built target
|
| +output location for the build tool being tested, as well as tack on any
|
| +necessary executable file suffix for the platform (for example `.exe` on
|
| +Windows).
|
| +
|
| +`stdout=` and `stderr=` keyword arguments specify expected standard output and
|
| +error output, respectively. Failure to match these (if specified) will cause
|
| +the test to fail. An explicit `None` value will suppress that verification:
|
| +
|
| +```
|
| + test.run_built_executable('program',
|
| + stdout="expect this output\n",
|
| + stderr=None)
|
| +```
|
| +
|
| +Note that the default values are `stdout=None` and `stderr=''` (that is, no
|
| +check for standard output, and error output must be empty).
|
| +
|
| +Arbitrary executables (not necessarily those built by GYP) can be executed with
|
| +the lower-level `.run()` method:
|
| +
|
| +```
|
| + test.run('program')
|
| +```
|
| +
|
| +The program must be in the local directory (that is, the temporary directory
|
| +for test execution) or be an absolute path name.
|
| +
|
| +### Fetching command output
|
| +
|
| +```
|
| + test.stdout()
|
| +```
|
| +
|
| +Returns the standard output from the most recent executed command (including
|
| +`.run_gyp()`, `.build_*()`, or `.run*()` methods).
|
| +
|
| +```
|
| + test.stderr()
|
| +```
|
| +
|
| +Returns the error output from the most recent executed command (including
|
| +`.run_gyp()`, `.build_*()`, or `.run*()` methods).
|
| +
|
| +### Verifying existence or non-existence of files or directories
|
| +
|
| +```
|
| + test.must_exist('file_or_dir')
|
| +```
|
| +
|
| +Verifies that the specified file or directory exists, and fails the test if it
|
| +doesn't.
|
| +
|
| +```
|
| + test.must_not_exist('file_or_dir')
|
| +```
|
| +
|
| +Verifies that the specified file or directory does not exist, and fails the
|
| +test if it does.
|
| +
|
| +### Verifying file contents
|
| +
|
| +```
|
| + test.must_match('file', 'expected content\n')
|
| +```
|
| +
|
| +Verifies that the content of the specified file match the expected string, and
|
| +fails the test if it does not. By default, the match must be exact, but
|
| +line-by-line regular expressions may be used if the `TestGyp` object was
|
| +initialized with `TestGyp.match_re`.
|
| +
|
| +```
|
| + test.must_not_match('file', 'expected content\n')
|
| +```
|
| +
|
| +Verifies that the content of the specified file does _not_ match the expected
|
| +string, and fails the test if it does. By default, the match must be exact,
|
| +but line-by-line regular expressions may be used if the `TestGyp` object was
|
| +initialized with `TestGyp.match_re`.
|
| +
|
| +```
|
| + test.must_contain('file', 'substring')
|
| +```
|
| +
|
| +Verifies that the specified file contains the specified substring, and fails
|
| +the test if it does not.
|
| +
|
| +```
|
| + test.must_not_contain('file', 'substring')
|
| +```
|
| +
|
| +Verifies that the specified file does not contain the specified substring, and
|
| +fails the test if it does.
|
| +
|
| +```
|
| + test.must_contain_all_lines(output, lines)
|
| +```
|
| +
|
| +Verifies that the output string contains all of the "lines" in the specified
|
| +list of lines. In practice, the lines can be any substring and need not be
|
| +`\n`-terminaed lines per se. If any line is missing, the test fails.
|
| +
|
| +```
|
| + test.must_not_contain_any_lines(output, lines)
|
| +```
|
| +
|
| +Verifies that the output string does _not_ contain any of the "lines" in the
|
| +specified list of lines. In practice, the lines can be any substring and need
|
| +not be `\n`-terminaed lines per se. If any line exists in the output string,
|
| +the test fails.
|
| +
|
| +```
|
| + test.must_contain_any_line(output, lines)
|
| +```
|
| +
|
| +Verifies that the output string contains at least one of the "lines" in the
|
| +specified list of lines. In practice, the lines can be any substring and need
|
| +not be `\n`-terminaed lines per se. If none of the specified lines is present,
|
| +the test fails.
|
| +
|
| +### Reading file contents
|
| +
|
| +```
|
| + test.read('file')
|
| +```
|
| +
|
| +Returns the contents of the specified file. Directory elements contained in a
|
| +list will be joined:
|
| +
|
| +```
|
| + test.read(['subdir', 'file'])
|
| +```
|
| +
|
| +### Test success or failure
|
| +
|
| +```
|
| + test.fail_test()
|
| +```
|
| +
|
| +Fails the test, reporting `FAILED` on standard output and exiting with an exit
|
| +status of `1`.
|
| +
|
| +```
|
| + test.pass_test()
|
| +```
|
| +
|
| +Passes the test, reporting `PASSED` on standard output and exiting with an exit
|
| +status of `0`.
|
| +
|
| +```
|
| + test.no_result()
|
| +```
|
| +
|
| +Indicates the test had no valid result (i.e., the conditions could not be
|
| +tested because of an external factor like a full file system). Reports `NO
|
| +RESULT` on standard output and exits with a status of `2`.
|
|
|