Mocking.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2020 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. // Syscall mocking support.
  8. //
  9. // NOTE: This is currently the bare minimum needed for System's testing purposes, though we do
  10. // eventually want to expose some solution to users.
  11. //
  12. // Mocking is contextual, accessible through MockingDriver.withMockingEnabled. Mocking
  13. // state, including whether it is enabled, is stored in thread-local storage. Mocking is only
  14. // enabled in testing builds of System currently, to minimize runtime overhead of release builds.
  15. //
  16. #if ENABLE_MOCKING
  17. internal struct Trace {
  18. internal struct Entry {
  19. internal var name: String
  20. internal var arguments: [AnyHashable]
  21. internal init(name: String, _ arguments: [AnyHashable]) {
  22. self.name = name
  23. self.arguments = arguments
  24. }
  25. }
  26. private var entries: [Entry] = []
  27. private var firstEntry: Int = 0
  28. internal var isEmpty: Bool { firstEntry >= entries.count }
  29. internal mutating func dequeue() -> Entry? {
  30. guard !self.isEmpty else { return nil }
  31. defer { firstEntry += 1 }
  32. return entries[firstEntry]
  33. }
  34. fileprivate mutating func add(_ e: Entry) {
  35. entries.append(e)
  36. }
  37. }
  38. internal enum ForceErrno: Equatable {
  39. case none
  40. case always(errno: CInt)
  41. case counted(errno: CInt, count: Int)
  42. }
  43. // Provide access to the driver, context, and trace stack of mocking
  44. internal class MockingDriver {
  45. // Record syscalls and their arguments
  46. internal var trace = Trace()
  47. // Mock errors inside syscalls
  48. internal var forceErrno = ForceErrno.none
  49. // Whether we should pretend to be Windows for syntactic operations
  50. // inside FilePath
  51. fileprivate var forceWindowsSyntaxForPaths: Bool? = nil
  52. }
  53. private let driverKey: _PlatformTLSKey = { makeTLSKey() }()
  54. internal var currentMockingDriver: MockingDriver? {
  55. #if !ENABLE_MOCKING
  56. fatalError("Contextual mocking in non-mocking build")
  57. #endif
  58. guard let rawPtr = getTLS(driverKey) else { return nil }
  59. return Unmanaged<MockingDriver>.fromOpaque(rawPtr).takeUnretainedValue()
  60. }
  61. extension MockingDriver {
  62. /// Enables mocking for the duration of `f` with a clean trace queue
  63. /// Restores prior mocking status and trace queue after execution
  64. internal static func withMockingEnabled(
  65. _ f: (MockingDriver) throws -> ()
  66. ) rethrows {
  67. let priorMocking = currentMockingDriver
  68. let driver = MockingDriver()
  69. defer {
  70. if let object = priorMocking {
  71. setTLS(driverKey, Unmanaged.passUnretained(object).toOpaque())
  72. } else {
  73. setTLS(driverKey, nil)
  74. }
  75. _fixLifetime(driver)
  76. }
  77. setTLS(driverKey, Unmanaged.passUnretained(driver).toOpaque())
  78. return try f(driver)
  79. }
  80. }
  81. // Check TLS for mocking
  82. @inline(never)
  83. private var contextualMockingEnabled: Bool {
  84. return currentMockingDriver != nil
  85. }
  86. extension MockingDriver {
  87. internal static var enabled: Bool { mockingEnabled }
  88. internal static var forceWindowsPaths: Bool? {
  89. currentMockingDriver?.forceWindowsSyntaxForPaths
  90. }
  91. }
  92. #endif // ENABLE_MOCKING
  93. @inline(__always)
  94. internal var mockingEnabled: Bool {
  95. // Fast constant-foldable check for release builds
  96. #if ENABLE_MOCKING
  97. return contextualMockingEnabled
  98. #else
  99. return false
  100. #endif
  101. }
  102. @inline(__always)
  103. internal var forceWindowsPaths: Bool? {
  104. #if !ENABLE_MOCKING
  105. return nil
  106. #else
  107. return MockingDriver.forceWindowsPaths
  108. #endif
  109. }
  110. #if ENABLE_MOCKING
  111. // Strip the mock_system prefix and the arg list suffix
  112. private func originalSyscallName(_ function: String) -> String {
  113. // `function` must be of format `system_<name>(<parameters>)`
  114. precondition(function.starts(with: "system_"))
  115. return String(function.dropFirst("system_".count).prefix { $0 != "(" })
  116. }
  117. private func mockImpl(
  118. name: String,
  119. path: UnsafePointer<CInterop.PlatformChar>?,
  120. _ args: [AnyHashable]
  121. ) -> CInt {
  122. precondition(mockingEnabled)
  123. let origName = originalSyscallName(name)
  124. guard let driver = currentMockingDriver else {
  125. fatalError("Mocking requested from non-mocking context")
  126. }
  127. var mockArgs: Array<AnyHashable> = []
  128. if let p = path {
  129. mockArgs.append(String(_errorCorrectingPlatformString: p))
  130. }
  131. mockArgs.append(contentsOf: args)
  132. driver.trace.add(Trace.Entry(name: origName, mockArgs))
  133. switch driver.forceErrno {
  134. case .none: break
  135. case .always(let e):
  136. system_errno = e
  137. return -1
  138. case .counted(let e, let count):
  139. assert(count >= 1)
  140. system_errno = e
  141. driver.forceErrno = count > 1 ? .counted(errno: e, count: count-1) : .none
  142. return -1
  143. }
  144. return 0
  145. }
  146. internal func _mock(
  147. name: String = #function, path: UnsafePointer<CInterop.PlatformChar>? = nil, _ args: AnyHashable...
  148. ) -> CInt {
  149. return mockImpl(name: name, path: path, args)
  150. }
  151. internal func _mockInt(
  152. name: String = #function, path: UnsafePointer<CInterop.PlatformChar>? = nil, _ args: AnyHashable...
  153. ) -> Int {
  154. Int(mockImpl(name: name, path: path, args))
  155. }
  156. internal func _mockOffT(
  157. name: String = #function, path: UnsafePointer<CInterop.PlatformChar>? = nil, _ args: AnyHashable...
  158. ) -> _COffT {
  159. _COffT(mockImpl(name: name, path: path, args))
  160. }
  161. #endif // ENABLE_MOCKING
  162. // Force paths to be treated as Windows syntactically if `enabled` is
  163. // true, and as POSIX syntactically if not.
  164. internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) {
  165. #if ENABLE_MOCKING
  166. MockingDriver.withMockingEnabled { driver in
  167. driver.forceWindowsSyntaxForPaths = enabled
  168. body()
  169. }
  170. #else
  171. body()
  172. #endif
  173. }