Test_Options.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // Copyright (c) 2014 - 2024 Apple Inc. and the project authors
  2. // Licensed under Apache License v2.0 with Runtime Library Exception
  3. //
  4. // See LICENSE.txt for license information:
  5. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  6. //
  7. // -----------------------------------------------------------------------------
  8. import Foundation
  9. import FuzzCommon
  10. import XCTest
  11. struct TestOptions: SupportsFuzzOptions {
  12. var bool1: Bool = false {
  13. didSet { sets.append("bool1:\(bool1)") }
  14. }
  15. var bool2: Bool = false {
  16. didSet { sets.append("bool2:\(bool2)") }
  17. }
  18. var int1: Int = 100 {
  19. didSet { sets.append("int1:\(int1)") }
  20. }
  21. var int2: Int = 1 {
  22. didSet { sets.append("int2:\(int2)") }
  23. }
  24. var sets: [String] = []
  25. static var fuzzOptionsList: [FuzzCommon.FuzzOption<Self>] = [
  26. .boolean(\.bool1),
  27. .boolean(\.bool2),
  28. .byte(\.int1),
  29. .byte(\.int2, mod: 16),
  30. ]
  31. init() {}
  32. }
  33. struct TestOptionsLarge: SupportsFuzzOptions {
  34. var bool1: Bool = false {
  35. didSet { sets.append("bool1:\(bool1)") }
  36. }
  37. var bool2: Bool = false {
  38. didSet { sets.append("bool2:\(bool2)") }
  39. }
  40. var bool3: Bool = false {
  41. didSet { sets.append("bool3:\(bool3)") }
  42. }
  43. var bool4: Bool = false {
  44. didSet { sets.append("bool4:\(bool4)") }
  45. }
  46. var int1: Int = 100 {
  47. didSet { sets.append("int1:\(int1)") }
  48. }
  49. var bool5: Bool = false {
  50. didSet { sets.append("bool5:\(bool5)") }
  51. }
  52. var bool6: Bool = false {
  53. didSet { sets.append("bool6:\(bool6)") }
  54. }
  55. var bool7: Bool = false {
  56. didSet { sets.append("bool7:\(bool7)") }
  57. }
  58. var bool8: Bool = false {
  59. didSet { sets.append("bool8:\(bool8)") }
  60. }
  61. var int2: Int = 1 {
  62. didSet { sets.append("int2:\(int2)") }
  63. }
  64. var sets: [String] = []
  65. static var fuzzOptionsList: [FuzzCommon.FuzzOption<Self>] = [
  66. .boolean(\.bool1),
  67. .boolean(\.bool2),
  68. .boolean(\.bool3),
  69. .boolean(\.bool4),
  70. .byte(\.int1),
  71. .boolean(\.bool5),
  72. .boolean(\.bool6),
  73. .boolean(\.bool7),
  74. .boolean(\.bool8),
  75. .byte(\.int2),
  76. ]
  77. init() {}
  78. }
  79. final class Test_FuzzOptions: XCTestCase {
  80. func testOptionBasics_noOptionsSignal() throws {
  81. // Claim no bytes passed.
  82. let bytes: [UInt8] = []
  83. XCTAssertEqual(bytes.count, 0)
  84. try bytes.withUnsafeBytes { ptr in
  85. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes.count)
  86. let (opts, bytes) = try XCTUnwrap(result)
  87. XCTAssertEqual(opts.sets, [])
  88. XCTAssertEqual(bytes.count, 0)
  89. }
  90. // Try with no leading zero, so no options.
  91. for x: UInt8 in 1...UInt8.max {
  92. let bytes: [UInt8] = [x]
  93. XCTAssertEqual(bytes.count, 1)
  94. try bytes.withUnsafeBytes { ptr in
  95. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes.count)
  96. let (opts, bytes) = try XCTUnwrap(result)
  97. XCTAssertEqual(opts.sets, [])
  98. // The buffer comes through.
  99. XCTAssertEqual(bytes.count, 1)
  100. XCTAssertEqual(bytes.baseAddress, ptr.baseAddress)
  101. }
  102. }
  103. }
  104. func testOptionBasics_optionsSignalNoBytes() throws {
  105. let bytes: [UInt8] = [0] // Options signal, then nothing
  106. XCTAssertEqual(bytes.count, 1)
  107. try bytes.withUnsafeBytes { ptr in
  108. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes.count)
  109. let (opts, bytes) = try XCTUnwrap(result)
  110. XCTAssertEqual(opts.sets, [])
  111. // Since no following bytes, the buffer comes through.
  112. XCTAssertEqual(bytes.count, 1)
  113. XCTAssertEqual(bytes.baseAddress, ptr.baseAddress)
  114. }
  115. }
  116. func testOptionBasics_bool() throws {
  117. let testCases: [(byte: UInt8, b1: Bool, b2: Bool, sets: [String])] = [
  118. (0x0, false, false, ["bool1:false", "bool2:false"]),
  119. (0x1, true, false, ["bool1:true", "bool2:false"]),
  120. (0x2, false, true, ["bool1:false", "bool2:true"]),
  121. (0x3, true, true, ["bool1:true", "bool2:true"]),
  122. ]
  123. for test in testCases {
  124. let bytes: [UInt8] = [0, test.byte]
  125. XCTAssertEqual(bytes.count, 2)
  126. try bytes.withUnsafeBytes { ptr in
  127. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes.count)
  128. let (opts, bytes) = try XCTUnwrap(result)
  129. XCTAssertEqual(opts.sets, test.sets)
  130. XCTAssertEqual(bytes.count, 0) // No bytes, the one was the options
  131. XCTAssertNotEqual(bytes.baseAddress, ptr.baseAddress)
  132. XCTAssertEqual(opts.bool1, test.b1)
  133. XCTAssertEqual(opts.bool2, test.b2)
  134. }
  135. }
  136. }
  137. func testOptionBasics_byte() throws {
  138. let testCases: [(bytes: [UInt8], i1: Int, i2: Int, sets: [String])] = [
  139. ([0x0], 100, 1, []),
  140. ([0x4, 2], 2, 1, ["int1:2"]),
  141. ([0x8, 7], 100, 7, ["int2:7"]),
  142. ([0xC, 3, 20], 3, 4, ["int1:3", "int2:4"]), // int2 has a mod applied
  143. ]
  144. for test in testCases {
  145. let bytes: [UInt8] = [0] + test.bytes
  146. try bytes.withUnsafeBytes { ptr in
  147. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes.count)
  148. let (opts, bytes) = try XCTUnwrap(result)
  149. XCTAssertEqual(opts.sets, ["bool1:false", "bool2:false"] + test.sets)
  150. XCTAssertEqual(bytes.count, 0) // No bytes, the one was the options
  151. XCTAssertNotEqual(bytes.baseAddress, ptr.baseAddress)
  152. XCTAssertEqual(opts.int1, test.i1)
  153. XCTAssertEqual(opts.int2, test.i2)
  154. }
  155. }
  156. }
  157. func testOptionBasics_byteMissingData() {
  158. let testCases: [[UInt8]] = [
  159. [0x4], // int1, no data
  160. [0x8], // int2, no data
  161. [0xC], // int1 & int2, no data
  162. [0xC, 20], // int1 & int2, data for only int1
  163. ]
  164. for test in testCases {
  165. let bytes: [UInt8] = [0] + test
  166. bytes.withUnsafeBytes { ptr in
  167. XCTAssertNil(TestOptions.extractOptions(ptr.baseAddress!, bytes.count))
  168. }
  169. }
  170. }
  171. func testOptionBasics_tailingZeros() {
  172. // Try every value that will have at least one bit set above the valid ones
  173. // to ensure it causing parsing failure.
  174. for x: UInt8 in 0x10...UInt8.max {
  175. let bytes: [UInt8] = [0, x]
  176. bytes.withUnsafeBytes { ptr in
  177. XCTAssertNil(TestOptions.extractOptions(ptr.baseAddress!, bytes.count))
  178. }
  179. }
  180. }
  181. func testOptionBasics_tailingMoreThan7_tailingZeros() {
  182. // For the first byte of optionBits, just signal that there is a second, but
  183. // then set all the expected zero bits to ensure it fails.
  184. for x: UInt8 in 0x8...UInt8.max {
  185. let bytes: [UInt8] = [0, 0x80, x]
  186. bytes.withUnsafeBytes { ptr in
  187. XCTAssertNil(TestOptions.extractOptions(ptr.baseAddress!, bytes.count))
  188. }
  189. }
  190. }
  191. func testOptionBasics_bytesAfterOptsComeThrough() throws {
  192. let bytes: [UInt8] = [0, 0, 1, 2, 3]
  193. XCTAssertEqual(bytes.count, 5)
  194. try bytes.withUnsafeBytes { ptr in
  195. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes.count)
  196. let (opts, bytes) = try XCTUnwrap(result)
  197. XCTAssertEqual(opts.sets, ["bool1:false", "bool2:false"])
  198. XCTAssertEqual(bytes.count, 3)
  199. XCTAssertNotEqual(bytes.baseAddress, ptr.baseAddress)
  200. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 0, as: UInt8.self), 1)
  201. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 1, as: UInt8.self), 2)
  202. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 2, as: UInt8.self), 3)
  203. }
  204. // Make sure data is right after a bytes value also
  205. let bytes2: [UInt8] = [0, 0x4, 20, 4, 15, 26]
  206. try bytes2.withUnsafeBytes { ptr in
  207. let result = TestOptions.extractOptions(ptr.baseAddress!, bytes2.count)
  208. let (opts, bytes) = try XCTUnwrap(result)
  209. XCTAssertEqual(opts.sets, ["bool1:false", "bool2:false", "int1:20"])
  210. XCTAssertEqual(bytes.count, 3)
  211. XCTAssertNotEqual(bytes.baseAddress, ptr.baseAddress)
  212. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 0, as: UInt8.self), 4)
  213. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 1, as: UInt8.self), 15)
  214. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 2, as: UInt8.self), 26)
  215. }
  216. // Options that can spill to two bytes for the optionBits.
  217. // Only one byte of optionsBits
  218. let bytes3: [UInt8] = [0, 0, 1, 2, 3]
  219. XCTAssertEqual(bytes3.count, 5)
  220. try bytes3.withUnsafeBytes { ptr in
  221. let result = TestOptionsLarge.extractOptions(ptr.baseAddress!, bytes3.count)
  222. let (opts, bytes) = try XCTUnwrap(result)
  223. XCTAssertEqual(
  224. opts.sets,
  225. ["bool1:false", "bool2:false", "bool3:false", "bool4:false", "bool5:false", "bool6:false"]
  226. )
  227. XCTAssertEqual(bytes.count, 3)
  228. XCTAssertNotEqual(bytes.baseAddress, ptr.baseAddress)
  229. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 0, as: UInt8.self), 1)
  230. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 1, as: UInt8.self), 2)
  231. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 2, as: UInt8.self), 3)
  232. }
  233. // Two bytes of optionsBits with a `byte` value
  234. let bytes4: [UInt8] = [0, 0x90, 123, 0x4, 20, 81, 92, 103]
  235. XCTAssertEqual(bytes4.count, 8)
  236. try bytes4.withUnsafeBytes { ptr in
  237. let result = TestOptionsLarge.extractOptions(ptr.baseAddress!, bytes4.count)
  238. let (opts, bytes) = try XCTUnwrap(result)
  239. XCTAssertEqual(
  240. opts.sets,
  241. [
  242. "bool1:false", "bool2:false", "bool3:false", "bool4:false", "int1:123", "bool5:false",
  243. "bool6:false", "bool7:false", "bool8:false", "int2:20",
  244. ]
  245. )
  246. XCTAssertEqual(bytes.count, 3)
  247. XCTAssertNotEqual(bytes.baseAddress, ptr.baseAddress)
  248. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 0, as: UInt8.self), 81)
  249. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 1, as: UInt8.self), 92)
  250. XCTAssertEqual(bytes.loadUnaligned(fromByteOffset: 2, as: UInt8.self), 103)
  251. }
  252. }
  253. }