Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a discover_tests attribute to swift_test #1259

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ that use the toolchain.
| <a id="SwiftToolchainInfo-requested_features"></a>requested_features | `List` of `string`s. Features that should be implicitly enabled by default for targets built using this toolchain, unless overridden by the user by listing their negation in the `features` attribute of a target/package or in the `--features` command line flag.<br><br>These features determine various compilation and debugging behaviors of the Swift build rules, and they are also passed to the C++ APIs used when linking (so features defined in CROSSTOOL may be used here). |
| <a id="SwiftToolchainInfo-root_dir"></a>root_dir | `String`. The workspace-relative root directory of the toolchain. |
| <a id="SwiftToolchainInfo-swift_worker"></a>swift_worker | `File`. The executable representing the worker executable used to invoke the compiler and other Swift tools (for both incremental and non-incremental compiles). |
| <a id="SwiftToolchainInfo-test_configuration"></a>test_configuration | `Struct` containing two fields:<br><br>* `env`: A `dict` of environment variables to be set when running tests that were built with this toolchain.<br><br>* `execution_requirements`: A `dict` of execution requirements for tests that were built with this toolchain.<br><br>This is used, for example, with Xcode-based toolchains to ensure that the `xctest` helper and coverage tools are found in the correct developer directory when running tests. |
| <a id="SwiftToolchainInfo-test_configuration"></a>test_configuration | `Struct` containing the following fields:<br><br>* `env`: A `dict` of environment variables to be set when running tests that were built with this toolchain.<br><br>* `execution_requirements`: A `dict` of execution requirements for tests that were built with this toolchain.<br><br>* `uses_xctest_bundles`: A Boolean value indicating whether test targets should emit `.xctest` bundles that are launched with the `xctest` tool.<br><br>This is used, for example, with Xcode-based toolchains to ensure that the `xctest` helper and coverage tools are found in the correct developer directory when running tests. |
| <a id="SwiftToolchainInfo-tool_configs"></a>tool_configs | This field is an internal implementation detail of the build rules. |
| <a id="SwiftToolchainInfo-unsupported_features"></a>unsupported_features | `List` of `string`s. Features that should be implicitly disabled by default for targets built using this toolchain, unless overridden by the user by listing them in the `features` attribute of a target/package or in the `--features` command line flag.<br><br>These features determine various compilation and debugging behaviors of the Swift build rules, and they are also passed to the C++ APIs used when linking (so features defined in CROSSTOOL may be used here). |

Expand Down
39 changes: 8 additions & 31 deletions doc/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -678,41 +678,17 @@ swift_binary(
## swift_test

<pre>
swift_test(<a href="#swift_test-name">name</a>, <a href="#swift_test-deps">deps</a>, <a href="#swift_test-srcs">srcs</a>, <a href="#swift_test-data">data</a>, <a href="#swift_test-copts">copts</a>, <a href="#swift_test-defines">defines</a>, <a href="#swift_test-env">env</a>, <a href="#swift_test-linkopts">linkopts</a>, <a href="#swift_test-malloc">malloc</a>, <a href="#swift_test-module_name">module_name</a>, <a href="#swift_test-package_name">package_name</a>,
<a href="#swift_test-plugins">plugins</a>, <a href="#swift_test-stamp">stamp</a>, <a href="#swift_test-swiftc_inputs">swiftc_inputs</a>)
swift_test(<a href="#swift_test-name">name</a>, <a href="#swift_test-deps">deps</a>, <a href="#swift_test-srcs">srcs</a>, <a href="#swift_test-data">data</a>, <a href="#swift_test-copts">copts</a>, <a href="#swift_test-defines">defines</a>, <a href="#swift_test-discover_tests">discover_tests</a>, <a href="#swift_test-env">env</a>, <a href="#swift_test-linkopts">linkopts</a>, <a href="#swift_test-malloc">malloc</a>,
<a href="#swift_test-module_name">module_name</a>, <a href="#swift_test-package_name">package_name</a>, <a href="#swift_test-plugins">plugins</a>, <a href="#swift_test-stamp">stamp</a>, <a href="#swift_test-swiftc_inputs">swiftc_inputs</a>)
</pre>

Compiles and links Swift code into an executable test target.

The behavior of `swift_test` differs slightly for macOS targets, in order to
provide seamless integration with Apple's XCTest framework. The output of the
rule is still a binary, but one whose Mach-O type is `MH_BUNDLE` (a loadable
bundle). Thus, the binary cannot be launched directly. Instead, running
`bazel test` on the target will launch a test runner script that copies it into
an `.xctest` bundle directory and then launches the `xctest` helper tool from
Xcode, which uses Objective-C runtime reflection to locate the tests.

On Linux, the output of a `swift_test` is a standard executable binary, because
the implementation of XCTest on that platform currently requires authors to
explicitly list the tests that are present and run them from their main program.

Test bundling on macOS can be disabled on a per-target basis, if desired. You
may wish to do this if you are not using XCTest, but rather a different test
framework (or no framework at all) where the pass/fail outcome is represented as
a zero/non-zero exit code (as is the case with other Bazel test rules like
`cc_test`). To do so, disable the `"swift.bundled_xctests"` feature on the
target:

```python
swift_test(
name = "MyTests",
srcs = [...],
features = ["-swift.bundled_xctests"],
)
```

You can also disable this feature for all the tests in a package by applying it
to your BUILD file's `package()` declaration instead of the individual targets.
By default, this rule performs _test discovery_ that finds tests written with
the `XCTest` framework and executes them automatically, without the user
providing their own `main` entry point. See the documentation of the
`discover_tests` attribute for more information about how this affects the rule
output and how to control this behavior.

If integrating with Xcode, the relative paths in test binaries can prevent the
Issue navigator from working for test failures. To work around this, you can
Expand All @@ -738,6 +714,7 @@ bazel test //:Tests --test_filter=TestModuleName.TestClassName/testMethodName
| <a id="swift_test-data"></a>data | The list of files needed by this target at runtime.<br><br>Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="swift_test-copts"></a>copts | Additional compiler options that should be passed to `swiftc`. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` |
| <a id="swift_test-defines"></a>defines | A list of defines to add to the compilation command line.<br><br>Note that unlike C-family languages, Swift defines do not have values; they are simply identifiers that are either defined or undefined. So strings in this list should be simple identifiers, **not** `name=value` pairs.<br><br>Each string is prepended with `-D` and added to the command line. Unlike `copts`, these flags are added for the target and every target that depends on it, so use this attribute with caution. It is preferred that you add defines directly to `copts`, only using this feature in the rare case that a library needs to propagate a symbol up to those that depend on it. | List of strings | optional | `[]` |
| <a id="swift_test-discover_tests"></a>discover_tests | Determines whether or not tests are automatically discovered in the binary. The default value is `True`.<br><br>If tests are discovered, then you should not provide your own `main` entry point in the `swift_test` binary; the test runtime provides the entry point for you. If you set this attribute to `False`, then you are responsible for providing your own `main`. This allows you to write tests that use a framework other than Apple's `XCTest`. The only requirement of such a test is that it terminate with a zero exit code for success or a non-zero exit code for failure.<br><br>Additionally, on Apple platforms, test discovery is handled by the Objective-C runtime and the output of a `swift_test` rule is an `.xctest` bundle that is invoked using the `xctest` tool in Xcode. If this attribute is used to disable test discovery, then the output of the `swift_test` rule will instead be a standard executable binary that is invoked directly. | Boolean | optional | `True` |
| <a id="swift_test-env"></a>env | Dictionary of environment variables that should be set during the test execution. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | `{}` |
| <a id="swift_test-linkopts"></a>linkopts | Additional linker options that should be passed to `clang`. These strings are subject to `$(location ...)` expansion. | List of strings | optional | `[]` |
| <a id="swift_test-malloc"></a>malloc | Override the default dependency on `malloc`.<br><br>By default, Swift binaries are linked against `@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use libc's `malloc`. This label must refer to a `cc_library` rule. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `"@bazel_tools//tools/cpp:malloc"` |
Expand Down
5 changes: 4 additions & 1 deletion swift/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,17 @@ compiler and other Swift tools (for both incremental and non-incremental
compiles).
""",
"test_configuration": """\
`Struct` containing two fields:
`Struct` containing the following fields:

* `env`: A `dict` of environment variables to be set when running tests
that were built with this toolchain.

* `execution_requirements`: A `dict` of execution requirements for tests
that were built with this toolchain.

* `uses_xctest_bundles`: A Boolean value indicating whether test targets
should emit `.xctest` bundles that are launched with the `xctest` tool.

This is used, for example, with Xcode-based toolchains to ensure that the
`xctest` helper and coverage tools are found in the correct developer
directory when running tests.
Expand Down
104 changes: 62 additions & 42 deletions swift/swift_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,17 @@ def _swift_test_impl(ctx):
unsupported_features = ctx.disabled_features,
)

is_bundled = swift_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = SWIFT_FEATURE_BUNDLED_XCTESTS,
discover_tests = ctx.attr.discover_tests

# TODO(b/220945250): Remove the `bundled_xctests` feature and use only the
# toolchain bit instead.
is_bundled = (
discover_tests and
swift_toolchain.test_configuration.uses_xctest_bundles and
swift_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = SWIFT_FEATURE_BUNDLED_XCTESTS,
)
)

# If we need to run the test in an .xctest bundle, the binary must have
Expand Down Expand Up @@ -255,6 +263,7 @@ def _swift_test_impl(ctx):
additional_linking_contexts.append(plugin_info.cc_info.linking_context)

srcs = ctx.files.srcs
extra_copts = []
extra_deps = []

# If no sources were provided and we're not using `.xctest` bundling, assume
Expand All @@ -263,16 +272,30 @@ def _swift_test_impl(ctx):
# (a separate module) maps to its own `swift_library`. We'll need to modify
# this approach if we want to support test discovery for simple `swift_test`
# targets that just write XCTest-style tests in the `srcs` directly.
if not srcs and not is_bundled:
srcs = _generate_test_discovery_srcs(
actions = ctx.actions,
deps = ctx.attr.deps,
name = ctx.label.name,
test_discoverer = ctx.executable._test_discoverer,
)
extra_deps = [ctx.attr._test_observer]
elif is_bundled:
if discover_tests:
if (
not srcs and
not swift_toolchain.test_configuration.uses_xctest_bundles
):
srcs = _generate_test_discovery_srcs(
actions = ctx.actions,
deps = ctx.attr.deps,
name = ctx.label.name,
test_discoverer = ctx.executable._test_discoverer,
)

# Discovered tests don't need an entry point; on Apple platforms,
# the binary is compiled as a bundle, and on non-Apple platforms,
# the generated sources above use `@main`.
# TODO(b/220945250): This should be moved out of this branch of the
# conditional, but it would break some tests that are already
# depending on this and need to be fixed.
extra_copts = ["-parse-as-library"]

# Inject the test observer that prints the xUnit-style output for Bazel.
extra_deps = [ctx.attr._test_observer]
else:
extra_copts = _maybe_parse_as_library_copts(srcs)

module_contexts = []
all_supplemental_outputs = []
Expand All @@ -292,7 +315,7 @@ def _swift_test_impl(ctx):
ctx,
ctx.attr.copts,
ctx.attr.swiftc_inputs,
) + _maybe_parse_as_library_copts(srcs),
) + extra_copts,
defines = ctx.attr.defines,
extra_swift_infos = extra_swift_infos,
feature_configuration = feature_configuration,
Expand Down Expand Up @@ -434,6 +457,27 @@ swift_test = rule(
stamp_default = 0,
),
{
"discover_tests": attr.bool(
default = True,
doc = """\
Determines whether or not tests are automatically discovered in the binary. The
default value is `True`.

If tests are discovered, then you should not provide your own `main` entry point
in the `swift_test` binary; the test runtime provides the entry point for you.
If you set this attribute to `False`, then you are responsible for providing
your own `main`. This allows you to write tests that use a framework other than
Apple's `XCTest`. The only requirement of such a test is that it terminate with
a zero exit code for success or a non-zero exit code for failure.

Additionally, on Apple platforms, test discovery is handled by the Objective-C
runtime and the output of a `swift_test` rule is an `.xctest` bundle that is
invoked using the `xctest` tool in Xcode. If this attribute is used to disable
test discovery, then the output of the `swift_test` rule will instead be a
standard executable binary that is invoked directly.
""",
mandatory = False,
),
"env": attr.string_dict(
doc = """
Dictionary of environment variables that should be set during the test execution.
Expand Down Expand Up @@ -473,35 +517,11 @@ swift_test = rule(
doc = """\
Compiles and links Swift code into an executable test target.

The behavior of `swift_test` differs slightly for macOS targets, in order to
provide seamless integration with Apple's XCTest framework. The output of the
rule is still a binary, but one whose Mach-O type is `MH_BUNDLE` (a loadable
bundle). Thus, the binary cannot be launched directly. Instead, running
`bazel test` on the target will launch a test runner script that copies it into
an `.xctest` bundle directory and then launches the `xctest` helper tool from
Xcode, which uses Objective-C runtime reflection to locate the tests.

On Linux, the output of a `swift_test` is a standard executable binary, because
the implementation of XCTest on that platform currently requires authors to
explicitly list the tests that are present and run them from their main program.

Test bundling on macOS can be disabled on a per-target basis, if desired. You
may wish to do this if you are not using XCTest, but rather a different test
framework (or no framework at all) where the pass/fail outcome is represented as
a zero/non-zero exit code (as is the case with other Bazel test rules like
`cc_test`). To do so, disable the `"swift.bundled_xctests"` feature on the
target:

```python
swift_test(
name = "MyTests",
srcs = [...],
features = ["-swift.bundled_xctests"],
)
```

You can also disable this feature for all the tests in a package by applying it
to your BUILD file's `package()` declaration instead of the individual targets.
By default, this rule performs _test discovery_ that finds tests written with
the `XCTest` framework and executes them automatically, without the user
providing their own `main` entry point. See the documentation of the
`discover_tests` attribute for more information about how this affects the rule
output and how to control this behavior.

If integrating with Xcode, the relative paths in test binaries can prevent the
Issue navigator from working for test failures. To work around this, you can
Expand Down
1 change: 1 addition & 0 deletions swift/toolchains/swift_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ def _swift_toolchain_impl(ctx):
test_configuration = struct(
env = env,
execution_requirements = {},
uses_xctest_bundles = False,
),
tool_configs = all_tool_configs,
unsupported_features = ctx.disabled_features + [
Expand Down
1 change: 1 addition & 0 deletions swift/toolchains/xcode_swift_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ def _xcode_swift_toolchain_impl(ctx):
test_configuration = struct(
env = env,
execution_requirements = execution_requirements,
uses_xctest_bundles = True,
),
tool_configs = all_tool_configs,
unsupported_features = ctx.disabled_features + [
Expand Down