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
|
/*
This source file is part of the Swift System open source project
Copyright (c) 2020 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
*/
import XCTest
#if SYSTEM_PACKAGE
@testable import SystemPackage
#else
@testable import System
#endif
// To aid debugging, force failures to fatal error
internal var forceFatalFailures = false
internal protocol TestCase {
// TODO: want a source location stack, more fidelity, kinds of stack entries, etc
var file: StaticString { get }
var line: UInt { get }
// TODO: Instead have an attribute to register a test in a allTests var, similar to the argument parser.
func runAllTests()
// Customization hook: add adornment to reported failure reason
// Defaut: reason or empty
func failureMessage(_ reason: String?) -> String
}
extension TestCase {
// Default implementation
func failureMessage(_ reason: String?) -> String { reason ?? "" }
func expectEqualSequence<S1: Sequence, S2: Sequence>(
_ expected: S1, _ actual: S2,
_ message: String? = nil
) where S1.Element: Equatable, S1.Element == S2.Element {
if !expected.elementsEqual(actual) {
defer { print("expected: \(expected), actual: \(actual)") }
fail(message)
}
}
func expectEqual<E: Equatable>(
_ expected: E, _ actual: E,
_ message: String? = nil
) {
if actual != expected {
defer { print("expected: \(expected), actual: \(actual)") }
fail(message)
}
}
func expectNotEqual<E: Equatable>(
_ expected: E, _ actual: E,
_ message: String? = nil
) {
if actual == expected {
defer { print("expected not equal: \(expected) and \(actual)") }
fail(message)
}
}
func expectNil<T>(
_ actual: T?,
_ message: String? = nil
) {
if actual != nil {
defer { print("expected nil: \(actual!)") }
fail(message)
}
}
func expectNotNil<T>(
_ actual: T?,
_ message: String? = nil
) {
if actual == nil {
defer { print("expected non-nil") }
fail(message)
}
}
func expectTrue(
_ actual: Bool,
_ message: String? = nil
) {
if !actual { fail(message) }
}
func expectFalse(
_ actual: Bool,
_ message: String? = nil
) {
if actual { fail(message) }
}
func fail(_ reason: String? = nil) {
XCTAssert(false, failureMessage(reason), file: file, line: line)
if forceFatalFailures {
fatalError(reason ?? "<no reason>")
}
}
}
internal struct MockTestCase: TestCase {
var file: StaticString
var line: UInt
var expected: Trace.Entry
var interruptBehavior: InterruptBehavior
var interruptable: Bool { return interruptBehavior == .interruptable }
internal enum InterruptBehavior {
// Retry the syscall on EINTR
case interruptable
// Cannot return EINTR
case noInterrupt
// Cannot error at all
case noError
}
var body: (_ retryOnInterrupt: Bool) throws -> ()
init(
_ file: StaticString = #file,
_ line: UInt = #line,
name: String,
_ interruptable: InterruptBehavior,
_ args: AnyHashable...,
body: @escaping (_ retryOnInterrupt: Bool) throws -> ()
) {
self.file = file
self.line = line
self.expected = Trace.Entry(name: name, args)
self.interruptBehavior = interruptable
self.body = body
}
func runAllTests() {
XCTAssertFalse(MockingDriver.enabled)
MockingDriver.withMockingEnabled { mocking in
// Make sure we completely match the trace queue
self.expectTrue(mocking.trace.isEmpty)
defer { self.expectTrue(mocking.trace.isEmpty) }
// Test our API mappings to the lower-level syscall invocation
do {
try body(true)
self.expectEqual(self.expected, mocking.trace.dequeue())
} catch {
self.fail()
}
// Non-error-ing syscalls shouldn't ever throw
guard interruptBehavior != .noError else {
do {
try body(interruptable)
self.expectEqual(self.expected, mocking.trace.dequeue())
try body(!interruptable)
self.expectEqual(self.expected, mocking.trace.dequeue())
} catch {
self.fail()
}
return
}
// Test interupt behavior. Interruptable calls will be told not to
// retry to catch the EINTR. Non-interruptable calls will be told to
// retry, to make sure they don't spin (e.g. if API changes to include
// interruptable)
do {
mocking.forceErrno = .always(errno: EINTR)
try body(!interruptable)
self.fail()
} catch Errno.interrupted {
// Success!
self.expectEqual(self.expected, mocking.trace.dequeue())
} catch {
self.fail()
}
// Force a limited number of EINTRs, and make sure interruptable functions
// retry that number of times. Non-interruptable functions should throw it.
do {
mocking.forceErrno = .counted(errno: EINTR, count: 3)
try body(interruptable)
self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR
self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR
self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR
self.expectEqual(self.expected, mocking.trace.dequeue()) // Success
} catch Errno.interrupted {
self.expectFalse(interruptable)
self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR
} catch {
self.fail()
}
}
}
}
internal func withWindowsPaths(enabled: Bool, _ body: () -> ()) {
_withWindowsPaths(enabled: enabled, body)
}
|