File: CustomTestStringConvertible.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (202 lines) | stat: -rw-r--r-- 6,685 bytes parent folder | download
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
//
// 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 protocol describing types with a custom string representation when
/// presented as part of a test's output.
///
/// Values whose types conform to this protocol use it to describe themselves
/// when they are present as part of the output of a test. For example, this
/// protocol affects the display of values that are passed as arguments to test
/// functions or that are elements of an expectation failure.
///
/// By default, the testing library converts values to strings using
/// `String(describing:)`. The resulting string may be inappropriate for some
/// types and their values. If the type of the value is made to conform to
/// ``CustomTestStringConvertible``, then the value of its ``testDescription``
/// property will be used instead.
///
/// For example, consider the following type:
///
/// ```swift
/// enum Food: CaseIterable {
///   case paella, oden, ragu
/// }
/// ```
///
/// If an array of cases from this enumeration is passed to a parameterized test
/// function:
///
/// ```swift
/// @Test(arguments: Food.allCases)
/// func isDelicious(_ food: Food) { ... }
/// ```
///
/// Then the values in the array need to be presented in the test output, but
/// the default description of a value may not be adequately descriptive:
///
/// ```
/// ◇ Passing argument food → .paella to isDelicious(_:)
/// ◇ Passing argument food → .oden to isDelicious(_:)
/// ◇ Passing argument food → .ragu to isDelicious(_:)
/// ```
///
/// By adopting ``CustomTestStringConvertible``, customized descriptions can be
/// included:
///
/// ```swift
/// extension Food: CustomTestStringConvertible {
///   var testDescription: String {
///     switch self {
///     case .paella:
///       "paella valenciana"
///     case .oden:
///       "おでん"
///     case .ragu:
///       "ragù alla bolognese"
///     }
///   }
/// }
/// ```
///
/// The presentation of these values will then reflect the value of the
/// ``testDescription`` property:
///
/// ```
/// ◇ Passing argument food → paella valenciana to isDelicious(_:)
/// ◇ Passing argument food → おでん to isDelicious(_:)
/// ◇ Passing argument food → ragù alla bolognese to isDelicious(_:)
/// ```
///
/// ## See Also
///
/// - ``Swift/String/init(describingForTest:)``
public protocol CustomTestStringConvertible {
  /// A description of this instance to use when presenting it in a test's
  /// output.
  ///
  /// Do not use this property directly. To get the test description of a value,
  /// use ``Swift/String/init(describingForTest:)``.
  var testDescription: String { get }
}

extension String {
  /// Initialize this instance so that it can be presented in a test's output.
  ///
  /// - Parameters:
  ///   - value: The value to describe.
  ///
  /// ## See Also
  ///
  /// - ``CustomTestStringConvertible``
  public init(describingForTest value: some Any) {
    // The mangled type name SPI doesn't handle generic types very well, so we
    // ask for the dynamic type of `value` (type(of:)) instead of just T.self.
    lazy var valueTypeInfo = TypeInfo(describingTypeOf: value)
    if let value = value as? any CustomTestStringConvertible {
      self = value.testDescription
    } else if let value = value as? any CustomStringConvertible {
      self.init(describing: value)
    } else if let value = value as? any TextOutputStreamable {
      self.init(describing: value)
    } else if let value = value as? any CustomDebugStringConvertible {
      self.init(reflecting: value)
    } else if let value = value as? any Any.Type {
      self = _testDescription(of: value)
    } else if let value = value as? any RawRepresentable, let type = valueTypeInfo.type, valueTypeInfo.isImportedFromC {
      // Present raw-representable C types, which we assume to be imported
      // enumerations, in a consistent fashion. The case names of C enumerations
      // are not statically visible, so instead present the enumeration type's
      // name along with the raw value of `value`.
      let typeName = String(describingForTest: type)
      self = "\(typeName)(rawValue: \(String(describingForTest: value.rawValue)))"
    } else if valueTypeInfo.isSwiftEnumeration {
      // Add a leading period to enumeration cases to more closely match their
      // source representation. SEE: _adHocPrint_unlocked() in the stdlib.
      self = ".\(value)"
    } else {
      // Use the generic description of the value.
      self.init(describing: value)
    }
  }
}

// MARK: - Built-in implementations

/// The _de facto_ implementation of ``CustomTestStringConvertible`` for a
/// metatype value.
///
/// - Parameters:
///   - type: The type to describe.
///
/// - Returns: The description of `type`, as produced by
///   ``Swift/String/init(describingForTest:)``.
private func _testDescription(of type: any Any.Type) -> String {
  TypeInfo(describing: type).unqualifiedName
}

extension Optional: CustomTestStringConvertible {
  public var testDescription: String {
    switch self {
    case let .some(unwrappedValue):
      String(describingForTest: unwrappedValue)
    case nil:
      "nil"
    }
  }
}

extension _OptionalNilComparisonType: CustomTestStringConvertible {
  public var testDescription: String {
    "nil"
  }
}

// MARK: - Strings

extension CustomTestStringConvertible where Self: StringProtocol {
  public var testDescription: String {
    "\"\(self)\""
  }
}

extension String: CustomTestStringConvertible {}
extension Substring: CustomTestStringConvertible {}

// MARK: - Ranges

extension ClosedRange: CustomTestStringConvertible {
  public var testDescription: String {
    "\(String(describingForTest: lowerBound)) ... \(String(describingForTest: upperBound))"
  }
}

extension PartialRangeFrom: CustomTestStringConvertible {
  public var testDescription: String {
    "\(String(describingForTest: lowerBound))..."
  }
}

extension PartialRangeThrough: CustomTestStringConvertible {
  public var testDescription: String {
    "...\(String(describingForTest: upperBound))"
  }
}

extension PartialRangeUpTo: CustomTestStringConvertible {
  public var testDescription: String {
    "..<\(String(describingForTest: upperBound))"
  }
}

extension Range: CustomTestStringConvertible {
  public var testDescription: String {
    "\(String(describingForTest: lowerBound)) ..< \(String(describingForTest: upperBound))"
  }
}