@@ -197,9 +197,17 @@ def _swift_test_impl(ctx):
197197 unsupported_features = ctx .disabled_features ,
198198 )
199199
200- is_bundled = swift_common .is_enabled (
201- feature_configuration = feature_configuration ,
202- feature_name = SWIFT_FEATURE_BUNDLED_XCTESTS ,
200+ discover_tests = ctx .attr .discover_tests
201+
202+ # TODO(b/220945250): Remove the `bundled_xctests` feature and use only the
203+ # toolchain bit instead.
204+ is_bundled = (
205+ discover_tests and
206+ swift_toolchain .test_configuration .uses_xctest_bundles and
207+ swift_common .is_enabled (
208+ feature_configuration = feature_configuration ,
209+ feature_name = SWIFT_FEATURE_BUNDLED_XCTESTS ,
210+ )
203211 )
204212
205213 # If we need to run the test in an .xctest bundle, the binary must have
@@ -216,18 +224,27 @@ def _swift_test_impl(ctx):
216224 # (a separate module) maps to its own `swift_library`. We'll need to modify
217225 # this approach if we want to support test discovery for simple `swift_test`
218226 # targets that just write XCTest-style tests in the `srcs` directly.
219- if not srcs and not is_bundled :
220- srcs = _generate_test_discovery_srcs (
221- actions = ctx .actions ,
222- deps = ctx .attr .deps ,
223- name = ctx .label .name ,
224- test_discoverer = ctx .executable ._test_discoverer ,
225- )
227+ if discover_tests :
228+ if (
229+ not srcs and
230+ not swift_toolchain .test_configuration .uses_xctest_bundles
231+ ):
232+ srcs = _generate_test_discovery_srcs (
233+ actions = ctx .actions ,
234+ deps = ctx .attr .deps ,
235+ name = ctx .label .name ,
236+ test_discoverer = ctx .executable ._test_discoverer ,
237+ )
226238
227- # The generated test runner uses `@main`.
228- extra_copts = ["-parse-as-library" ]
229- extra_deps = [ctx .attr ._test_observer ]
230- elif is_bundled :
239+ # Discovered tests don't need an entry point; on Apple platforms,
240+ # the binary is compiled as a bundle, and on non-Apple platforms,
241+ # the generated sources above use `@main`.
242+ # TODO(b/220945250): This should be moved out of this branch of the
243+ # conditional, but it would break some tests that are already
244+ # depending on this and need to be fixed.
245+ extra_copts = ["-parse-as-library" ]
246+
247+ # Inject the test observer that prints the xUnit-style output for Bazel.
231248 extra_deps = [ctx .attr ._test_observer ]
232249
233250 output_groups = {}
@@ -348,6 +365,27 @@ swift_test = rule(
348365 stamp_default = 0 ,
349366 ),
350367 {
368+ "discover_tests" : attr .bool (
369+ default = True ,
370+ doc = """\
371+ Determines whether or not tests are automatically discovered in the binary. The
372+ default value is `True`.
373+
374+ If tests are discovered, then you should not provide your own `main` entry point
375+ in the `swift_test` binary; the test runtime provides the entry point for you.
376+ If you set this attribute to `False`, then you are responsible for providing
377+ your own `main`. This allows you to write tests that use a framework other than
378+ Apple's `XCTest`. The only requirement of such a test is that it terminate with
379+ a zero exit code for success or a non-zero exit code for failure.
380+
381+ Additionally, on Apple platforms, test discovery is handled by the Objective-C
382+ runtime and the output of a `swift_test` rule is an `.xctest` bundle that is
383+ invoked using the `xctest` tool in Xcode. If this attribute is used to disable
384+ test discovery, then the output of the `swift_test` rule will instead be a
385+ standard executable binary that is invoked directly.
386+ """ ,
387+ mandatory = False ,
388+ ),
351389 "_apple_coverage_support" : attr .label (
352390 cfg = "exec" ,
353391 default = Label (
@@ -377,35 +415,11 @@ swift_test = rule(
377415 doc = """\
378416 Compiles and links Swift code into an executable test target.
379417
380- The behavior of `swift_test` differs slightly for macOS targets, in order to
381- provide seamless integration with Apple's XCTest framework. The output of the
382- rule is still a binary, but one whose Mach-O type is `MH_BUNDLE` (a loadable
383- bundle). Thus, the binary cannot be launched directly. Instead, running
384- `bazel test` on the target will launch a test runner script that copies it into
385- an `.xctest` bundle directory and then launches the `xctest` helper tool from
386- Xcode, which uses Objective-C runtime reflection to locate the tests.
387-
388- On Linux, the output of a `swift_test` is a standard executable binary, because
389- the implementation of XCTest on that platform currently requires authors to
390- explicitly list the tests that are present and run them from their main program.
391-
392- Test bundling on macOS can be disabled on a per-target basis, if desired. You
393- may wish to do this if you are not using XCTest, but rather a different test
394- framework (or no framework at all) where the pass/fail outcome is represented as
395- a zero/non-zero exit code (as is the case with other Bazel test rules like
396- `cc_test`). To do so, disable the `"swift.bundled_xctests"` feature on the
397- target:
398-
399- ```python
400- swift_test(
401- name = "MyTests",
402- srcs = [...],
403- features = ["-swift.bundled_xctests"],
404- )
405- ```
406-
407- You can also disable this feature for all the tests in a package by applying it
408- to your BUILD file's `package()` declaration instead of the individual targets.
418+ By default, this rule performs _test discovery_ that finds tests written with
419+ the `XCTest` framework and executes them automatically, without the user
420+ providing their own `main` entry point. See the documentation of the
421+ `discover_tests` attribute for more information about how this affects the rule
422+ output and how to control this behavior.
409423""" ,
410424 executable = True ,
411425 fragments = ["cpp" ],
0 commit comments