| OLD | NEW |
| (Empty) |
| 1 # GYP (Generate Your Projects) Tests | |
| 2 | |
| 3 -- | |
| 4 | |
| 5 Status: Draft (as of 2009-08-18) | |
| 6 | |
| 7 Steven Knight <sgk@chromium.org> | |
| 8 _et al._ | |
| 9 | |
| 10 Modified: 2009-08-18 | |
| 11 | |
| 12 [TOC] | |
| 13 | |
| 14 ## Introduction | |
| 15 | |
| 16 This document describes the GYP testing infrastructure, | |
| 17 as provided by the `TestGyp.py` module. | |
| 18 | |
| 19 These tests emphasize testing the _behavior_ of the | |
| 20 various GYP-generated build configurations: | |
| 21 Visual Studio, Xcode, SCons, Make, etc. | |
| 22 The goal is _not_ to test the output of the GYP generators by, | |
| 23 for example, comparing a GYP-generated Makefile | |
| 24 against a set of known "golden" Makefiles | |
| 25 (although the testing infrastructure could | |
| 26 be used to write those kinds of tests). | |
| 27 The idea is that the generated build configuration files | |
| 28 could be completely written to add a feature or fix a bug | |
| 29 so long as they continue to support the functional behaviors | |
| 30 defined by the tests: building programs, shared libraries, etc. | |
| 31 | |
| 32 ## "Hello, world!" GYP test configuration | |
| 33 | |
| 34 Here is an actual test configuration, | |
| 35 a simple build of a C program to print `"Hello, world!"`. | |
| 36 | |
| 37 ``` | |
| 38 $ ls -l test/hello | |
| 39 total 20 | |
| 40 -rw-r--r-- 1 knight knight 312 Jul 30 20:22 gyptest-all.py | |
| 41 -rw-r--r-- 1 knight knight 307 Jul 30 20:22 gyptest-default.py | |
| 42 -rwxr-xr-x 1 knight knight 326 Jul 30 20:22 gyptest-target.py | |
| 43 -rw-r--r-- 1 knight knight 98 Jul 30 20:22 hello.c | |
| 44 -rw-r--r-- 1 knight knight 142 Jul 30 20:22 hello.gyp | |
| 45 $ | |
| 46 ``` | |
| 47 | |
| 48 The `gyptest-*.py` files are three separate tests (test scripts) | |
| 49 that use this configuration. The first one, `gyptest-all.py`, | |
| 50 looks like this: | |
| 51 | |
| 52 ``` | |
| 53 #!/usr/bin/env python | |
| 54 | |
| 55 """ | |
| 56 Verifies simplest-possible build of a "Hello, world!" program | |
| 57 using an explicit build target of 'all'. | |
| 58 """ | |
| 59 | |
| 60 import TestGyp | |
| 61 | |
| 62 test = TestGyp.TestGyp() | |
| 63 | |
| 64 test.run_gyp('hello.gyp') | |
| 65 | |
| 66 test.build_all('hello.gyp') | |
| 67 | |
| 68 test.run_built_executable('hello', stdout="Hello, world!\n") | |
| 69 | |
| 70 test.pass_test() | |
| 71 ``` | |
| 72 | |
| 73 The test script above runs GYP against the specified input file | |
| 74 (`hello.gyp`) to generate a build configuration. | |
| 75 It then tries to build the `'all'` target | |
| 76 (or its equivalent) using the generated build configuration. | |
| 77 Last, it verifies that the build worked as expected | |
| 78 by running the executable program (`hello`) | |
| 79 that was just presumably built by the generated configuration, | |
| 80 and verifies that the output from the program | |
| 81 matches the expected `stdout` string (`"Hello, world!\n"`). | |
| 82 | |
| 83 Which configuration is generated | |
| 84 (i.e., which build tool to test) | |
| 85 is specified when the test is run; | |
| 86 see the next section. | |
| 87 | |
| 88 Surrounding the functional parts of the test | |
| 89 described above are the header, | |
| 90 which should be basically the same for each test | |
| 91 (modulo a different description in the docstring): | |
| 92 | |
| 93 ``` | |
| 94 #!/usr/bin/env python | |
| 95 | |
| 96 """ | |
| 97 Verifies simplest-possible build of a "Hello, world!" program | |
| 98 using an explicit build target of 'all'. | |
| 99 """ | |
| 100 | |
| 101 import TestGyp | |
| 102 | |
| 103 test = TestGyp.TestGyp() | |
| 104 ``` | |
| 105 | |
| 106 Similarly, the footer should be the same in every test: | |
| 107 | |
| 108 ``` | |
| 109 test.pass_test() | |
| 110 ``` | |
| 111 | |
| 112 ## Running tests | |
| 113 | |
| 114 Test scripts are run by the `gyptest.py` script. | |
| 115 You can specify (an) explicit test script(s) to run: | |
| 116 | |
| 117 ``` | |
| 118 $ python gyptest.py test/hello/gyptest-all.py | |
| 119 PYTHONPATH=/home/knight/src/gyp/trunk/test/lib | |
| 120 TESTGYP_FORMAT=scons | |
| 121 /usr/bin/python test/hello/gyptest-all.py | |
| 122 PASSED | |
| 123 $ | |
| 124 ``` | |
| 125 | |
| 126 If you specify a directory, all test scripts | |
| 127 (scripts prefixed with `gyptest-`) underneath | |
| 128 the directory will be run: | |
| 129 | |
| 130 ``` | |
| 131 $ python gyptest.py test/hello | |
| 132 PYTHONPATH=/home/knight/src/gyp/trunk/test/lib | |
| 133 TESTGYP_FORMAT=scons | |
| 134 /usr/bin/python test/hello/gyptest-all.py | |
| 135 PASSED | |
| 136 /usr/bin/python test/hello/gyptest-default.py | |
| 137 PASSED | |
| 138 /usr/bin/python test/hello/gyptest-target.py | |
| 139 PASSED | |
| 140 $ | |
| 141 ``` | |
| 142 | |
| 143 Or you can specify the `-a` option to run all scripts | |
| 144 in the tree: | |
| 145 | |
| 146 ``` | |
| 147 $ python gyptest.py -a | |
| 148 PYTHONPATH=/home/knight/src/gyp/trunk/test/lib | |
| 149 TESTGYP_FORMAT=scons | |
| 150 /usr/bin/python test/configurations/gyptest-configurations.py | |
| 151 PASSED | |
| 152 /usr/bin/python test/defines/gyptest-defines.py | |
| 153 PASSED | |
| 154 . | |
| 155 . | |
| 156 . | |
| 157 . | |
| 158 /usr/bin/python test/variables/gyptest-commands.py | |
| 159 PASSED | |
| 160 $ | |
| 161 ``` | |
| 162 | |
| 163 If any tests fail during the run, | |
| 164 the `gyptest.py` script will report them in a | |
| 165 summary at the end. | |
| 166 | |
| 167 ## Debugging tests | |
| 168 | |
| 169 Tests that create intermediate output do so under the gyp/out/testworkarea | |
| 170 directory. On test completion, intermediate output is cleaned up. To preserve | |
| 171 this output, set the environment variable PRESERVE=1. This can be handy to | |
| 172 inspect intermediate data when debugging a test. | |
| 173 | |
| 174 You can also set PRESERVE\_PASS=1, PRESERVE\_FAIL=1 or PRESERVE\_NO\_RESULT=1 | |
| 175 to preserve output for tests that fall into one of those categories. | |
| 176 | |
| 177 # Specifying the format (build tool) to use | |
| 178 | |
| 179 By default, the `gyptest.py` script will generate configurations for | |
| 180 the "primary" supported build tool for the platform you're on: | |
| 181 Visual Studio on Windows, | |
| 182 Xcode on Mac, | |
| 183 and (currently) SCons on Linux. | |
| 184 An alternate format (build tool) may be specified | |
| 185 using the `-f` option: | |
| 186 | |
| 187 ``` | |
| 188 $ python gyptest.py -f make test/hello/gyptest-all.py | |
| 189 PYTHONPATH=/home/knight/src/gyp/trunk/test/lib | |
| 190 TESTGYP_FORMAT=make | |
| 191 /usr/bin/python test/hello/gyptest-all.py | |
| 192 PASSED | |
| 193 $ | |
| 194 ``` | |
| 195 | |
| 196 Multiple tools may be specified in a single pass as | |
| 197 a comma-separated list: | |
| 198 | |
| 199 ``` | |
| 200 $ python gyptest.py -f make,scons test/hello/gyptest-all.py | |
| 201 PYTHONPATH=/home/knight/src/gyp/trunk/test/lib | |
| 202 TESTGYP_FORMAT=make | |
| 203 /usr/bin/python test/hello/gyptest-all.py | |
| 204 PASSED | |
| 205 TESTGYP_FORMAT=scons | |
| 206 /usr/bin/python test/hello/gyptest-all.py | |
| 207 PASSED | |
| 208 $ | |
| 209 ``` | |
| 210 | |
| 211 ## Test script functions and methods | |
| 212 | |
| 213 The `TestGyp` class contains a lot of functionality | |
| 214 intended to make it easy to write tests. | |
| 215 This section describes the most useful pieces for GYP testing. | |
| 216 | |
| 217 (The `TestGyp` class is actually a subclass of more generic | |
| 218 `TestCommon` and `TestCmd` base classes | |
| 219 that contain even more functionality than is | |
| 220 described here.) | |
| 221 | |
| 222 ### Initialization | |
| 223 | |
| 224 The standard initialization formula is: | |
| 225 | |
| 226 ``` | |
| 227 import TestGyp | |
| 228 test = TestGyp.TestGyp() | |
| 229 ``` | |
| 230 | |
| 231 This copies the contents of the directory tree in which | |
| 232 the test script lives to a temporary directory for execution, | |
| 233 and arranges for the temporary directory's removal on exit. | |
| 234 | |
| 235 By default, any comparisons of output or file contents | |
| 236 must be exact matches for the test to pass. | |
| 237 If you need to use regular expressions for matches, | |
| 238 a useful alternative initialization is: | |
| 239 | |
| 240 ``` | |
| 241 import TestGyp | |
| 242 test = TestGyp.TestGyp(match = TestGyp.match_re, | |
| 243 diff = TestGyp.diff_re)` | |
| 244 ``` | |
| 245 | |
| 246 ### Running GYP | |
| 247 | |
| 248 The canonical invocation is to simply specify the `.gyp` file to be executed: | |
| 249 | |
| 250 ``` | |
| 251 test.run_gyp('file.gyp') | |
| 252 ``` | |
| 253 | |
| 254 Additional GYP arguments may be specified: | |
| 255 | |
| 256 ``` | |
| 257 test.run_gyp('file.gyp', arguments=['arg1', 'arg2', ...]) | |
| 258 ``` | |
| 259 | |
| 260 To execute GYP from a subdirectory (where, presumably, the specified file | |
| 261 lives): | |
| 262 | |
| 263 ``` | |
| 264 test.run_gyp('file.gyp', chdir='subdir') | |
| 265 ``` | |
| 266 | |
| 267 ### Running the build tool | |
| 268 | |
| 269 Running the build tool requires passing in a `.gyp` file, which may be used to | |
| 270 calculate the name of a specific build configuration file (such as a MSVS | |
| 271 solution file corresponding to the `.gyp` file). | |
| 272 | |
| 273 There are several different `.build_*()` methods for invoking different types | |
| 274 of builds. | |
| 275 | |
| 276 To invoke a build tool with an explicit `all` target (or equivalent): | |
| 277 | |
| 278 ``` | |
| 279 test.build_all('file.gyp') | |
| 280 ``` | |
| 281 | |
| 282 To invoke a build tool with its default behavior (for example, executing `make` | |
| 283 with no targets specified): | |
| 284 | |
| 285 ``` | |
| 286 test.build_default('file.gyp') | |
| 287 ``` | |
| 288 | |
| 289 To invoke a build tool with an explicit specified target: | |
| 290 | |
| 291 ``` | |
| 292 test.build_target('file.gyp', 'target') | |
| 293 ``` | |
| 294 | |
| 295 ### Running executables | |
| 296 | |
| 297 The most useful method executes a program built by the GYP-generated | |
| 298 configuration: | |
| 299 | |
| 300 ``` | |
| 301 test.run_built_executable('program') | |
| 302 ``` | |
| 303 | |
| 304 The `.run_built_executable()` method will account for the actual built target | |
| 305 output location for the build tool being tested, as well as tack on any | |
| 306 necessary executable file suffix for the platform (for example `.exe` on | |
| 307 Windows). | |
| 308 | |
| 309 `stdout=` and `stderr=` keyword arguments specify expected standard output and | |
| 310 error output, respectively. Failure to match these (if specified) will cause | |
| 311 the test to fail. An explicit `None` value will suppress that verification: | |
| 312 | |
| 313 ``` | |
| 314 test.run_built_executable('program', | |
| 315 stdout="expect this output\n", | |
| 316 stderr=None) | |
| 317 ``` | |
| 318 | |
| 319 Note that the default values are `stdout=None` and `stderr=''` (that is, no | |
| 320 check for standard output, and error output must be empty). | |
| 321 | |
| 322 Arbitrary executables (not necessarily those built by GYP) can be executed with | |
| 323 the lower-level `.run()` method: | |
| 324 | |
| 325 ``` | |
| 326 test.run('program') | |
| 327 ``` | |
| 328 | |
| 329 The program must be in the local directory (that is, the temporary directory | |
| 330 for test execution) or be an absolute path name. | |
| 331 | |
| 332 ### Fetching command output | |
| 333 | |
| 334 ``` | |
| 335 test.stdout() | |
| 336 ``` | |
| 337 | |
| 338 Returns the standard output from the most recent executed command (including | |
| 339 `.run_gyp()`, `.build_*()`, or `.run*()` methods). | |
| 340 | |
| 341 ``` | |
| 342 test.stderr() | |
| 343 ``` | |
| 344 | |
| 345 Returns the error output from the most recent executed command (including | |
| 346 `.run_gyp()`, `.build_*()`, or `.run*()` methods). | |
| 347 | |
| 348 ### Verifying existence or non-existence of files or directories | |
| 349 | |
| 350 ``` | |
| 351 test.must_exist('file_or_dir') | |
| 352 ``` | |
| 353 | |
| 354 Verifies that the specified file or directory exists, and fails the test if it | |
| 355 doesn't. | |
| 356 | |
| 357 ``` | |
| 358 test.must_not_exist('file_or_dir') | |
| 359 ``` | |
| 360 | |
| 361 Verifies that the specified file or directory does not exist, and fails the | |
| 362 test if it does. | |
| 363 | |
| 364 ### Verifying file contents | |
| 365 | |
| 366 ``` | |
| 367 test.must_match('file', 'expected content\n') | |
| 368 ``` | |
| 369 | |
| 370 Verifies that the content of the specified file match the expected string, and | |
| 371 fails the test if it does not. By default, the match must be exact, but | |
| 372 line-by-line regular expressions may be used if the `TestGyp` object was | |
| 373 initialized with `TestGyp.match_re`. | |
| 374 | |
| 375 ``` | |
| 376 test.must_not_match('file', 'expected content\n') | |
| 377 ``` | |
| 378 | |
| 379 Verifies that the content of the specified file does _not_ match the expected | |
| 380 string, and fails the test if it does. By default, the match must be exact, | |
| 381 but line-by-line regular expressions may be used if the `TestGyp` object was | |
| 382 initialized with `TestGyp.match_re`. | |
| 383 | |
| 384 ``` | |
| 385 test.must_contain('file', 'substring') | |
| 386 ``` | |
| 387 | |
| 388 Verifies that the specified file contains the specified substring, and fails | |
| 389 the test if it does not. | |
| 390 | |
| 391 ``` | |
| 392 test.must_not_contain('file', 'substring') | |
| 393 ``` | |
| 394 | |
| 395 Verifies that the specified file does not contain the specified substring, and | |
| 396 fails the test if it does. | |
| 397 | |
| 398 ``` | |
| 399 test.must_contain_all_lines(output, lines) | |
| 400 ``` | |
| 401 | |
| 402 Verifies that the output string contains all of the "lines" in the specified | |
| 403 list of lines. In practice, the lines can be any substring and need not be | |
| 404 `\n`-terminaed lines per se. If any line is missing, the test fails. | |
| 405 | |
| 406 ``` | |
| 407 test.must_not_contain_any_lines(output, lines) | |
| 408 ``` | |
| 409 | |
| 410 Verifies that the output string does _not_ contain any of the "lines" in the | |
| 411 specified list of lines. In practice, the lines can be any substring and need | |
| 412 not be `\n`-terminaed lines per se. If any line exists in the output string, | |
| 413 the test fails. | |
| 414 | |
| 415 ``` | |
| 416 test.must_contain_any_line(output, lines) | |
| 417 ``` | |
| 418 | |
| 419 Verifies that the output string contains at least one of the "lines" in the | |
| 420 specified list of lines. In practice, the lines can be any substring and need | |
| 421 not be `\n`-terminaed lines per se. If none of the specified lines is present, | |
| 422 the test fails. | |
| 423 | |
| 424 ### Reading file contents | |
| 425 | |
| 426 ``` | |
| 427 test.read('file') | |
| 428 ``` | |
| 429 | |
| 430 Returns the contents of the specified file. Directory elements contained in a | |
| 431 list will be joined: | |
| 432 | |
| 433 ``` | |
| 434 test.read(['subdir', 'file']) | |
| 435 ``` | |
| 436 | |
| 437 ### Test success or failure | |
| 438 | |
| 439 ``` | |
| 440 test.fail_test() | |
| 441 ``` | |
| 442 | |
| 443 Fails the test, reporting `FAILED` on standard output and exiting with an exit | |
| 444 status of `1`. | |
| 445 | |
| 446 ``` | |
| 447 test.pass_test() | |
| 448 ``` | |
| 449 | |
| 450 Passes the test, reporting `PASSED` on standard output and exiting with an exit | |
| 451 status of `0`. | |
| 452 | |
| 453 ``` | |
| 454 test.no_result() | |
| 455 ``` | |
| 456 | |
| 457 Indicates the test had no valid result (i.e., the conditions could not be | |
| 458 tested because of an external factor like a full file system). Reports `NO | |
| 459 RESULT` on standard output and exits with a status of `2`. | |
| OLD | NEW |