From e0e3a82b9491e4d64bcea2f07798f1fb9c9811f0 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Fri, 10 Jan 2025 17:29:04 -0700 Subject: [PATCH 01/11] Add ConditionTrait.evaluate() --- Sources/Testing/Traits/ConditionTrait.swift | 31 +++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Sources/Testing/Traits/ConditionTrait.swift b/Sources/Testing/Traits/ConditionTrait.swift index 8e1117f8a..b79d11bc9 100644 --- a/Sources/Testing/Traits/ConditionTrait.swift +++ b/Sources/Testing/Traits/ConditionTrait.swift @@ -19,6 +19,14 @@ /// - ``Trait/disabled(if:_:sourceLocation:)`` /// - ``Trait/disabled(_:sourceLocation:_:)`` public struct ConditionTrait: TestTrait, SuiteTrait { + /// An enumeration identifying the result of evaluating the condition. + public enum Evaluation: Sendable { + /// The condition succeeded. + case success + /// The condition failed, potentially returning a `Comment`. + case failure(Comment?) + } + /// An enumeration describing the kinds of conditions that can be represented /// by an instance of this type. enum Kind: Sendable { @@ -79,19 +87,26 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// The source location where this trait was specified. public var sourceLocation: SourceLocation - - public func prepare(for test: Test) async throws { - let result: Bool - var commentOverride: Comment? - + + /// Returns the result of evaluating the condition. + public func evaluate() async throws -> Evaluation { switch kind { case let .conditional(condition): - (result, commentOverride) = try await condition() + switch try await condition() { + case (true, _): + .success + case (false, let comment): + .failure(comment) + } case let .unconditional(unconditionalValue): - result = unconditionalValue + unconditionalValue ? .success : .failure(nil) } + } + + public func prepare(for test: Test) async throws { + let result = try await evaluate() - if !result { + if case let .failure(commentOverride) = result { // We don't need to consider including a backtrace here because it will // primarily contain frames in the testing library, not user code. If an // error was thrown by a condition evaluated above, the caller _should_ From 2922f0242f42da2b90b5c0751b32279842e60a80 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Tue, 14 Jan 2025 11:53:34 -0700 Subject: [PATCH 02/11] Change to tuple for return value --- Sources/Testing/Traits/ConditionTrait.swift | 27 +++++++-------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Sources/Testing/Traits/ConditionTrait.swift b/Sources/Testing/Traits/ConditionTrait.swift index b79d11bc9..2beecb57c 100644 --- a/Sources/Testing/Traits/ConditionTrait.swift +++ b/Sources/Testing/Traits/ConditionTrait.swift @@ -19,13 +19,8 @@ /// - ``Trait/disabled(if:_:sourceLocation:)`` /// - ``Trait/disabled(_:sourceLocation:_:)`` public struct ConditionTrait: TestTrait, SuiteTrait { - /// An enumeration identifying the result of evaluating the condition. - public enum Evaluation: Sendable { - /// The condition succeeded. - case success - /// The condition failed, potentially returning a `Comment`. - case failure(Comment?) - } + /// The result of evaluating the condition. + public typealias Evaluation = (Bool, comment: Comment?) /// An enumeration describing the kinds of conditions that can be represented /// by an instance of this type. @@ -38,7 +33,7 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// `false` and a comment is also returned, it is used in place of the /// value of the associated trait's ``ConditionTrait/comment`` property. /// If this function returns `true`, the returned comment is ignored. - case conditional(_ body: @Sendable () async throws -> (Bool, comment: Comment?)) + case conditional(_ body: @Sendable () async throws -> Evaluation) /// Create an instance of this type associated with a trait that is /// conditional on the result of calling a function. @@ -49,7 +44,7 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// /// - Returns: An instance of this type. static func conditional(_ body: @escaping @Sendable () async throws -> Bool) -> Self { - conditional { () -> (Bool, comment: Comment?) in + conditional { () -> Evaluation in return (try await body(), nil) } } @@ -89,24 +84,20 @@ public struct ConditionTrait: TestTrait, SuiteTrait { public var sourceLocation: SourceLocation /// Returns the result of evaluating the condition. + @_spi(Experimental) public func evaluate() async throws -> Evaluation { switch kind { case let .conditional(condition): - switch try await condition() { - case (true, _): - .success - case (false, let comment): - .failure(comment) - } + try await condition() case let .unconditional(unconditionalValue): - unconditionalValue ? .success : .failure(nil) + (unconditionalValue, nil) } } public func prepare(for test: Test) async throws { - let result = try await evaluate() + let (isEnabled, commentOverride) = try await evaluate() - if case let .failure(commentOverride) = result { + if !isEnabled { // We don't need to consider including a backtrace here because it will // primarily contain frames in the testing library, not user code. If an // error was thrown by a condition evaluated above, the caller _should_ From 6c841903a91eac434f3e873051bc659e2de6a162 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Tue, 14 Jan 2025 15:07:29 -0700 Subject: [PATCH 03/11] Change name and add comments --- Sources/Testing/Traits/ConditionTrait.swift | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/Testing/Traits/ConditionTrait.swift b/Sources/Testing/Traits/ConditionTrait.swift index 2beecb57c..0ce1ff49c 100644 --- a/Sources/Testing/Traits/ConditionTrait.swift +++ b/Sources/Testing/Traits/ConditionTrait.swift @@ -20,8 +20,13 @@ /// - ``Trait/disabled(_:sourceLocation:_:)`` public struct ConditionTrait: TestTrait, SuiteTrait { /// The result of evaluating the condition. - public typealias Evaluation = (Bool, comment: Comment?) - + /// + /// - Parameters: + /// - wasMet: Whether or not the condition was met. + /// - comment: Optionally, a comment describing the result of evaluating the condition. + @_spi(Experimental) + public typealias EvaluationResult = (wasMet: Bool, comment: Comment?) + /// An enumeration describing the kinds of conditions that can be represented /// by an instance of this type. enum Kind: Sendable { @@ -33,7 +38,7 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// `false` and a comment is also returned, it is used in place of the /// value of the associated trait's ``ConditionTrait/comment`` property. /// If this function returns `true`, the returned comment is ignored. - case conditional(_ body: @Sendable () async throws -> Evaluation) + case conditional(_ body: @Sendable () async throws -> EvaluationResult) /// Create an instance of this type associated with a trait that is /// conditional on the result of calling a function. @@ -44,7 +49,7 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// /// - Returns: An instance of this type. static func conditional(_ body: @escaping @Sendable () async throws -> Bool) -> Self { - conditional { () -> Evaluation in + conditional { () -> EvaluationResult in return (try await body(), nil) } } @@ -83,9 +88,14 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// The source location where this trait was specified. public var sourceLocation: SourceLocation - /// Returns the result of evaluating the condition. + /// Evaluate this instance's underlying condition. + /// + /// - Returns: The result of evaluating this instance's underlying condition. + /// + /// The evaluation is performed each time this function is called, and is not + /// cached. @_spi(Experimental) - public func evaluate() async throws -> Evaluation { + public func evaluate() async throws -> EvaluationResult { switch kind { case let .conditional(condition): try await condition() From 1a08ed340786d3b992295d547858d4f54bf08984 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Tue, 14 Jan 2025 15:08:52 -0700 Subject: [PATCH 04/11] Draft proposal --- .../Proposals/NNNN-evaluate-condition.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Documentation/Proposals/NNNN-evaluate-condition.md diff --git a/Documentation/Proposals/NNNN-evaluate-condition.md b/Documentation/Proposals/NNNN-evaluate-condition.md new file mode 100644 index 000000000..b9fcd1e33 --- /dev/null +++ b/Documentation/Proposals/NNNN-evaluate-condition.md @@ -0,0 +1,57 @@ +# Public API to evaluate ConditionTrait + +* Proposal: [SWT-NNNN](NNNN-evaluate-condition.md) +* Authors: [David Catmull](https://github.com/Uncommon) +* Status: **Awaiting review** +* Implementation: [swiftlang/swift-testing#909](https://github.com/swiftlang/swift-testing/pull/909) +* Review: ([pitch](https://forums.swift.org/...)) + +## Introduction + +This adds an `evaluate()` method to `ConditionTrait` to evaluate the condition +without requiring a `Test` instance. + +## Motivation + +Currently, the only way a `ConditionTrait` is evaluated is inside the +`prepare(for:)` method. This makes it difficult for third-party libraries to +utilize these traits because evaluating a condition would require creating a +dummy `Test` to pass to that method. + +## Proposed solution + +The proposal is to add a `ConditionTrait.evaluate()` method which returns the +result of the evaluation. The existing `prepare(for:)` method is updated to call +`evaluate()` so that the logic is not duplicated. + +## Detailed design + +The signature is `evaluate() async throws -> Evaluation`, where `Evaluation` is +a `typealias` for the tuple already used for the callback in `Kind.conditional`, +containing a boolean result and an optional comment. + +## Source compatibility + +This change is purely additive. + +## Integration with supporting tools + +This change allows third-party libraries to apply condition traits at other +levels than suites or whole test functions, for example if tests are broken up +into smaller sections. + +## Future directions + +This change seems sufficient for third party libraries to make use of +`ConditionTrait`. Changes for other traits can be tackled in separate proposals. + +## Alternatives considered + +Exposing `ConditionTrait.Kind` and `.kind` was also considered, but it seemed +unnecessary to go that far, and it would encourage duplicating the logic that +already exists in `prepare(for:)`. + +In the first draft implementation, the `Evaluation` type was an enum that only +contained the comment in the failure case. It was changed to match the existing +tuple to allow for potentially including comments for the success case without +requiring a change to the API. From 783595aeb9555bfaa1d5c1e7c595398b987a04a7 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Thu, 16 Jan 2025 09:05:28 -0700 Subject: [PATCH 05/11] Add forum thread link --- Documentation/Proposals/NNNN-evaluate-condition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Proposals/NNNN-evaluate-condition.md b/Documentation/Proposals/NNNN-evaluate-condition.md index b9fcd1e33..ccfc50e85 100644 --- a/Documentation/Proposals/NNNN-evaluate-condition.md +++ b/Documentation/Proposals/NNNN-evaluate-condition.md @@ -4,7 +4,7 @@ * Authors: [David Catmull](https://github.com/Uncommon) * Status: **Awaiting review** * Implementation: [swiftlang/swift-testing#909](https://github.com/swiftlang/swift-testing/pull/909) -* Review: ([pitch](https://forums.swift.org/...)) +* Review: ([pitch](https://forums.swift.org/t/pitch-introduce-conditiontrait-evaluate/77242)) ## Introduction From b49502287f08f0796fa80dea4f8efb0384b5a960 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Thu, 16 Jan 2025 12:56:40 -0700 Subject: [PATCH 06/11] Update proposal with specific code samples --- .../Proposals/NNNN-evaluate-condition.md | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Documentation/Proposals/NNNN-evaluate-condition.md b/Documentation/Proposals/NNNN-evaluate-condition.md index ccfc50e85..3fc8b9ad4 100644 --- a/Documentation/Proposals/NNNN-evaluate-condition.md +++ b/Documentation/Proposals/NNNN-evaluate-condition.md @@ -26,9 +26,26 @@ result of the evaluation. The existing `prepare(for:)` method is updated to call ## Detailed design -The signature is `evaluate() async throws -> Evaluation`, where `Evaluation` is -a `typealias` for the tuple already used for the callback in `Kind.conditional`, -containing a boolean result and an optional comment. +The `evaluate()` method is as follows, containing essentially the same logic +as was in `prepare(for:)`: + +```swift +public func evaluate() async throws -> EvaluationResult { + switch kind { + case let .conditional(condition): + try await condition() + case let .unconditional(unconditionalValue): + (unconditionalValue, nil) + } +} +``` + +`EvaluationResult` is a `typealias` for the tuple already used as the result +of the callback in `Kind.conditional`: + +```swift +public typealias EvaluationResult = (wasMet: Bool, comment: Comment?) +``` ## Source compatibility @@ -51,7 +68,7 @@ Exposing `ConditionTrait.Kind` and `.kind` was also considered, but it seemed unnecessary to go that far, and it would encourage duplicating the logic that already exists in `prepare(for:)`. -In the first draft implementation, the `Evaluation` type was an enum that only -contained the comment in the failure case. It was changed to match the existing -tuple to allow for potentially including comments for the success case without -requiring a change to the API. +In the first draft implementation, the `EvaluationResult` type was an enum that +only contained the comment in the failure case. It was changed to match the +existing tuple to allow for potentially including comments for the success case +without requiring a change to the API. From d39d5096375944cee4ecf28bfc0b0902babce5cc Mon Sep 17 00:00:00 2001 From: David Catmull Date: Fri, 17 Jan 2025 07:45:26 -0700 Subject: [PATCH 07/11] Add tests for ConditionTrait.evaluate() --- Tests/TestingTests/RunnerTests.swift | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Tests/TestingTests/RunnerTests.swift b/Tests/TestingTests/RunnerTests.swift index 857cfdd81..4474ef0db 100644 --- a/Tests/TestingTests/RunnerTests.swift +++ b/Tests/TestingTests/RunnerTests.swift @@ -332,6 +332,32 @@ final class RunnerTests: XCTestCase { let test2 = Test(.disabled(if: Bool.random())) { } XCTAssertTrue(test2.traits.compactMap { $0 as? ConditionTrait }.allSatisfy { !$0.isConstant }) } + + func testEvaluateConditionTrait() async throws { + let comment: Comment = "comment" + let trueUnconditional = ConditionTrait(kind: .unconditional(true), comments: [], sourceLocation: .__here()) + let falseUnconditional = ConditionTrait.disabled() + let enabledTrue = ConditionTrait.enabled(if: true) + let enabledFalse = ConditionTrait.enabled(if: false) + let enabledTrueComment = ConditionTrait(kind: .conditional { (true, comment) }, comments: [], sourceLocation: .__here()) + let enabledFalseComment = ConditionTrait(kind: .conditional { (false, comment) }, comments: [], sourceLocation: .__here()) + var result: ConditionTrait.EvaluationResult + + result = try await trueUnconditional.evaluate() + XCTAssertTrue(result.wasMet) + result = try await falseUnconditional.evaluate() + XCTAssertFalse(result.wasMet) + result = try await enabledTrue.evaluate() + XCTAssertTrue(result.wasMet) + result = try await enabledFalse.evaluate() + XCTAssertFalse(result.wasMet) + result = try await enabledTrueComment.evaluate() + XCTAssertTrue(result.wasMet) + XCTAssertEqual(result.comment, comment) + result = try await enabledFalseComment.evaluate() + XCTAssertFalse(result.wasMet) + XCTAssertEqual(result.comment, comment) + } func testGeneratedPlan() async throws { let tests: [(Any.Type, String)] = [ From 22fece102172cee0208383060ca48db23a36290f Mon Sep 17 00:00:00 2001 From: David Catmull Date: Fri, 17 Jan 2025 08:43:08 -0700 Subject: [PATCH 08/11] Use #_sourceLocation macro instead of __here --- Tests/TestingTests/RunnerTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/TestingTests/RunnerTests.swift b/Tests/TestingTests/RunnerTests.swift index 4474ef0db..9bd33c9da 100644 --- a/Tests/TestingTests/RunnerTests.swift +++ b/Tests/TestingTests/RunnerTests.swift @@ -335,12 +335,12 @@ final class RunnerTests: XCTestCase { func testEvaluateConditionTrait() async throws { let comment: Comment = "comment" - let trueUnconditional = ConditionTrait(kind: .unconditional(true), comments: [], sourceLocation: .__here()) + let trueUnconditional = ConditionTrait(kind: .unconditional(true), comments: [], sourceLocation: #_sourceLocation) let falseUnconditional = ConditionTrait.disabled() let enabledTrue = ConditionTrait.enabled(if: true) let enabledFalse = ConditionTrait.enabled(if: false) - let enabledTrueComment = ConditionTrait(kind: .conditional { (true, comment) }, comments: [], sourceLocation: .__here()) - let enabledFalseComment = ConditionTrait(kind: .conditional { (false, comment) }, comments: [], sourceLocation: .__here()) + let enabledTrueComment = ConditionTrait(kind: .conditional { (true, comment) }, comments: [], sourceLocation: #_sourceLocation) + let enabledFalseComment = ConditionTrait(kind: .conditional { (false, comment) }, comments: [], sourceLocation: #_sourceLocation) var result: ConditionTrait.EvaluationResult result = try await trueUnconditional.evaluate() From e420c878d64976b584d75016fec6c291526ace41 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Sat, 15 Mar 2025 13:33:12 -0600 Subject: [PATCH 09/11] Proposal document moved to swift-evolution repo --- .../Proposals/NNNN-evaluate-condition.md | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 Documentation/Proposals/NNNN-evaluate-condition.md diff --git a/Documentation/Proposals/NNNN-evaluate-condition.md b/Documentation/Proposals/NNNN-evaluate-condition.md deleted file mode 100644 index 3fc8b9ad4..000000000 --- a/Documentation/Proposals/NNNN-evaluate-condition.md +++ /dev/null @@ -1,74 +0,0 @@ -# Public API to evaluate ConditionTrait - -* Proposal: [SWT-NNNN](NNNN-evaluate-condition.md) -* Authors: [David Catmull](https://github.com/Uncommon) -* Status: **Awaiting review** -* Implementation: [swiftlang/swift-testing#909](https://github.com/swiftlang/swift-testing/pull/909) -* Review: ([pitch](https://forums.swift.org/t/pitch-introduce-conditiontrait-evaluate/77242)) - -## Introduction - -This adds an `evaluate()` method to `ConditionTrait` to evaluate the condition -without requiring a `Test` instance. - -## Motivation - -Currently, the only way a `ConditionTrait` is evaluated is inside the -`prepare(for:)` method. This makes it difficult for third-party libraries to -utilize these traits because evaluating a condition would require creating a -dummy `Test` to pass to that method. - -## Proposed solution - -The proposal is to add a `ConditionTrait.evaluate()` method which returns the -result of the evaluation. The existing `prepare(for:)` method is updated to call -`evaluate()` so that the logic is not duplicated. - -## Detailed design - -The `evaluate()` method is as follows, containing essentially the same logic -as was in `prepare(for:)`: - -```swift -public func evaluate() async throws -> EvaluationResult { - switch kind { - case let .conditional(condition): - try await condition() - case let .unconditional(unconditionalValue): - (unconditionalValue, nil) - } -} -``` - -`EvaluationResult` is a `typealias` for the tuple already used as the result -of the callback in `Kind.conditional`: - -```swift -public typealias EvaluationResult = (wasMet: Bool, comment: Comment?) -``` - -## Source compatibility - -This change is purely additive. - -## Integration with supporting tools - -This change allows third-party libraries to apply condition traits at other -levels than suites or whole test functions, for example if tests are broken up -into smaller sections. - -## Future directions - -This change seems sufficient for third party libraries to make use of -`ConditionTrait`. Changes for other traits can be tackled in separate proposals. - -## Alternatives considered - -Exposing `ConditionTrait.Kind` and `.kind` was also considered, but it seemed -unnecessary to go that far, and it would encourage duplicating the logic that -already exists in `prepare(for:)`. - -In the first draft implementation, the `EvaluationResult` type was an enum that -only contained the comment in the failure case. It was changed to match the -existing tuple to allow for potentially including comments for the success case -without requiring a change to the API. From 9e7a2b92fdb5699fc2d5168ce29136687f7989a6 Mon Sep 17 00:00:00 2001 From: David Catmull Date: Fri, 28 Mar 2025 10:28:18 -0600 Subject: [PATCH 10/11] Restore accidental deletion --- Sources/Testing/Traits/ConditionTrait.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Testing/Traits/ConditionTrait.swift b/Sources/Testing/Traits/ConditionTrait.swift index f09f36476..a3b98c99d 100644 --- a/Sources/Testing/Traits/ConditionTrait.swift +++ b/Sources/Testing/Traits/ConditionTrait.swift @@ -26,6 +26,7 @@ public struct ConditionTrait: TestTrait, SuiteTrait { /// /// - Parameters: /// - body: The function to call. The result of this function determines + /// if the condition is satisfied or not. case conditional(_ body: @Sendable () async throws -> Bool) /// The trait is unconditional and always has the same result. From 0f0162663c14e47d2e25cb1603b6f2e897dd259f Mon Sep 17 00:00:00 2001 From: David Catmull Date: Fri, 28 Mar 2025 12:17:11 -0600 Subject: [PATCH 11/11] Move test to ConditionTraitTests --- Tests/TestingTests/RunnerTests.swift | 17 ---------------- .../Traits/ConditionTraitTests.swift | 20 ++++++++++++++++++- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Tests/TestingTests/RunnerTests.swift b/Tests/TestingTests/RunnerTests.swift index 830c0756e..335f8be37 100644 --- a/Tests/TestingTests/RunnerTests.swift +++ b/Tests/TestingTests/RunnerTests.swift @@ -332,23 +332,6 @@ final class RunnerTests: XCTestCase { let test2 = Test(.disabled(if: Bool.random())) { } XCTAssertTrue(test2.traits.compactMap { $0 as? ConditionTrait }.allSatisfy { !$0.isConstant }) } - - func testEvaluateConditionTrait() async throws { - let trueUnconditional = ConditionTrait(kind: .unconditional(true), comments: [], sourceLocation: #_sourceLocation) - let falseUnconditional = ConditionTrait.disabled() - let enabledTrue = ConditionTrait.enabled(if: true) - let enabledFalse = ConditionTrait.enabled(if: false) - var result: Bool - - result = try await trueUnconditional.evaluate() - XCTAssertTrue(result) - result = try await falseUnconditional.evaluate() - XCTAssertFalse(result) - result = try await enabledTrue.evaluate() - XCTAssertTrue(result) - result = try await enabledFalse.evaluate() - XCTAssertFalse(result) - } func testGeneratedPlan() async throws { let tests: [(Any.Type, String)] = [ diff --git a/Tests/TestingTests/Traits/ConditionTraitTests.swift b/Tests/TestingTests/Traits/ConditionTraitTests.swift index 5d70c8e87..6b5311202 100644 --- a/Tests/TestingTests/Traits/ConditionTraitTests.swift +++ b/Tests/TestingTests/Traits/ConditionTraitTests.swift @@ -8,7 +8,7 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // -@testable import Testing +@testable @_spi(Experimental) import Testing @Suite("Condition Trait Tests", .tags(.traitRelated)) struct ConditionTraitTests { @@ -41,4 +41,22 @@ struct ConditionTraitTests { .disabled(if: false) ) func disabledTraitIf() throws {} + + @Test + func evaluateCondition() async throws { + let trueUnconditional = ConditionTrait(kind: .unconditional(true), comments: [], sourceLocation: #_sourceLocation) + let falseUnconditional = ConditionTrait.disabled() + let enabledTrue = ConditionTrait.enabled(if: true) + let enabledFalse = ConditionTrait.enabled(if: false) + var result: Bool + + result = try await trueUnconditional.evaluate() + #expect(result) + result = try await falseUnconditional.evaluate() + #expect(!result) + result = try await enabledTrue.evaluate() + #expect(result) + result = try await enabledFalse.evaluate() + #expect(!result) + } }