File: Mocking.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 (212 lines) | stat: -rw-r--r-- 5,691 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
203
204
205
206
207
208
209
210
211
212
/*
 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
*/

// Syscall mocking support.
//
// NOTE: This is currently the bare minimum needed for System's testing purposes, though we do
// eventually want to expose some solution to users.
//
// Mocking is contextual, accessible through MockingDriver.withMockingEnabled. Mocking
// state, including whether it is enabled, is stored in thread-local storage. Mocking is only
// enabled in testing builds of System currently, to minimize runtime overhead of release builds.
//

#if ENABLE_MOCKING
internal struct Trace {
  internal struct Entry: Hashable {
    private var name: String
    private var arguments: [AnyHashable]

    internal init(name: String, _ arguments: [AnyHashable]) {
      self.name = name
      self.arguments = arguments
    }
  }

  private var entries: [Entry] = []
  private var firstEntry: Int = 0

  internal var isEmpty: Bool { firstEntry >= entries.count }

  internal mutating func dequeue() -> Entry? {
    guard !self.isEmpty else { return nil }
    defer { firstEntry += 1 }
    return entries[firstEntry]
  }

  fileprivate mutating func add(_ e: Entry) {
    entries.append(e)
  }
}

internal enum ForceErrno: Equatable {
  case none
  case always(errno: CInt)

  case counted(errno: CInt, count: Int)
}

// Provide access to the driver, context, and trace stack of mocking
internal class MockingDriver {
  // Record syscalls and their arguments
  internal var trace = Trace()

  // Mock errors inside syscalls
  internal var forceErrno = ForceErrno.none

  // Whether we should pretend to be Windows for syntactic operations
  // inside FilePath
  fileprivate var forceWindowsSyntaxForPaths = false
}

private let driverKey: _PlatformTLSKey = { makeTLSKey() }()

internal var currentMockingDriver: MockingDriver? {
  #if !ENABLE_MOCKING
    fatalError("Contextual mocking in non-mocking build")
  #endif

  guard let rawPtr = getTLS(driverKey) else { return nil }

  return Unmanaged<MockingDriver>.fromOpaque(rawPtr).takeUnretainedValue()
}

extension MockingDriver {
  /// Enables mocking for the duration of `f` with a clean trace queue
  /// Restores prior mocking status and trace queue after execution
  internal static func withMockingEnabled(
    _ f: (MockingDriver) throws -> ()
  ) rethrows {
    let priorMocking = currentMockingDriver
    let driver = MockingDriver()

    defer {
      if let object = priorMocking {
        setTLS(driverKey, Unmanaged.passUnretained(object).toOpaque())
      } else {
        setTLS(driverKey, nil)
      }
      _fixLifetime(driver)
    }

    setTLS(driverKey, Unmanaged.passUnretained(driver).toOpaque())
    return try f(driver)
  }
}

// Check TLS for mocking
@inline(never)
private var contextualMockingEnabled: Bool {
  return currentMockingDriver != nil
}

extension MockingDriver {
  internal static var enabled: Bool { mockingEnabled }

  internal static var forceWindowsPaths: Bool {
    currentMockingDriver?.forceWindowsSyntaxForPaths ?? false
  }
}

#endif // ENABLE_MOCKING

@inline(__always)
internal var mockingEnabled: Bool {
  // Fast constant-foldable check for release builds
  #if ENABLE_MOCKING
    return contextualMockingEnabled
  #else
    return false
  #endif
}

@inline(__always)
internal var forceWindowsPaths: Bool {
  #if !ENABLE_MOCKING
  return false
  #else
  return MockingDriver.forceWindowsPaths
  #endif
}


#if ENABLE_MOCKING
// Strip the mock_system prefix and the arg list suffix
private func originalSyscallName(_ function: String) -> String {
  // `function` must be of format `system_<name>(<parameters>)`
  precondition(function.starts(with: "system_"))
  return String(function.dropFirst("system_".count).prefix { $0 != "(" })
}

private func mockImpl(
  name: String,
  path: UnsafePointer<CInterop.PlatformChar>?,
  _ args: [AnyHashable]
) -> CInt {
  precondition(mockingEnabled)
  let origName = originalSyscallName(name)
  guard let driver = currentMockingDriver else {
    fatalError("Mocking requested from non-mocking context")
  }
  var mockArgs: Array<AnyHashable> = []
  if let p = path {
    mockArgs.append(String(_errorCorrectingPlatformString: p))
  }
  mockArgs.append(contentsOf: args)
  driver.trace.add(Trace.Entry(name: origName, mockArgs))

  switch driver.forceErrno {
  case .none: break
  case .always(let e):
    system_errno = e
    return -1
  case .counted(let e, let count):
    assert(count >= 1)
    system_errno = e
    driver.forceErrno = count > 1 ? .counted(errno: e, count: count-1) : .none
    return -1
  }

  return 0
}

internal func _mock(
  name: String = #function, path: UnsafePointer<CInterop.PlatformChar>? = nil, _ args: AnyHashable...
) -> CInt {
  return mockImpl(name: name, path: path, args)
}
internal func _mockInt(
  name: String = #function, path: UnsafePointer<CInterop.PlatformChar>? = nil, _ args: AnyHashable...
) -> Int {
  Int(mockImpl(name: name, path: path, args))
}

internal func _mockOffT(
  name: String = #function, path: UnsafePointer<CInterop.PlatformChar>? = nil, _ args: AnyHashable...
) -> _COffT {
  _COffT(mockImpl(name: name, path: path, args))
}
#endif // ENABLE_MOCKING

// Force paths to be treated as Windows syntactically if `enabled` is
// true.
internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) {
  #if ENABLE_MOCKING
  guard enabled else {
    body()
    return
  }
  MockingDriver.withMockingEnabled { driver in
    driver.forceWindowsSyntaxForPaths = true
    body()
  }
  #else
  body()
  #endif
}