diff options
Diffstat (limited to 'third_party/libwebrtc/tools_webrtc/mb')
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/OWNERS | 1 | ||||
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/PRESUBMIT.py | 51 | ||||
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/README.md | 22 | ||||
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/docs/README.md | 4 | ||||
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/docs/design_spec.md | 426 | ||||
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/docs/user_guide.md | 298 | ||||
-rwxr-xr-x | third_party/libwebrtc/tools_webrtc/mb/mb | 8 | ||||
-rwxr-xr-x | third_party/libwebrtc/tools_webrtc/mb/mb.bat | 6 | ||||
-rwxr-xr-x | third_party/libwebrtc/tools_webrtc/mb/mb.py | 156 | ||||
-rw-r--r-- | third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl | 501 | ||||
-rwxr-xr-x | third_party/libwebrtc/tools_webrtc/mb/mb_unittest.py | 728 |
11 files changed, 2201 insertions, 0 deletions
diff --git a/third_party/libwebrtc/tools_webrtc/mb/OWNERS b/third_party/libwebrtc/tools_webrtc/mb/OWNERS new file mode 100644 index 0000000000..48e6927746 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/OWNERS @@ -0,0 +1 @@ +mbonadei@webrtc.org diff --git a/third_party/libwebrtc/tools_webrtc/mb/PRESUBMIT.py b/third_party/libwebrtc/tools_webrtc/mb/PRESUBMIT.py new file mode 100644 index 0000000000..dd957d0482 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/PRESUBMIT.py @@ -0,0 +1,51 @@ +#!/usr/bin/env vpython3 + +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + + +# Runs PRESUBMIT.py in py3 mode by git cl presubmit. +USE_PYTHON3 = True + + +def _CommonChecks(input_api, output_api): + results = [] + + # Run the MB unittests. + results.extend( + input_api.canned_checks.RunUnitTestsInDirectory(input_api, + output_api, + '.', + [r'^.+_unittest\.py$'], + skip_shebang_check=False, + run_on_python2=False)) + + # Validate the format of the mb_config.pyl file. + cmd = [input_api.python3_executable, 'mb.py', 'validate'] + kwargs = {'cwd': input_api.PresubmitLocalPath()} + results.extend(input_api.RunTests([ + input_api.Command(name='mb_validate', + cmd=cmd, kwargs=kwargs, + message=output_api.PresubmitError)])) + + results.extend( + input_api.canned_checks.CheckLongLines( + input_api, + output_api, + maxlen=80, + source_file_filter=lambda x: 'mb_config.pyl' in x.LocalPath())) + + return results + + +def CheckChangeOnUpload(input_api, output_api): + return _CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return _CommonChecks(input_api, output_api) diff --git a/third_party/libwebrtc/tools_webrtc/mb/README.md b/third_party/libwebrtc/tools_webrtc/mb/README.md new file mode 100644 index 0000000000..4e73a8e9fc --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/README.md @@ -0,0 +1,22 @@ +# MB - The Meta-Build wrapper + +MB is a simple wrapper intended to provide a uniform interface to either +GYP or GN, such that users and bots can call one script and not need to +worry about whether a given bot is meant to use GN or GYP. + +It supports two main functions: + +1. "gen" - the main `gyp_chromium` / `gn gen` invocation that generates the + Ninja files needed for the build. + +2. "analyze" - the step that takes a list of modified files and a list of + desired targets and reports which targets will need to be rebuilt. + +We also use MB as a forcing function to collect all of the different +build configurations that we actually support for Chromium builds into +one place, in `//tools/mb/mb_config.pyl`. + +For more information, see: + +* [The User Guide](docs/user_guide.md) +* [The Design Spec](docs/design_spec.md) diff --git a/third_party/libwebrtc/tools_webrtc/mb/docs/README.md b/third_party/libwebrtc/tools_webrtc/mb/docs/README.md new file mode 100644 index 0000000000..f29007d9ed --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/docs/README.md @@ -0,0 +1,4 @@ +# The MB (Meta-Build wrapper) documentation + +* The [User Guide](user_guide.md) +* The [Design Spec](design_spec.md) diff --git a/third_party/libwebrtc/tools_webrtc/mb/docs/design_spec.md b/third_party/libwebrtc/tools_webrtc/mb/docs/design_spec.md new file mode 100644 index 0000000000..0aaaf89f9b --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/docs/design_spec.md @@ -0,0 +1,426 @@ +# The MB (Meta-Build wrapper) design spec + +[TOC] + +## Intro + +MB is intended to address two major aspects of the GYP -> GN transition +for Chromium: + +1. "bot toggling" - make it so that we can easily flip a given bot + back and forth between GN and GYP. + +2. "bot configuration" - provide a single source of truth for all of + the different configurations (os/arch/`gyp_define` combinations) of + Chromium that are supported. + +MB must handle at least the `gen` and `analyze` steps on the bots, i.e., +we need to wrap both the `gyp_chromium` invocation to generate the +Ninja files, and the `analyze` step that takes a list of modified files +and a list of targets to build and returns which targets are affected by +the files. + +For more information on how to actually use MB, see +[the user guide](user_guide.md). + +## Design + +MB is intended to be as simple as possible, and to defer as much work as +possible to GN or GYP. It should live as a very simple Python wrapper +that offers little in the way of surprises. + +### Command line + +It is structured as a single binary that supports a list of subcommands: + +* `mb gen -c linux_rel_bot //out/Release` +* `mb analyze -m tryserver.chromium.linux -b linux_rel /tmp/input.json /tmp/output.json` + +### Configurations + +`mb` will first look for a bot config file in a set of different locations +(initially just in //ios/build/bots). Bot config files are JSON files that +contain keys for 'GYP_DEFINES' (a list of strings that will be joined together +with spaces and passed to GYP, or a dict that will be similarly converted), +'gn_args' (a list of strings that will be joined together), and an +'mb_type' field that says whether to use GN or GYP. Bot config files +require the full list of settings to be given explicitly. + +If no matching bot config file is found, `mb` looks in the +`//tools/mb/mb_config.pyl` config file to determine whether to use GYP or GN +for a particular build directory, and what set of flags (`GYP_DEFINES` or `gn +args`) to use. + +A config can either be specified directly (useful for testing) or by specifying +the builder group name and builder name (useful on the bots so that they do not +need to specify a config directly and can be hidden from the details). + +See the [user guide](user_guide.md#mb_config.pyl) for details. + +### Handling the analyze step + +The interface to `mb analyze` is described in the +[user\_guide](user_guide.md#mb_analyze). + +The way analyze works can be subtle and complicated (see below). + +Since the interface basically mirrors the way the "analyze" step on the bots +invokes `gyp_chromium` today, when the config is found to be a gyp config, +the arguments are passed straight through. + +It implements the equivalent functionality in GN by calling `gn refs +[list of files] --type=executable --all --as=output` and filtering the +output to match the list of targets. + +## Analyze + +The goal of the `analyze` step is to speed up the cycle time of the try servers +by only building and running the tests affected by the files in a patch, rather +than everything that might be out of date. Doing this ends up being tricky. + +We start with the following requirements and observations: + +* In an ideal (un-resource-constrained) world, we would build and test + everything that a patch affected on every patch. This does not + necessarily mean that we would build 'all' on every patch (see below). + +* In the real world, however, we do not have an infinite number of machines, + and try jobs are not infinitely fast, so we need to balance the desire + to get maximum test coverage against the desire to have reasonable cycle + times, given the number of machines we have. + +* Also, since we run most try jobs against tip-of-tree Chromium, by + the time one job completes on the bot, new patches have probably landed, + rendering the build out of date. + +* This means that the next try job may have to do a build that is out of + date due to a combination of files affected by a given patch, and files + affected for unrelated reasons. We want to rebuild and test only the + targets affected by the patch, so that we don't blame or punish the + patch author for unrelated changes. + +So: + +1. We need a way to indicate which changed files we care about and which + we don't (the affected files of a patch). + +2. We need to know which tests we might potentially want to run, and how + those are mapped onto build targets. For some kinds of tests (like + GTest-based tests), the mapping is 1:1 - if you want to run base_unittests, + you need to build base_unittests. For others (like the telemetry and + layout tests), you might need to build several executables in order to + run the tests, and that mapping might best be captured by a *meta* + target (a GN group or a GYP 'none' target like `webkit_tests`) that + depends on the right list of files. Because the GN and GYP files know + nothing about test steps, we have to have some way of mapping back + and forth between test steps and build targets. That mapping + is *not* currently available to MB (or GN or GYP), and so we have to + enough information to make it possible for the caller to do the mapping. + +3. We might also want to know when test targets are affected by data files + that aren't compiled (python scripts, or the layout tests themselves). + There's no good way to do this in GYP, but GN supports this. + +4. We also want to ensure that particular targets still compile even if they + are not actually tested; consider testing the installers themselves, or + targets that don't yet have good test coverage. We might want to use meta + targets for this purpose as well. + +5. However, for some meta targets, we don't necessarily want to rebuild the + meta target itself, perhaps just the dependencies of the meta target that + are affected by the patch. For example, if you have a meta target like + `blink_tests` that might depend on ten different test binaries. If a patch + only affects one of them (say `wtf_unittests`), you don't want to + build `blink_tests`, because that might actually also build the other nine + targets. In other words, some meta targets are *prunable*. + +6. As noted above, in the ideal case we actually have enough resources and + things are fast enough that we can afford to build everything affected by a + patch, but listing every possible target explicitly would be painful. The + GYP and GN Ninja generators provide an 'all' target that captures (nearly, + see [crbug.com/503241](crbug.com/503241)) everything, but unfortunately + neither GN nor GYP actually represents 'all' as a meta target in the build + graph, so we will need to write code to handle that specially. + +7. In some cases, we will not be able to correctly analyze the build graph to + determine the impact of a patch, and need to bail out (e.g,. if you change a + build file itself, it may not be easy to tell how that affects the graph). + In that case we should simply build and run everything. + +The interaction between 2) and 5) means that we need to treat meta targets +two different ways, and so we need to know which targets should be +pruned in the sense of 5) and which targets should be returned unchanged +so that we can map them back to the appropriate tests. + +So, we need three things as input: + +* `files`: the list of files in the patch +* `test_targets`: the list of ninja targets which, if affected by a patch, + should be reported back so that we can map them back to the appropriate + tests to run. Any meta targets in this list should *not* be pruned. +* `additional_compile_targets`: the list of ninja targets we wish to compile + *in addition to* the list in `test_targets`. Any meta targets + present in this list should be pruned (we don't need to return the + meta targets because they aren't mapped back to tests, and we don't want + to build them because we might build too much). + +We can then return two lists as output: + +* `compile_targets`, which is a list of pruned targets to be + passed to Ninja to build. It is acceptable to replace a list of + pruned targets by a meta target if it turns out that all of the + dependendencies of the target are affected by the patch (i.e., + all ten binaries that blink_tests depends on), but doing so is + not required. +* `test_targets`, which is a list of unpruned targets to be mapped + back to determine which tests to run. + +There may be substantial overlap between the two lists, but there is +no guarantee that one is a subset of the other and the two cannot be +used interchangeably or merged together without losing information and +causing the wrong thing to happen. + +The implementation is responsible for recognizing 'all' as a magic string +and mapping it onto the list of all root nodes in the build graph. + +There may be files listed in the input that don't actually exist in the build +graph: this could be either the result of an error (the file should be in the +build graph, but isn't), or perfectly fine (the file doesn't affect the build +graph at all). We can't tell these two apart, so we should ignore missing +files. + +There may be targets listed in the input that don't exist in the build +graph; unlike missing files, this can only indicate a configuration error, +and so we should return which targets are missing so the caller can +treat this as an error, if so desired. + +Any of the three inputs may be an empty list: + +* It normally doesn't make sense to call analyze at all if no files + were modified, but in rare cases we can hit a race where we try to + test a patch after it has already been committed, in which case + the list of modified files is empty. We should return 'no dependency' + in that case. + +* Passing an empty list for one or the other of test_targets and + additional_compile_targets is perfectly sensible: in the former case, + it can indicate that you don't want to run any tests, and in the latter, + it can indicate that you don't want to do build anything else in + addition to the test targets. + +* It doesn't make sense to call analyze if you don't want to compile + anything at all, so passing [] for both test_targets and + additional_compile_targets should probably return an error. + +In the output case, an empty list indicates that there was nothing to +build, or that there were no affected test targets as appropriate. + +Note that passing no arguments to Ninja is equivalent to passing +`all` to Ninja (at least given how GN and GYP work); however, we +don't want to take advantage of this in most cases because we don't +actually want to build every out of date target, only the targets +potentially affected by the files. One could try to indicate +to analyze that we wanted to use no arguments instead of an empty +list, but using the existing fields for this seems fragile and/or +confusing, and adding a new field for this seems unwarranted at this time. + +There is an "error" field in case something goes wrong (like the +empty file list case, above, or an internal error in MB/GYP/GN). The +analyze code should also return an error code to the shell if appropriate +to indicate that the command failed. + +In the case where build files themselves are modified and analyze may +not be able to determine a correct answer (point 7 above, where we return +"Found dependency (all)"), we should also return the `test_targets` unmodified +and return the union of `test_targets` and `additional_compile_targets` for +`compile_targets`, to avoid confusion. + +### Examples + +Continuing the example given above, suppose we have the following build +graph: + +* `blink_tests` is a meta target that depends on `webkit_unit_tests`, + `wtf_unittests`, and `webkit_tests` and represents all of the targets + needed to fully test Blink. Each of those is a separate test step. +* `webkit_tests` is also a meta target; it depends on `content_shell` + and `image_diff`. +* `base_unittests` is a separate test binary. +* `wtf_unittests` depends on `Assertions.cpp` and `AssertionsTest.cpp`. +* `webkit_unit_tests` depends on `WebNode.cpp` and `WebNodeTest.cpp`. +* `content_shell` depends on `WebNode.cpp` and `Assertions.cpp`. +* `base_unittests` depends on `logging.cc` and `logging_unittest.cc`. + +#### Example 1 + +We wish to run 'wtf_unittests' and 'webkit_tests' on a bot, but not +compile any additional targets. + +If a patch touches WebNode.cpp, then analyze gets as input: + + { + "files": ["WebNode.cpp"], + "test_targets": ["wtf_unittests", "webkit_tests"], + "additional_compile_targets": [] + } + +and should return as output: + + { + "status": "Found dependency", + "compile_targets": ["webkit_unit_tests"], + "test_targets": ["webkit_tests"] + } + +Note how `webkit_tests` was pruned in compile_targets but not in test_targets. + +#### Example 2 + +Using the same patch as Example 1, assume we wish to run only `wtf_unittests`, +but additionally build everything needed to test Blink (`blink_tests`): + +We pass as input: + + { + "files": ["WebNode.cpp"], + "test_targets": ["wtf_unittests"], + "additional_compile_targets": ["blink_tests"] + } + +And should get as output: + + { + "status": "Found dependency", + "compile_targets": ["webkit_unit_tests"], + "test_targets": [] + } + +Here `blink_tests` was pruned in the output compile_targets, and +test_targets was empty, since blink_tests was not listed in the input +test_targets. + +#### Example 3 + +Build everything, but do not run any tests. + +Input: + + { + "files": ["WebNode.cpp"], + "test_targets": [], + "additional_compile_targets": ["all"] + } + +Output: + + { + "status": "Found dependency", + "compile_targets": ["webkit_unit_tests", "content_shell"], + "test_targets": [] + } + +#### Example 4 + +Same as Example 2, but a build file was modified instead of a source file. + +Input: + + { + "files": ["BUILD.gn"], + "test_targets": ["wtf_unittests"], + "additional_compile_targets": ["blink_tests"] + } + +Output: + + { + "status": "Found dependency (all)", + "compile_targets": ["webkit_unit_tests", "wtf_unittests"], + "test_targets": ["wtf_unittests"] + } + +test_targets was returned unchanged, compile_targets was pruned. + +## Random Requirements and Rationale + +This section is collection of semi-organized notes on why MB is the way +it is ... + +### in-tree or out-of-tree + +The first issue is whether or not this should exist as a script in +Chromium at all; an alternative would be to simply change the bot +configurations to know whether to use GYP or GN, and which flags to +pass. + +That would certainly work, but experience over the past two years +suggests a few things: + + * we should push as much logic as we can into the source repositories + so that they can be versioned and changed atomically with changes to + the product code; having to coordinate changes between src/ and + build/ is at best annoying and can lead to weird errors. + * the infra team would really like to move to providing + product-independent services (i.e., not have to do one thing for + Chromium, another for NaCl, a third for V8, etc.). + * we found that during the SVN->GIT migration the ability to flip bot + configurations between the two via changes to a file in chromium + was very useful. + +All of this suggests that the interface between bots and Chromium should +be a simple one, hiding as much of the chromium logic as possible. + +### Why not have MB be smarter about de-duping flags? + +This just adds complexity to the MB implementation, and duplicates logic +that GYP and GN already have to support anyway; in particular, it might +require MB to know how to parse GYP and GN values. The belief is that +if MB does *not* do this, it will lead to fewer surprises. + +It will not be hard to change this if need be. + +### Integration w/ gclient runhooks + +On the bots, we will disable `gyp_chromium` as part of runhooks (using +`GYP_CHROMIUM_NO_ACTION=1`), so that mb shows up as a separate step. + +At the moment, we expect most developers to either continue to use +`gyp_chromium` in runhooks or to disable at as above if they have no +use for GYP at all. We may revisit how this works once we encourage more +people to use GN full-time (i.e., we might take `gyp_chromium` out of +runhooks altogether). + +### Config per flag set or config per (os/arch/flag set)? + +Currently, mb_config.pyl does not specify the host_os, target_os, host_cpu, or +target_cpu values for every config that Chromium runs on, it only specifies +them for when the values need to be explicitly set on the command line. + +Instead, we have one config per unique combination of flags only. + +In other words, rather than having `linux_rel_bot`, `win_rel_bot`, and +`mac_rel_bot`, we just have `rel_bot`. + +This design allows us to determine easily all of the different sets +of flags that we need to support, but *not* which flags are used on which +host/target combinations. + +It may be that we should really track the latter. Doing so is just a +config file change, however. + +### Non-goals + +* MB is not intended to replace direct invocation of GN or GYP for + complicated build scenarios (aka ChromeOS), where multiple flags need + to be set to user-defined paths for specific toolchains (e.g., where + ChromeOS needs to specify specific board types and compilers). + +* MB is not intended at this time to be something developers use frequently, + or to add a lot of features to. We hope to be able to get rid of it once + the GYP->GN migration is done, and so we should not add things for + developers that can't easily be added to GN itself. + +* MB is not intended to replace the + [CR tool](https://code.google.com/p/chromium/wiki/CRUserManual). Not + only is it only intended to replace the gyp\_chromium part of `'gclient + runhooks'`, it is not really meant as a developer-facing tool. diff --git a/third_party/libwebrtc/tools_webrtc/mb/docs/user_guide.md b/third_party/libwebrtc/tools_webrtc/mb/docs/user_guide.md new file mode 100644 index 0000000000..8c66cd328c --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/docs/user_guide.md @@ -0,0 +1,298 @@ +# The MB (Meta-Build wrapper) user guide + +[TOC] + +## Introduction + +`mb` is a simple python wrapper around the GYP and GN meta-build tools to +be used as part of the GYP->GN migration. + +It is intended to be used by bots to make it easier to manage the configuration +each bot builds (i.e., the configurations can be changed from chromium +commits), and to consolidate the list of all of the various configurations +that Chromium is built in. + +Ideally this tool will no longer be needed after the migration is complete. + +For more discussion of MB, see also [the design spec](design_spec.md). + +## MB subcommands + +### `mb analyze` + +`mb analyze` is reponsible for determining what targets are affected by +a list of files (e.g., the list of files in a patch on a trybot): + +``` +mb analyze -c chromium_linux_rel //out/Release input.json output.json +``` + +Either the `-c/--config` flag or the `-m/--builder-group` and `-b/--builder` +flags must be specified so that `mb` can figure out which config to use. + +The first positional argument must be a GN-style "source-absolute" path +to the build directory. + +The second positional argument is a (normal) path to a JSON file containing +a single object with the following fields: + + * `files`: an array of the modified filenames to check (as paths relative to + the checkout root). + * `test_targets`: an array of (ninja) build targets that needed to run the + tests we wish to run. An empty array will be treated as if there are + no tests that will be run. + * `additional_compile_targets`: an array of (ninja) build targets that + reflect the stuff we might want to build *in addition to* the list + passed in `test_targets`. Targets in this list will be treated + specially, in the following way: if a given target is a "meta" + (GN: group, GYP: none) target like 'blink_tests' or + 'chromium_builder_tests', or even the ninja-specific 'all' target, + then only the *dependencies* of the target that are affected by + the modified files will be rebuilt (not the target itself, which + might also cause unaffected dependencies to be rebuilt). An empty + list will be treated as if there are no additional targets to build. + Empty lists for both `test_targets` and `additional_compile_targets` + would cause no work to be done, so will result in an error. + * `targets`: a legacy field that resembled a union of `compile_targets` + and `test_targets`. Support for this field will be removed once the + bots have been updated to use compile_targets and test_targets instead. + +The third positional argument is a (normal) path to where mb will write +the result, also as a JSON object. This object may contain the following +fields: + + * `error`: this should only be present if something failed. + * `compile_targets`: the list of ninja targets that should be passed + directly to the corresponding ninja / compile.py invocation. This + list may contain entries that are *not* listed in the input (see + the description of `additional_compile_targets` above and + [design_spec.md](the design spec) for how this works). + * `invalid_targets`: a list of any targets that were passed in + either of the input lists that weren't actually found in the graph. + * `test_targets`: the subset of the input `test_targets` that are + potentially out of date, indicating that the matching test steps + should be re-run. + * `targets`: a legacy field that indicates the subset of the input `targets` + that depend on the input `files`. + * `build_targets`: a legacy field that indicates the minimal subset of + targets needed to build all of `targets` that were affected. + * `status`: a field containing one of three strings: + + * `"Found dependency"` (build the `compile_targets`) + * `"No dependency"` (i.e., no build needed) + * `"Found dependency (all)"` (`test_targets` is returned as-is; + `compile_targets` should contain the union of `test_targets` and + `additional_compile_targets`. In this case the targets do not + need to be pruned). + +See [design_spec.md](the design spec) for more details and examples; the +differences can be subtle. We won't even go into how the `targets` and +`build_targets` differ from each other or from `compile_targets` and +`test_targets`. + +The `-b/--builder`, `-c/--config`, `-f/--config-file`, `-m/--builder-group`, +`-q/--quiet`, and `-v/--verbose` flags work as documented for `mb gen`. + +### `mb audit` + +`mb audit` is used to track the progress of the GYP->GN migration. You can +use it to check a single builder group, or all the builder groups we care +about. +See `mb help audit` for more details (most people are not expected to care +about this). + +### `mb gen` + +`mb gen` is responsible for generating the Ninja files by invoking either GYP +or GN as appropriate. It takes arguments to specify a build config and +a directory, then runs GYP or GN as appropriate: + +``` +% mb gen -m tryserver.chromium.linux -b linux_rel //out/Release +% mb gen -c linux_rel_trybot //out/Release +``` + +Either the `-c/--config` flag or the `-m/--builder-group` and `-b/--builder` +flags must be specified so that `mb` can figure out which config to use. The +`--phase` flag must also be used with builders that have multiple +build/compile steps (and only with those builders). + +By default, MB will look for a bot config file under `//ios/build/bots` (see +[design_spec.md](the design spec) for details of how the bot config files +work). If no matching one is found, will then look in +`//tools/mb/mb_config.pyl` to look up the config information, but you can +specify a custom config file using the `-f/--config-file` flag. + +The path must be a GN-style "source-absolute" path (as above). + +You can pass the `-n/--dryrun` flag to mb gen to see what will happen without +actually writing anything. + +You can pass the `-q/--quiet` flag to get mb to be silent unless there is an +error, and pass the `-v/--verbose` flag to get mb to log all of the files +that are read and written, and all the commands that are run. + +If the build config will use the Goma distributed-build system, you can pass +the path to your Goma client in the `-g/--goma-dir` flag, and it will be +incorporated into the appropriate flags for GYP or GN as needed. + +If gen ends up using GYP, the path must have a valid GYP configuration as the +last component of the path (i.e., specify `//out/Release_x64`, not `//out`). +The gyp script defaults to `//build/gyp_chromium`, but can be overridden with +the `--gyp-script` flag, e.g. `--gyp-script=gypfiles/gyp_v8`. + +### `mb help` + +Produces help output on the other subcommands + +### `mb lookup` + +Prints what command will be run by `mb gen` (like `mb gen -n` but does +not require you to specify a path). + +The `-b/--builder`, `-c/--config`, `-f/--config-file`, `-m/--builder-group`, +`--phase`, `-q/--quiet`, and `-v/--verbose` flags work as documented for +`mb gen`. + +### `mb validate` + +Does internal checking to make sure the config file is syntactically +valid and that all of the entries are used properly. It does not validate +that the flags make sense, or that the builder names are legal or +comprehensive, but it does complain about configs and mixins that aren't +used. + +The `-f/--config-file` and `-q/--quiet` flags work as documented for +`mb gen`. + +This is mostly useful as a presubmit check and for verifying changes to +the config file. + +## Isolates and Swarming + +`mb gen` is also responsible for generating the `.isolate` and +`.isolated.gen.json` files needed to run test executables through swarming +in a GN build (in a GYP build, this is done as part of the compile step). + +If you wish to generate the isolate files, pass `mb gen` the +`--swarming-targets-file` command line argument; that arg should be a path +to a file containing a list of ninja build targets to compute the runtime +dependencies for (on Windows, use the ninja target name, not the file, so +`base_unittests`, not `base_unittests.exe`). + +MB will take this file, translate each build target to the matching GN +label (e.g., `base_unittests` -> `//base:base_unittests`, write that list +to a file called `runtime_deps` in the build directory, and pass that to +`gn gen $BUILD ... --runtime-deps-list-file=$BUILD/runtime_deps`. + +Once GN has computed the lists of runtime dependencies, MB will then +look up the command line for each target (currently this is hard-coded +in [mb.py](https://code.google.com/p/chromium/codesearch?q=mb.py#chromium/src/tools/mb/mb.py&q=mb.py%20GetIsolateCommand&sq=package:chromium&type=cs)), and write out the +matching `.isolate` and `.isolated.gen.json` files. + +## The `mb_config.pyl` config file + +The `mb_config.pyl` config file is intended to enumerate all of the +supported build configurations for Chromium. Generally speaking, you +should never need to (or want to) build a configuration that isn't +listed here, and so by using the configs in this file you can avoid +having to juggle long lists of GYP_DEFINES and gn args by hand. + +`mb_config.pyl` is structured as a file containing a single PYthon Literal +expression: a dictionary with three main keys, `builder_groups`, `configs` and +`mixins`. + +The `builder_groups` key contains a nested series of dicts containing mappings +of builder group -> builder -> config . This allows us to isolate the buildbot +recipes from the actual details of the configs. The config should either +be a single string value representing a key in the `configs` dictionary, +or a list of strings, each of which is a key in the `configs` dictionary; +the latter case is for builders that do multiple compiles with different +arguments in a single build, and must *only* be used for such builders +(where a --phase argument must be supplied in each lookup or gen call). + +The `configs` key points to a dictionary of named build configurations. + +There should be an key in this dict for every supported configuration +of Chromium, meaning every configuration we have a bot for, and every +configuration commonly used by develpers but that we may not have a bot +for. + +The value of each key is a list of "mixins" that will define what that +build_config does. Each item in the list must be an entry in the dictionary +value of the `mixins` key. + +Each mixin value is itself a dictionary that contains one or more of the +following keys: + + * `gyp_crosscompile`: a boolean; if true, GYP_CROSSCOMPILE=1 is set in + the environment and passed to GYP. + * `gyp_defines`: a string containing a list of GYP_DEFINES. + * `gn_args`: a string containing a list of values passed to gn --args. + * `mixins`: a list of other mixins that should be included. + * `type`: a string with either the value `gyp` or `gn`; + setting this indicates which meta-build tool to use. + +When `mb gen` or `mb analyze` executes, it takes a config name, looks it +up in the 'configs' dict, and then does a left-to-right expansion of the +mixins; gyp_defines and gn_args values are concatenated, and the type values +override each other. + +For example, if you had: + +``` +{ + 'configs`: { + 'linux_release_trybot': ['gyp_release', 'trybot'], + 'gn_shared_debug': None, + } + 'mixins': { + 'bot': { + 'gyp_defines': 'use_goma=1 dcheck_always_on=0', + 'gn_args': 'use_goma=true dcheck_always_on=false', + }, + 'debug': { + 'gn_args': 'is_debug=true', + }, + 'gn': {'type': 'gn'}, + 'gyp_release': { + 'mixins': ['release'], + 'type': 'gyp', + }, + 'release': { + 'gn_args': 'is_debug=false', + } + 'shared': { + 'gn_args': 'is_component_build=true', + 'gyp_defines': 'component=shared_library', + }, + 'trybot': { + 'gyp_defines': 'dcheck_always_on=1', + 'gn_args': 'dcheck_always_on=true', + } + } +} +``` + +and you ran `mb gen -c linux_release_trybot //out/Release`, it would +translate into a call to `gyp_chromium -G Release` with `GYP_DEFINES` set to +`"use_goma=true dcheck_always_on=false dcheck_always_on=true"`. + +(From that you can see that mb is intentionally dumb and does not +attempt to de-dup the flags, it lets gyp do that). + +## Debugging MB + +By design, MB should be simple enough that very little can go wrong. + +The most obvious issue is that you might see different commands being +run than you expect; running `'mb -v'` will print what it's doing and +run the commands; `'mb -n'` will print what it will do but *not* run +the commands. + +If you hit weirder things than that, add some print statements to the +python script, send a question to gn-dev@chromium.org, or +[file a bug](https://crbug.com/new) with the label +'mb' and cc: dpranke@chromium.org. + + diff --git a/third_party/libwebrtc/tools_webrtc/mb/mb b/third_party/libwebrtc/tools_webrtc/mb/mb new file mode 100755 index 0000000000..007d23cb64 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/mb @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +base_dir=$(dirname "$0") + +PYTHONDONTWRITEBYTECODE=1 exec python3 "$base_dir/mb.py" "$@" diff --git a/third_party/libwebrtc/tools_webrtc/mb/mb.bat b/third_party/libwebrtc/tools_webrtc/mb/mb.bat new file mode 100755 index 0000000000..1252560a08 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/mb.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +:: This is required with cygwin only. +PATH=%~dp0;%PATH% +set PYTHONDONTWRITEBYTECODE=1 +call vpython3 "%~dp0mb.py" %* diff --git a/third_party/libwebrtc/tools_webrtc/mb/mb.py b/third_party/libwebrtc/tools_webrtc/mb/mb.py new file mode 100755 index 0000000000..c23e69c09f --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/mb.py @@ -0,0 +1,156 @@ +#!/usr/bin/env vpython3 + +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +"""MB - the Meta-Build wrapper around GN. + +MB is a wrapper script for GN that can be used to generate build files +for sets of canned configurations and analyze them. +""" + +import os +import sys + +_SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +_SRC_DIR = os.path.dirname(os.path.dirname(_SCRIPT_DIR)) +sys.path.insert(0, _SRC_DIR) + +from tools.mb import mb + + +def _GetExecutable(target, platform): + executable_prefix = '.\\' if platform == 'win32' else './' + executable_suffix = '.exe' if platform == 'win32' else '' + return executable_prefix + target + executable_suffix + + +def main(args): + mbw = WebRTCMetaBuildWrapper() + return mbw.Main(args) + + +class WebRTCMetaBuildWrapper(mb.MetaBuildWrapper): + def __init__(self): + super().__init__() + # Make sure default_config and default_isolate_map are attributes of the + # parent class before changing their values. + # pylint: disable=access-member-before-definition + assert self.default_config + assert self.default_isolate_map + self.default_config = os.path.join(_SCRIPT_DIR, 'mb_config.pyl') + self.default_isolate_map = os.path.join(_SRC_DIR, 'infra', 'specs', + 'gn_isolate_map.pyl') + + def GetSwarmingCommand(self, target, vals): + isolate_map = self.ReadIsolateMap() + test_type = isolate_map[target]['type'] + + is_android = 'target_os="android"' in vals['gn_args'] + is_fuchsia = 'target_os="fuchsia"' in vals['gn_args'] + is_ios = 'target_os="ios"' in vals['gn_args'] + is_linux = self.platform.startswith('linux') and not is_android + is_win = self.platform.startswith('win') + + if test_type == 'nontest': + self.WriteFailureAndRaise('We should not be isolating %s.' % target, + output_path=None) + if test_type not in ('console_test_launcher', 'windowed_test_launcher', + 'non_parallel_console_test_launcher', 'raw', + 'additional_compile_target', 'junit_test', 'script'): + self.WriteFailureAndRaise('No command line for ' + '%s found (test type %s).' % + (target, test_type), + output_path=None) + + cmdline = [] + extra_files = [ + '../../.vpython3', + '../../testing/test_env.py', + ] + vpython_exe = 'vpython3' + + if isolate_map[target].get('script'): + cmdline += [ + vpython_exe, + '../../' + self.ToSrcRelPath(isolate_map[target]['script']) + ] + elif is_android: + cmdline += [ + 'luci-auth', 'context', '--', vpython_exe, + '../../build/android/test_wrapper/logdog_wrapper.py', '--target', + target, '--logdog-bin-cmd', + '../../.task_template_packages/logdog_butler', '--logcat-output-file', + '${ISOLATED_OUTDIR}/logcats', '--store-tombstones' + ] + elif is_ios or is_fuchsia or test_type == 'raw': + if is_win: + cmdline += ['bin\\run_{}.bat'.format(target)] + else: + cmdline += ['bin/run_{}'.format(target)] + else: + if isolate_map[target].get('use_webcam', False): + cmdline += [ + vpython_exe, '../../tools_webrtc/ensure_webcam_is_running.py' + ] + extra_files.append('../../tools_webrtc/ensure_webcam_is_running.py') + if isolate_map[target].get('use_pipewire', False): + cmdline += [vpython_exe, '../../tools_webrtc/configure_pipewire.py'] + extra_files.append('../../tools_webrtc/configure_pipewire.py') + + # is_linux uses use_ozone and x11 by default. + use_x11 = is_linux + + xvfb = use_x11 and test_type == 'windowed_test_launcher' + if xvfb: + cmdline += [vpython_exe, '../../testing/xvfb.py'] + extra_files.append('../../testing/xvfb.py') + else: + cmdline += [vpython_exe, '../../testing/test_env.py'] + + extra_files += [ + '../../third_party/gtest-parallel/gtest-parallel', + '../../third_party/gtest-parallel/gtest_parallel.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + ] + output_dir = '${ISOLATED_OUTDIR}/test_logs' + cmdline += [ + '../../tools_webrtc/gtest-parallel-wrapper.py', + '--output_dir=%s' % output_dir, + '--gtest_color=no', + ] + if test_type == 'non_parallel_console_test_launcher': + # Still use the gtest-parallel-wrapper.py script since we + # need it to run tests on swarming, but don't execute tests + # in parallel. + cmdline.append('--workers=1') + + asan = 'is_asan=true' in vals['gn_args'] + lsan = 'is_lsan=true' in vals['gn_args'] + msan = 'is_msan=true' in vals['gn_args'] + tsan = 'is_tsan=true' in vals['gn_args'] + sanitizer = asan or lsan or msan or tsan + if not sanitizer: + # Retry would hide most sanitizers detections. + cmdline.append('--retry_failed=3') + + cmdline.append(_GetExecutable(target, self.platform)) + + cmdline.extend([ + '--asan=%d' % asan, + '--lsan=%d' % lsan, + '--msan=%d' % msan, + '--tsan=%d' % tsan, + ]) + + cmdline += isolate_map[target].get('args', []) + + return cmdline, extra_files + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl b/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl new file mode 100644 index 0000000000..6aea3c9cba --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl @@ -0,0 +1,501 @@ +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +# FOR DETAILS ON THIS FILE SEE THE MAIN COPY IN //tools/mb/mb_config.pyl. +# This is configuration for standalone WebRTC bots. It is used to keep the bot +# configurations source-side instead of in the buildbot scripts. That makes it +# easy to try different configurations of GN args in tryjob patches. + +{ + # This is a map of buildbot builder group names -> buildbot builder names -> + # config names (where each config name is a key in the 'configs' dict, + # above). mb uses this dict to look up which config to use for a given bot. + # The builders should be sorted by the order they appear in the /builders + # page on the buildbots, *not* alphabetically. + 'builder_groups': { + # This is required because WebRTC mb.py overwrites the default configs + # and Chromium's mb.py checks the default config contains 'chromium'. + 'chromium': {}, + 'chromium.infra.codesearch': { + 'codesearch-gen-webrtc-android': { + 'android': 'android_debug_static_bot_arm', + }, + 'codesearch-gen-webrtc-linux': { + 'linux': 'codesearch_gen_linux_bot', + } + }, + 'client.webrtc': { + # Android + 'Android32': 'android_release_bot_arm', + 'Android32 (dbg)': 'android_debug_static_bot_arm', + 'Android32 (more configs)': { + 'bwe_test_logging': 'bwe_test_logging_android_arm', + 'dummy_audio_file_devices_no_protobuf': + 'dummy_audio_file_devices_no_protobuf_android_arm', + 'rtti_no_sctp': 'rtti_no_sctp_android_arm', + }, + 'Android32 Builder arm': 'android_pure_release_bot_arm', + 'Android32 Builder x86': 'android_release_bot_x86', + 'Android32 Builder x86 (dbg)': 'android_debug_static_bot_x86', + 'Android64': 'android_release_bot_arm64', + 'Android64 (dbg)': 'android_debug_static_bot_arm64', + 'Android64 Builder arm64': 'android_pure_release_bot_arm64', + 'Android64 Builder x64 (dbg)': 'android_debug_static_bot_x64', + + # Fuchsia + 'Fuchsia Builder': 'release_bot_x64_fuchsia', + 'Fuchsia Release': 'release_bot_x64_fuchsia', + + # Linux + # "More configs" bots will build all the following configs in sequence. + # This is using MB's "phases" feature. + 'Linux (more configs)': { + 'bwe_test_logging': 'bwe_test_logging_x64', + 'dummy_audio_file_devices_no_protobuf': + 'dummy_audio_file_devices_no_protobuf_x64', + 'rtti_no_sctp': 'rtti_no_sctp_x64', + }, + 'Linux Asan': 'asan_lsan_clang_release_bot_x64', + 'Linux MSan': 'msan_clang_release_bot_x64', + 'Linux Tsan v2': 'tsan_clang_release_bot_x64', + 'Linux UBSan': 'ubsan_clang_release_bot_x64', + 'Linux UBSan vptr': 'ubsan_vptr_clang_release_bot_x64', + 'Linux32 Debug': 'no_h264_debug_bot_x86', + 'Linux32 Debug (ARM)': 'debug_bot_arm', + 'Linux32 Release': 'release_bot_x86', + 'Linux32 Release (ARM)': 'release_bot_arm', + 'Linux64 Builder': 'pure_release_bot_x64', + 'Linux64 Debug': 'debug_bot_x64', + 'Linux64 Debug (ARM)': 'debug_bot_arm64', + 'Linux64 Release': 'release_bot_x64', + 'Linux64 Release (ARM)': 'release_bot_arm64', + 'Linux64 Release (Libfuzzer)': 'libfuzzer_asan_release_bot_x64', + + # Mac + 'Mac Asan': 'mac_asan_clang_release_bot_x64', + 'Mac64 Builder': 'pure_release_bot_x64', + 'Mac64 Debug': 'debug_bot_x64', + 'Mac64 Release': 'release_bot_x64', + 'MacARM64 M1 Release': 'release_bot_arm64', + 'MacArm64 Builder': 'release_bot_arm64', + + # Windows + 'Win (more configs)': { + 'bwe_test_logging': 'bwe_test_logging_x86', + 'dummy_audio_file_devices_no_protobuf': + 'dummy_audio_file_devices_no_protobuf_x86', + 'rtti_no_sctp': 'rtti_no_sctp_no_unicode_win_x86', + }, + 'Win32 Debug (Clang)': 'win_clang_debug_bot_x86', + 'Win32 Release (Clang)': 'win_clang_release_bot_x86', + 'Win64 ASan': 'win_asan_clang_release_bot_x64', + 'Win64 Builder (Clang)': 'win_clang_pure_release_bot_x64', + 'Win64 Debug (Clang)': 'win_clang_debug_bot_x64', + 'Win64 Release (Clang)': 'win_clang_release_bot_x64', + + # iOS + 'iOS Debug (simulator)': 'ios_debug_bot_x64', + 'iOS64 Debug': 'ios_debug_bot_arm64', + 'iOS64 Release': 'ios_release_bot_arm64', + }, + 'client.webrtc.fyi': { + # Android + 'Android ASan (swarming)': 'android_asan_shared_release_bot_arm', + 'Android Perf (swarming)': 'android_pure_release_bot_arm', + + # Mac + 'Mac (swarming)': 'release_bot_x64', + + # Windows + 'Win (swarming)': 'release_bot_x86', + 'Win64 Debug (Win10)': 'debug_bot_x64', + 'Win64 Debug (Win8)': 'debug_bot_x64', + }, + 'client.webrtc.perf': { + # These are here because testers need to gn gen + ninja for the + # webrtc_dashboard_upload target (otherwise a tester would not need to + # build anything). + # TODO(http://crbug.com/1029452): Nuke these and isolate on builder + # instead? + 'Perf Android32 (R Pixel5)': 'release_bot_x64', + 'Perf Android64 (R Pixel5)': 'release_bot_x64', + 'Perf Fuchsia': 'release_bot_x64_fuchsia', + 'Perf Linux Bionic': 'release_bot_x64', + 'Perf Linux Trusty': 'release_bot_x64', + 'Perf Mac 11': 'release_bot_x64', + 'Perf Mac M1 Arm64 12': 'release_bot_x64', + 'Perf Win 10': 'release_bot_x64', + }, + 'internal.client.webrtc': { + 'iOS64 Debug': 'ios_internal_debug_bot_arm64', + 'iOS64 Perf': 'ios_internal_pure_release_bot_arm64', + 'iOS64 Release': 'ios_internal_release_bot_arm64', + }, + 'tryserver.webrtc': { + # Android + 'android_arm64_dbg': 'android_release_bot_arm64', + 'android_arm64_rel': 'android_release_bot_arm64', + 'android_arm_dbg': 'android_debug_static_bot_arm', + 'android_arm_more_configs': { + 'bwe_test_logging': 'bwe_test_logging_android_arm', + 'dummy_audio_file_devices_no_protobuf': + 'dummy_audio_file_devices_no_protobuf_android_arm', + 'rtti_no_sctp': 'rtti_no_sctp_android_arm', + }, + 'android_arm_rel': 'android_release_bot_arm', + 'android_compile_arm64_dbg': 'android_debug_static_bot_arm64', + 'android_compile_arm64_rel': 'android_pure_release_bot_arm64', + 'android_compile_arm_dbg': 'android_debug_static_bot_arm', + 'android_compile_arm_rel': 'android_pure_release_bot_arm', + 'android_compile_x64_dbg': 'android_debug_static_bot_x64', + 'android_compile_x64_rel': 'android_release_bot_x64', + 'android_compile_x86_dbg': 'android_debug_static_bot_x86', + 'android_compile_x86_rel': 'android_release_bot_x86', + + # Fuchsia + 'fuchsia_rel': 'release_bot_x64_fuchsia', + + # iOS + 'ios_compile_arm64_dbg': 'ios_debug_bot_arm64', + 'ios_compile_arm64_rel': 'ios_release_bot_arm64', + 'ios_dbg_simulator': 'ios_debug_bot_x64', + + # Linux + 'linux_asan': 'asan_lsan_clang_release_bot_x64', + 'linux_compile_arm64_dbg': 'debug_bot_arm64', + 'linux_compile_arm64_rel': 'release_bot_arm64', + 'linux_compile_arm_dbg': 'debug_bot_arm', + 'linux_compile_arm_rel': 'release_bot_arm', + 'linux_compile_dbg': 'debug_bot_x64', + 'linux_compile_rel': 'pure_release_bot_x64', + 'linux_compile_x86_dbg': 'debug_bot_x86', + 'linux_compile_x86_rel': 'pure_release_bot_x86', + 'linux_coverage': 'code_coverage_bot_x64', + 'linux_dbg': 'debug_bot_x64', + 'linux_libfuzzer_rel': 'libfuzzer_asan_release_bot_x64', + 'linux_more_configs': { + 'bwe_test_logging': 'bwe_test_logging_x64', + 'dummy_audio_file_devices_no_protobuf': + 'dummy_audio_file_devices_no_protobuf_x64', + 'rtti_no_sctp': 'rtti_no_sctp_x64', + }, + 'linux_msan': 'msan_clang_release_bot_x64', + 'linux_rel': 'release_bot_x64', + 'linux_tsan2': 'tsan_clang_release_bot_x64', + 'linux_ubsan': 'ubsan_clang_release_bot_x64', + 'linux_ubsan_vptr': 'ubsan_vptr_clang_release_bot_x64', + 'linux_x86_dbg': 'no_h264_debug_bot_x86', + 'linux_x86_rel': 'release_bot_x86', + + # Mac + 'mac_asan': 'mac_asan_clang_release_bot_x64', + 'mac_compile_dbg': 'debug_bot_x64', + 'mac_compile_rel': 'pure_release_bot_x64', + 'mac_dbg': 'debug_bot_x64', + 'mac_dbg_m1': 'debug_bot_arm64', + 'mac_rel': 'release_bot_x64', + 'mac_rel_m1': 'release_bot_arm64', + + # Windows + 'win_asan': 'win_asan_clang_release_bot_x64', + 'win_compile_x64_clang_dbg': 'win_clang_debug_bot_x64', + 'win_compile_x64_clang_rel': 'win_clang_release_bot_x64', + 'win_compile_x86_clang_dbg': 'win_clang_debug_bot_x86', + 'win_compile_x86_clang_rel': 'win_clang_release_bot_x86', + 'win_x64_clang_dbg': 'win_clang_debug_bot_x64', + 'win_x64_clang_rel': 'win_clang_release_bot_x64', + 'win_x86_clang_dbg': 'win_clang_debug_bot_x86', + 'win_x86_clang_rel': 'win_clang_release_bot_x86', + 'win_x86_more_configs': { + 'bwe_test_logging': 'bwe_test_logging_x86', + 'dummy_audio_file_devices_no_protobuf': + 'dummy_audio_file_devices_no_protobuf_x86', + 'rtti_no_sctp': 'rtti_no_sctp_no_unicode_win_x86', + }, + } + }, + + # This is the list of configs that you can pass to mb; each config + # represents a particular combination of gn args that we must support. + # A given config *may* be platform-specific but is not necessarily so (i.e., + # we might have mac, win, and linux bots all using the 'release_bot' config). + 'configs': { + 'android_asan_shared_release_bot_arm': + ['android', 'asan', 'clang', 'pure_release_bot', 'arm'], + 'android_debug_static_bot_arm': ['android', 'debug_static_bot', 'arm'], + 'android_debug_static_bot_arm64': ['android', 'debug_static_bot', 'arm64'], + 'android_debug_static_bot_x64': ['android', 'debug_static_bot', 'x64'], + 'android_debug_static_bot_x86': ['android', 'debug_static_bot', 'x86'], + 'android_pure_release_bot_arm': ['android', 'pure_release_bot', 'arm'], + 'android_pure_release_bot_arm64': ['android', 'pure_release_bot', 'arm64'], + 'android_release_bot_arm': ['android', 'release_bot', 'arm'], + 'android_release_bot_arm64': ['android', 'release_bot', 'arm64'], + 'android_release_bot_x64': ['android', 'release_bot', 'x64'], + 'android_release_bot_x86': ['android', 'release_bot', 'x86'], + 'asan_lsan_clang_release_bot_x64': + ['asan', 'lsan', 'clang', 'openh264', 'pure_release_bot', 'x64', 'h265'], + 'bwe_test_logging_android_arm': + ['android', 'debug_static_bot', 'arm', 'bwe_test_logging'], + 'bwe_test_logging_x64': ['debug_bot', 'x64', 'bwe_test_logging'], + 'bwe_test_logging_x86': ['debug_bot', 'x86', 'bwe_test_logging'], + 'code_coverage_bot_x64': [ + 'openh264', 'release_bot', 'x64', 'code_coverage', + 'partial_code_coverage_instrumentation', 'h265' + ], + 'codesearch_gen_linux_bot': + ['openh264', 'debug_bot', 'minimal_symbols', 'h265'], + 'debug_bot_arm': ['openh264', 'debug_bot', 'arm', 'h265'], + 'debug_bot_arm64': ['openh264', 'debug_bot', 'arm64', 'h265'], + 'debug_bot_x64': ['openh264', 'debug_bot', 'x64', 'h265'], + 'debug_bot_x86': ['openh264', 'debug_bot', 'x86', 'h265'], + 'dummy_audio_file_devices_no_protobuf_android_arm': [ + 'android', 'debug_static_bot', 'arm', 'dummy_audio_file_devices', + 'no_protobuf' + ], + 'dummy_audio_file_devices_no_protobuf_x64': + ['debug_bot', 'x64', 'dummy_audio_file_devices', 'no_protobuf'], + 'dummy_audio_file_devices_no_protobuf_x86': + ['debug_bot', 'x86', 'dummy_audio_file_devices', 'no_protobuf'], + 'ios_debug_bot_arm64': + ['ios', 'debug_bot', 'arm64', 'no_ios_code_signing', 'xctest'], + 'ios_debug_bot_x64': ['ios', 'debug_bot', 'x64', 'xctest'], + 'ios_internal_debug_bot_arm64': [ + 'ios', 'debug_bot', 'arm64', 'ios_code_signing_identity_description', + 'xctest' + ], + 'ios_internal_pure_release_bot_arm64': [ + 'ios', 'pure_release_bot', 'arm64', + 'ios_code_signing_identity_description', 'xctest', + 'rtc_objc_test_prefix', + ], + 'ios_internal_release_bot_arm64': [ + 'ios', 'release_bot', 'arm64', 'ios_code_signing_identity_description', + 'xctest' + ], + 'ios_release_bot_arm64': [ + 'ios', 'release_bot', 'arm64', 'no_ios_code_signing', 'xctest', + ], + 'libfuzzer_asan_release_bot_x64': [ + 'libfuzzer', 'asan', 'optimize_for_fuzzing', 'openh264', + 'pure_release_bot', 'x64', 'h265' + ], + 'mac_asan_clang_release_bot_x64': [ + 'asan', + 'clang', + 'openh264', + 'pure_release_bot', + 'x64', + 'h265', + ], + 'msan_clang_release_bot_x64': + ['msan', 'clang', 'openh264', 'pure_release_bot', 'x64', 'h265'], + 'no_h264_debug_bot_x86': ['debug_bot', 'x86'], + 'pure_release_bot_x64': ['openh264', 'pure_release_bot', 'x64', 'h265'], + 'pure_release_bot_x86': ['openh264', 'pure_release_bot', 'x86', 'h265'], + 'release_bot_arm': ['openh264', 'release_bot', 'arm', 'h265'], + 'release_bot_arm64': ['openh264', 'release_bot', 'arm64', 'h265'], + 'release_bot_x64': ['openh264', 'release_bot', 'x64', 'h265'], + 'release_bot_x64_fuchsia': + ['openh264', 'release_bot', 'x64', 'fuchsia', 'h265'], + 'release_bot_x86': ['openh264', 'release_bot', 'x86', 'h265'], + 'rtti_no_sctp_android_arm': + ['android', 'debug_static_bot', 'arm', 'rtti', 'no_sctp'], + 'rtti_no_sctp_no_unicode_win_x86': + ['debug_bot', 'x86', 'rtti', 'no_sctp', 'win_undef_unicode'], + 'rtti_no_sctp_x64': ['debug_bot', 'x64', 'rtti', 'no_sctp'], + 'tsan_clang_release_bot_x64': + ['tsan', 'clang', 'openh264', 'pure_release_bot', 'x64', 'h265'], + 'ubsan_clang_release_bot_x64': [ + 'ubsan', 'clang', 'openh264', 'pure_release_bot', 'x64', 'h265' + ], + 'ubsan_vptr_clang_release_bot_x64': [ + 'ubsan_vptr', 'clang', 'openh264', 'pure_release_bot', 'x64', 'h265' + ], + 'win_asan_clang_release_bot_x64': [ + 'asan', + 'clang', + 'full_symbols', + 'openh264', + 'pure_release_bot', + 'x64', + 'h265', + ], + 'win_clang_debug_bot_x64': [ + 'clang', + 'openh264', + 'debug_bot', + 'x64', + 'h265', + ], + 'win_clang_debug_bot_x86': [ + 'clang', + 'openh264', + 'debug_bot', + 'x86', + 'h265', + ], + 'win_clang_pure_release_bot_x64': [ + 'clang', + 'openh264', + 'pure_release_bot', + 'x64', + 'h265', + ], + 'win_clang_release_bot_x64': [ + 'clang', + 'openh264', + 'release_bot', + 'x64', + 'h265', + ], + 'win_clang_release_bot_x86': [ + 'clang', + 'openh264', + 'release_bot', + 'x86', + 'h265', + ], + }, + + # This is a dict mapping a given 'mixin' name to a dict of settings that + # mb should use. See //tools/mb/docs/user_guide.md for more information. + 'mixins': { + 'android': { + 'gn_args': 'target_os="android"', + }, + 'arm': { + 'gn_args': 'target_cpu="arm"', + }, + 'arm64': { + 'gn_args': 'target_cpu="arm64"', + }, + 'asan': { + 'gn_args': 'is_asan=true', + }, + 'bwe_test_logging': { + 'gn_args': 'rtc_enable_bwe_test_logging=true', + }, + # is_clang=true by default, this is only to guard from upstream changes. + 'clang': { + 'gn_args': 'is_clang=true', + }, + 'code_coverage': { + 'gn_args': 'use_clang_coverage=true', + }, + 'dcheck_always_on': { + 'gn_args': 'dcheck_always_on=true', + }, + 'dcheck_off': { + 'gn_args': 'dcheck_always_on=false', + }, + 'debug': { + 'gn_args': 'is_debug=true', + }, + 'debug_bot': { + 'mixins': ['debug', 'reclient', 'strict_field_trials'], + }, + 'debug_static_bot': { + 'mixins': ['debug', 'minimal_symbols', 'reclient', 'strict_field_trials'], + }, + 'dummy_audio_file_devices': { + 'gn_args': 'rtc_use_dummy_audio_file_devices=true', + }, + 'fuchsia': { + 'gn_args': 'target_os="fuchsia"' + }, + 'full_symbols': { + 'gn_args': 'symbol_level=2', + }, + 'h265': { + 'gn_args': 'rtc_use_h265=true', + }, + 'ios': { + 'gn_args': 'target_os="ios"', + }, + 'ios_code_signing_identity_description': { + 'gn_args': 'ios_code_signing_identity_description="Apple Development"', + }, + 'libfuzzer': { + 'gn_args': 'use_libfuzzer=true', + }, + 'lsan': { + 'gn_args': 'is_lsan=true', + }, + 'minimal_symbols': { + 'gn_args': 'symbol_level=1', + }, + 'msan': { + 'gn_args': 'is_msan=true msan_track_origins=2' + ' instrumented_libraries_release = "focal"', + }, + 'no_ios_code_signing': { + 'gn_args': 'ios_enable_code_signing=false', + }, + 'no_protobuf': { + 'gn_args': 'rtc_enable_protobuf=false', + }, + 'no_sctp': { + 'gn_args': 'rtc_enable_sctp=false', + }, + 'openh264': { + 'gn_args': 'ffmpeg_branding="Chrome" rtc_use_h264=true', + }, + 'optimize_for_fuzzing': { + 'gn_args': 'optimize_for_fuzzing=true', + }, + 'partial_code_coverage_instrumentation': { + 'gn_args': + 'coverage_instrumentation_input_file="//.code-coverage/files_to_instrument.txt"' + }, + # The 'pure_release_bot' configuration is for release bots that are doing a + # 100% release build without DCHECKs while 'release_bot' is a partial + # release configs since `dcheck_always_on` is set to true. + 'pure_release_bot': { + 'mixins': ['release', 'reclient', 'dcheck_off'], + }, + 'reclient': { + 'gn_args': 'use_remoteexec=true', + }, + 'release': { + 'gn_args': 'is_debug=false', + }, + 'release_bot': { + 'mixins': ['pure_release_bot', 'dcheck_always_on', 'strict_field_trials'], + }, + 'rtc_objc_test_prefix': { + 'gn_args': 'rtc_objc_prefix="RTC_TESTING"', + }, + 'rtti': { + 'gn_args': 'use_rtti=true', + }, + 'strict_field_trials': { + 'gn_args': 'rtc_strict_field_trials="dcheck"', + }, + 'tsan': { + 'gn_args': 'is_tsan=true', + }, + 'ubsan': { + 'gn_args': 'is_ubsan=true is_ubsan_no_recover=true', + }, + 'ubsan_vptr': { + 'gn_args': 'is_ubsan_vptr=true is_ubsan_no_recover=true', + }, + 'win_undef_unicode': { + 'gn_args': 'rtc_win_undef_unicode=true', + }, + 'x64': { + 'gn_args': 'target_cpu="x64"', + }, + 'x86': { + 'gn_args': 'target_cpu="x86"', + }, + 'xctest': { + 'gn_args': 'enable_run_ios_unittests_with_xctest=true', + }, + }, +} diff --git a/third_party/libwebrtc/tools_webrtc/mb/mb_unittest.py b/third_party/libwebrtc/tools_webrtc/mb/mb_unittest.py new file mode 100755 index 0000000000..583fefd87e --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/mb/mb_unittest.py @@ -0,0 +1,728 @@ +#!/usr/bin/env vpython3 + +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +"""Tests for mb.py.""" + +import ast +import os +import re +import sys +import tempfile +import unittest + +_SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +_SRC_DIR = os.path.dirname(os.path.dirname(_SCRIPT_DIR)) +sys.path.insert(0, _SRC_DIR) + +from tools_webrtc.mb import mb + + +class FakeMBW(mb.WebRTCMetaBuildWrapper): + def __init__(self, win32=False): + super().__init__() + + # Override vars for test portability. + if win32: + self.chromium_src_dir = 'c:\\fake_src' + self.default_config = 'c:\\fake_src\\tools_webrtc\\mb\\mb_config.pyl' + self.default_isolate_map = ('c:\\fake_src\\testing\\buildbot\\' + 'gn_isolate_map.pyl') + self.platform = 'win32' + self.executable = 'c:\\python\\vpython3.exe' + self.sep = '\\' + self.cwd = 'c:\\fake_src\\out\\Default' + else: + self.chromium_src_dir = '/fake_src' + self.default_config = '/fake_src/tools_webrtc/mb/mb_config.pyl' + self.default_isolate_map = '/fake_src/testing/buildbot/gn_isolate_map.pyl' + self.executable = '/usr/bin/vpython3' + self.platform = 'linux2' + self.sep = '/' + self.cwd = '/fake_src/out/Default' + + self.files = {} + self.dirs = set() + self.calls = [] + self.cmds = [] + self.cross_compile = None + self.out = '' + self.err = '' + self.rmdirs = [] + + def ExpandUser(self, path): + # pylint: disable=no-self-use + return '$HOME/%s' % path + + def Exists(self, path): + abs_path = self._AbsPath(path) + return self.files.get(abs_path) is not None or abs_path in self.dirs + + def ListDir(self, path): + dir_contents = [] + for f in list(self.files.keys()) + list(self.dirs): + head, _ = os.path.split(f) + if head == path: + dir_contents.append(f) + return dir_contents + + def MaybeMakeDirectory(self, path): + abpath = self._AbsPath(path) + self.dirs.add(abpath) + + def PathJoin(self, *comps): + return self.sep.join(comps) + + def ReadFile(self, path): + try: + return self.files[self._AbsPath(path)] + except KeyError as e: + raise IOError('%s not found' % path) from e + + def WriteFile(self, path, contents, force_verbose=False): + if self.args.dryrun or self.args.verbose or force_verbose: + self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path)) + abpath = self._AbsPath(path) + self.files[abpath] = contents + + def Call(self, cmd, env=None, capture_output=True, input=None): + # pylint: disable=redefined-builtin + del env + del capture_output + del input + self.calls.append(cmd) + if self.cmds: + return self.cmds.pop(0) + return 0, '', '' + + def Print(self, *args, **kwargs): + sep = kwargs.get('sep', ' ') + end = kwargs.get('end', '\n') + f = kwargs.get('file', sys.stdout) + if f == sys.stderr: + self.err += sep.join(args) + end + else: + self.out += sep.join(args) + end + + def TempDir(self): + tmp_dir = os.path.join(tempfile.gettempdir(), 'mb_test') + self.dirs.add(tmp_dir) + return tmp_dir + + def TempFile(self, mode='w'): + del mode + return FakeFile(self.files) + + def RemoveFile(self, path): + abpath = self._AbsPath(path) + self.files[abpath] = None + + def RemoveDirectory(self, abs_path): + # Normalize the passed-in path to handle different working directories + # used during unit testing. + abs_path = self._AbsPath(abs_path) + self.rmdirs.append(abs_path) + files_to_delete = [f for f in self.files if f.startswith(abs_path)] + for f in files_to_delete: + self.files[f] = None + + def _AbsPath(self, path): + if not ((self.platform == 'win32' and path.startswith('c:')) or + (self.platform != 'win32' and path.startswith('/'))): + path = self.PathJoin(self.cwd, path) + if self.sep == '\\': + return re.sub(r'\\+', r'\\', path) + return re.sub('/+', '/', path) + + +class FakeFile: + # pylint: disable=invalid-name + def __init__(self, files): + self.name = '/tmp/file' + self.buf = '' + self.files = files + + def write(self, contents): + self.buf += contents + + def close(self): + self.files[self.name] = self.buf + + +TEST_CONFIG = """\ +{ + 'builder_groups': { + 'chromium': {}, + 'fake_group': { + 'fake_builder': 'rel_bot', + 'fake_debug_builder': 'debug_goma', + 'fake_args_bot': 'fake_args_bot', + 'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'}, + 'fake_android_bot': 'android_bot', + 'fake_args_file': 'args_file_goma', + 'fake_ios_error': 'ios_error', + }, + }, + 'configs': { + 'args_file_goma': ['fake_args_bot', 'goma'], + 'fake_args_bot': ['fake_args_bot'], + 'rel_bot': ['rel', 'goma', 'fake_feature1'], + 'debug_goma': ['debug', 'goma'], + 'phase_1': ['rel', 'phase_1'], + 'phase_2': ['rel', 'phase_2'], + 'android_bot': ['android'], + 'ios_error': ['error'], + }, + 'mixins': { + 'error': { + 'gn_args': 'error', + }, + 'fake_args_bot': { + 'args_file': '//build/args/bots/fake_group/fake_args_bot.gn', + }, + 'fake_feature1': { + 'gn_args': 'enable_doom_melon=true', + }, + 'goma': { + 'gn_args': 'use_goma=true', + }, + 'phase_1': { + 'gn_args': 'phase=1', + }, + 'phase_2': { + 'gn_args': 'phase=2', + }, + 'rel': { + 'gn_args': 'is_debug=false dcheck_always_on=false', + }, + 'debug': { + 'gn_args': 'is_debug=true', + }, + 'android': { + 'gn_args': 'target_os="android" dcheck_always_on=false', + } + }, +} +""" + + +def CreateFakeMBW(files=None, win32=False): + mbw = FakeMBW(win32=win32) + mbw.files.setdefault(mbw.default_config, TEST_CONFIG) + mbw.files.setdefault( + mbw.ToAbsPath('//testing/buildbot/gn_isolate_map.pyl'), '''{ + "foo_unittests": { + "label": "//foo:foo_unittests", + "type": "console_test_launcher", + "args": [], + }, + }''') + mbw.files.setdefault( + mbw.ToAbsPath('//build/args/bots/fake_group/fake_args_bot.gn'), + 'is_debug = false\ndcheck_always_on=false\n') + mbw.files.setdefault(mbw.ToAbsPath('//tools/mb/rts_banned_suites.json'), '{}') + if files: + for path, contents in list(files.items()): + mbw.files[path] = contents + if path.endswith('.runtime_deps'): + + def FakeCall(cmd, env=None, capture_output=True, stdin=None): + # pylint: disable=cell-var-from-loop + del cmd + del env + del capture_output + del stdin + mbw.files[path] = contents + return 0, '', '' + + # pylint: disable=invalid-name + mbw.Call = FakeCall + return mbw + + +class UnitTest(unittest.TestCase): + # pylint: disable=invalid-name + def check(self, + args, + mbw=None, + files=None, + out=None, + err=None, + ret=None, + env=None): + if not mbw: + mbw = CreateFakeMBW(files) + + try: + prev_env = os.environ.copy() + os.environ = env if env else prev_env + actual_ret = mbw.Main(args) + finally: + os.environ = prev_env + self.assertEqual( + actual_ret, ret, + "ret: %s, out: %s, err: %s" % (actual_ret, mbw.out, mbw.err)) + if out is not None: + self.assertEqual(mbw.out, out) + if err is not None: + self.assertEqual(mbw.err, err) + return mbw + + def test_gen_swarming(self): + files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'raw'," + " 'args': []," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + mbw = CreateFakeMBW(files) + self.check([ + 'gen', '-c', 'debug_goma', '--swarming-targets-file', + '/tmp/swarming_targets', '//out/Default' + ], + mbw=mbw, + ret=0) + self.assertIn('/fake_src/out/Default/foo_unittests.isolate', mbw.files) + self.assertIn('/fake_src/out/Default/foo_unittests.isolated.gen.json', + mbw.files) + + def test_gen_swarming_android(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'console_test_launcher'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + mbw = self.check([ + 'gen', '-c', 'android_bot', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual( + files, + ['../../.vpython3', '../../testing/test_env.py', 'foo_unittests']) + self.assertEqual(command, [ + 'luci-auth', + 'context', + '--', + 'vpython3', + '../../build/android/test_wrapper/logdog_wrapper.py', + '--target', + 'foo_unittests', + '--logdog-bin-cmd', + '../../.task_template_packages/logdog_butler', + '--logcat-output-file', + '${ISOLATED_OUTDIR}/logcats', + '--store-tombstones', + ]) + + def test_gen_swarming_android_junit_test(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'junit_test'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + mbw = self.check([ + 'gen', '-c', 'android_bot', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual( + files, + ['../../.vpython3', '../../testing/test_env.py', 'foo_unittests']) + self.assertEqual(command, [ + 'luci-auth', + 'context', + '--', + 'vpython3', + '../../build/android/test_wrapper/logdog_wrapper.py', + '--target', + 'foo_unittests', + '--logdog-bin-cmd', + '../../.task_template_packages/logdog_butler', + '--logcat-output-file', + '${ISOLATED_OUTDIR}/logcats', + '--store-tombstones', + ]) + + def test_gen_script(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests_script\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests_script': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'script'," + " 'script': '//foo/foo_unittests_script.py'," + "}}\n"), + '/fake_src/out/Default/foo_unittests_script.runtime_deps': + ("foo_unittests\n" + "foo_unittests_script.py\n"), + } + mbw = self.check([ + 'gen', '-c', 'debug_goma', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = ( + mbw.files['/fake_src/out/Default/foo_unittests_script.isolate']) + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + 'foo_unittests', + 'foo_unittests_script.py', + ]) + self.assertEqual(command, [ + 'vpython3', + '../../foo/foo_unittests_script.py', + ]) + + def test_gen_raw(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'raw'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + mbw = self.check([ + 'gen', '-c', 'debug_goma', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + 'foo_unittests', + ]) + self.assertEqual(command, ['bin/run_foo_unittests']) + + def test_gen_non_parallel_console_test_launcher(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'non_parallel_console_test_launcher'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + mbw = self.check([ + 'gen', '-c', 'debug_goma', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + '../../third_party/gtest-parallel/gtest-parallel', + '../../third_party/gtest-parallel/gtest_parallel.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + 'foo_unittests', + ]) + self.assertEqual(command, [ + 'vpython3', + '../../testing/test_env.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + '--output_dir=${ISOLATED_OUTDIR}/test_logs', + '--gtest_color=no', + '--workers=1', + '--retry_failed=3', + './foo_unittests', + '--asan=0', + '--lsan=0', + '--msan=0', + '--tsan=0', + ]) + + def test_isolate_windowed_test_launcher_linux(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'windowed_test_launcher'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': + ("foo_unittests\n" + "some_resource_file\n"), + } + mbw = self.check([ + 'gen', '-c', 'debug_goma', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + '../../testing/xvfb.py', + '../../third_party/gtest-parallel/gtest-parallel', + '../../third_party/gtest-parallel/gtest_parallel.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + 'foo_unittests', + 'some_resource_file', + ]) + self.assertEqual(command, [ + 'vpython3', + '../../testing/xvfb.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + '--output_dir=${ISOLATED_OUTDIR}/test_logs', + '--gtest_color=no', + '--retry_failed=3', + './foo_unittests', + '--asan=0', + '--lsan=0', + '--msan=0', + '--tsan=0', + ]) + + def test_gen_windowed_test_launcher_win(self): + files = { + 'c:\\fake_src\\out\\Default\\tmp\\swarming_targets': + 'unittests\n', + 'c:\\fake_src\\testing\\buildbot\\gn_isolate_map.pyl': + ("{'unittests': {" + " 'label': '//somewhere:unittests'," + " 'type': 'windowed_test_launcher'," + "}}\n"), + r'c:\fake_src\out\Default\unittests.exe.runtime_deps': + ("unittests.exe\n" + "some_dependency\n"), + } + mbw = CreateFakeMBW(files=files, win32=True) + self.check([ + 'gen', '-c', 'debug_goma', '--swarming-targets-file', + 'c:\\fake_src\\out\\Default\\tmp\\swarming_targets', + '--isolate-map-file', + 'c:\\fake_src\\testing\\buildbot\\gn_isolate_map.pyl', '//out/Default' + ], + mbw=mbw, + ret=0) + + isolate_file = mbw.files['c:\\fake_src\\out\\Default\\unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + '../../third_party/gtest-parallel/gtest-parallel', + '../../third_party/gtest-parallel/gtest_parallel.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + 'some_dependency', + 'unittests.exe', + ]) + self.assertEqual(command, [ + 'vpython3', + '../../testing/test_env.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + '--output_dir=${ISOLATED_OUTDIR}/test_logs', + '--gtest_color=no', + '--retry_failed=3', + r'.\unittests.exe', + '--asan=0', + '--lsan=0', + '--msan=0', + '--tsan=0', + ]) + + def test_gen_console_test_launcher(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'console_test_launcher'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + mbw = self.check([ + 'gen', '-c', 'debug_goma', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + '../../third_party/gtest-parallel/gtest-parallel', + '../../third_party/gtest-parallel/gtest_parallel.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + 'foo_unittests', + ]) + self.assertEqual(command, [ + 'vpython3', + '../../testing/test_env.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + '--output_dir=${ISOLATED_OUTDIR}/test_logs', + '--gtest_color=no', + '--retry_failed=3', + './foo_unittests', + '--asan=0', + '--lsan=0', + '--msan=0', + '--tsan=0', + ]) + + def test_isolate_test_launcher_with_webcam(self): + test_files = { + '/tmp/swarming_targets': + 'foo_unittests\n', + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'console_test_launcher'," + " 'use_webcam': True," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': + ("foo_unittests\n" + "some_resource_file\n"), + } + mbw = self.check([ + 'gen', '-c', 'debug_goma', '//out/Default', '--swarming-targets-file', + '/tmp/swarming_targets', '--isolate-map-file', + '/fake_src/testing/buildbot/gn_isolate_map.pyl' + ], + files=test_files, + ret=0) + + isolate_file = mbw.files['/fake_src/out/Default/foo_unittests.isolate'] + isolate_file_contents = ast.literal_eval(isolate_file) + files = isolate_file_contents['variables']['files'] + command = isolate_file_contents['variables']['command'] + + self.assertEqual(files, [ + '../../.vpython3', + '../../testing/test_env.py', + '../../third_party/gtest-parallel/gtest-parallel', + '../../third_party/gtest-parallel/gtest_parallel.py', + '../../tools_webrtc/ensure_webcam_is_running.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + 'foo_unittests', + 'some_resource_file', + ]) + self.assertEqual(command, [ + 'vpython3', + '../../tools_webrtc/ensure_webcam_is_running.py', + 'vpython3', + '../../testing/test_env.py', + '../../tools_webrtc/gtest-parallel-wrapper.py', + '--output_dir=${ISOLATED_OUTDIR}/test_logs', + '--gtest_color=no', + '--retry_failed=3', + './foo_unittests', + '--asan=0', + '--lsan=0', + '--msan=0', + '--tsan=0', + ]) + + def test_isolate(self): + files = { + '/fake_src/out/Default/toolchain.ninja': + "", + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'foo_unittests': {" + " 'label': '//foo:foo_unittests'," + " 'type': 'non_parallel_console_test_launcher'," + "}}\n"), + '/fake_src/out/Default/foo_unittests.runtime_deps': ("foo_unittests\n"), + } + self.check( + ['isolate', '-c', 'debug_goma', '//out/Default', 'foo_unittests'], + files=files, + ret=0) + + # test running isolate on an existing build_dir + files['/fake_src/out/Default/args.gn'] = 'is_debug = true\n' + self.check(['isolate', '//out/Default', 'foo_unittests'], + files=files, + ret=0) + files['/fake_src/out/Default/mb_type'] = 'gn\n' + self.check(['isolate', '//out/Default', 'foo_unittests'], + files=files, + ret=0) + +if __name__ == '__main__': + unittest.main() |