1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
/// A type that defines a condition which must be satisfied for a test to be
/// enabled.
///
/// To add this trait to a test, use one of the following functions:
///
/// - ``Trait/enabled(if:_:sourceLocation:)``
/// - ``Trait/enabled(_:sourceLocation:_:)``
/// - ``Trait/disabled(_:sourceLocation:)``
/// - ``Trait/disabled(if:_:sourceLocation:)``
/// - ``Trait/disabled(_:sourceLocation:_:)``
public struct ConditionTrait: TestTrait, SuiteTrait {
/// An enumeration describing the kinds of conditions that can be represented
/// by an instance of this type.
enum Kind: Sendable {
/// The trait is conditional on the result of calling a function.
///
/// - Parameters:
/// - body: The function to call. The result of this function determines
/// if the condition is satisfied or not. If this function returns
/// `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?))
/// Create an instance of this type associated with a trait that is
/// conditional on the result of calling a function.
///
/// - Parameters:
/// - body: The function to call. The result of this function determines
/// whether or not the condition was met.
///
/// - Returns: An instance of this type.
static func conditional(_ body: @escaping @Sendable () async throws -> Bool) -> Self {
conditional { () -> (Bool, comment: Comment?) in
return (try await body(), nil)
}
}
/// The trait is unconditional and always has the same result.
///
/// - Parameters:
/// - value: Whether or not the condition was met.
case unconditional(_ value: Bool)
}
/// The kind of condition represented by this instance.
var kind: Kind
/// Whether or not this trait has a condition that is evaluated at runtime.
///
/// If this trait was created using a function such as
/// ``disabled(_:sourceLocation:)`` that unconditionally enables or disables a
/// test, the value of this property is `true`.
///
/// If this trait was created using a function such as
/// ``enabled(if:_:sourceLocation:)`` that is evaluated at runtime, the value
/// of this property is `false`.
@_spi(ForToolsIntegrationOnly)
public var isConstant: Bool {
switch kind {
case .conditional:
return false
case .unconditional:
return true
}
}
public var comments: [Comment]
/// 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?
switch kind {
case let .conditional(condition):
(result, commentOverride) = try await condition()
case let .unconditional(unconditionalValue):
result = unconditionalValue
}
if !result {
let sourceContext = SourceContext(sourceLocation: sourceLocation)
throw SkipInfo(comment: commentOverride ?? comments.first, sourceContext: sourceContext)
}
}
public var isRecursive: Bool {
true
}
}
// MARK: -
extension Trait where Self == ConditionTrait {
/// Construct a condition trait that causes a test to be disabled if it
/// returns `false`.
///
/// - Parameters:
/// - condition: A closure containing the trait's custom condition logic. If
/// this closure returns `true`, the test is allowed to run. Otherwise,
/// the test is skipped.
/// - comment: An optional, user-specified comment describing this trait.
/// - sourceLocation: The source location of the trait.
///
/// - Returns: An instance of ``ConditionTrait`` that will evaluate the
/// specified closure.
///
/// @Comment {
/// - Bug: `condition` cannot be `async` without making this function
/// `async` even though `condition` is not evaluated locally.
/// ([103037177](rdar://103037177))
/// }
public static func enabled(
if condition: @autoclosure @escaping @Sendable () throws -> Bool,
_ comment: Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) -> Self {
Self(kind: .conditional(condition), comments: Array(comment), sourceLocation: sourceLocation)
}
/// Construct a condition trait that causes a test to be disabled if it
/// returns `false`.
///
/// - Parameters:
/// - comment: An optional, user-specified comment describing this trait.
/// - sourceLocation: The source location of the trait.
/// - condition: A closure containing the trait's custom condition logic. If
/// this closure returns `true`, the test is allowed to run. Otherwise,
/// the test is skipped.
///
/// - Returns: An instance of ``ConditionTrait`` that will evaluate the
/// specified closure.
public static func enabled(
_ comment: Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation,
_ condition: @escaping @Sendable () async throws -> Bool
) -> Self {
Self(kind: .conditional(condition), comments: Array(comment), sourceLocation: sourceLocation)
}
/// Construct a condition trait that disables a test unconditionally.
///
/// - Parameters:
/// - comment: An optional, user-specified comment describing this trait.
/// - sourceLocation: The source location of the trait.
///
/// - Returns: An instance of ``ConditionTrait`` that will always disable the
/// test to which it is added.
public static func disabled(
_ comment: Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) -> Self {
Self(kind: .unconditional(false), comments: Array(comment), sourceLocation: sourceLocation)
}
/// Construct a condition trait that causes a test to be disabled if it
/// returns `true`.
///
/// - Parameters:
/// - condition: A closure containing the trait's custom condition logic. If
/// this closure returns `false`, the test is allowed to run. Otherwise,
/// the test is skipped.
/// - comment: An optional, user-specified comment describing this trait.
/// - sourceLocation: The source location of the trait.
///
/// - Returns: An instance of ``ConditionTrait`` that will evaluate the
/// specified closure.
///
/// @Comment {
/// - Bug: `condition` cannot be `async` without making this function
/// `async` even though `condition` is not evaluated locally.
/// ([103037177](rdar://103037177))
/// }
public static func disabled(
if condition: @autoclosure @escaping @Sendable () throws -> Bool,
_ comment: Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) -> Self {
Self(kind: .conditional { !(try condition()) }, comments: Array(comment), sourceLocation: sourceLocation)
}
/// Construct a condition trait that causes a test to be disabled if it
/// returns `true`.
///
/// - Parameters:
/// - comment: An optional, user-specified comment describing this trait.
/// - sourceLocation: The source location of the trait.
/// - condition: A closure containing the trait's custom condition logic. If
/// this closure returns `false`, the test is allowed to run. Otherwise,
/// the test is skipped.
///
/// - Returns: An instance of ``ConditionTrait`` that will evaluate the
/// specified closure.
public static func disabled(
_ comment: Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation,
_ condition: @escaping @Sendable () async throws -> Bool
) -> Self {
Self(kind: .conditional { !(try await condition()) }, comments: Array(comment), sourceLocation: sourceLocation)
}
}
|