Skip to content

Commit a1b6b4f

Browse files
committed
Fix seed being truncated
1 parent 11e7b34 commit a1b6b4f

File tree

5 files changed

+25
-35
lines changed

5 files changed

+25
-35
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ It's possible that a test only fails on very specific inputs that don't trigger
5050
PropertyBased will report which input caused the failing case, and which seed was used:
5151

5252
```
53-
Failure occured with input 998.
54-
Add `.fixedSeed("e1050d658e5e03cea3c802ed4196207a")` to the Test to reproduce this issue.
53+
Failure occured with input 992.
54+
Add `.fixedSeed("aKPPWDEafU0CGMDYHef/ETcbYUyjWQvRVP1DTNy6qJk=")` to the Test to reproduce this issue.
5555
```
5656

5757
You can supply the fixed seed to reproduce the issue every time.
5858

5959
```swift
60-
@Test(.fixedSeed("e1050d658e5e03cea3c802ed4196207a"))
60+
@Test(.fixedSeed("aKPPWDEafU0CGMDYHef/ETcbYUyjWQvRVP1DTNy6qJk="))
6161
func failsSometimes() async {
6262
await propertyCheck(input: .int(in: 0...1000)) { n in
6363
#expect(n < 990)

Sources/PropertyBased/PropertyCheck.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ import Testing
4949
/// The function will report which input caused the failing case, and which seed was used:
5050
///
5151
/// ```
52-
/// Failure occured with input 998.
53-
/// Add `.fixedSeed("e1050d658e5e03cea3c802ed4196207a")` to the Test to reproduce this issue.
52+
/// Failure occured with input 992.
53+
/// Add `.fixedSeed("aKPPWDEafU0CGMDYHef/ETcbYUyjWQvRVP1DTNy6qJk=")` to the Test to reproduce this issue.
5454
/// ```
5555
///
5656
/// You can supply the fixed seed to reproduce the issue every time.
5757
///
5858
/// ```swift
59-
/// @Test(.fixedSeed("e1050d658e5e03cea3c802ed4196207a"))
59+
/// @Test(.fixedSeed("aKPPWDEafU0CGMDYHef/ETcbYUyjWQvRVP1DTNy6qJk="))
6060
/// func failsSometimes() async {
6161
/// await propertyCheck(input: .int(in: 0...1000)) { n in
6262
/// #expect(n < 990)
@@ -93,15 +93,12 @@ public func propertyCheck<each Value>(isolation: isolated (any Actor)? = #isolat
9393
var rng = fixedRng?.rng ?? Xoshiro()
9494
var rngCopy = rng
9595

96-
rng.perturb()
97-
9896
let foundIssues = await countIssues(isolation: isolation) {
9997
try await body(repeat (each input).run(using: &rng))
10098
}
10199

102100
if foundIssues > 0 {
103101
let seed = rngCopy.currentSeed
104-
rngCopy.perturb()
105102

106103
var paramLabels: [String] = []
107104
for gen in repeat each input {

Sources/PropertyBased/Xoshiro+SeededRandomNumberGenerator.swift

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ import Gen
99
import Foundation
1010

1111
extension Xoshiro: @retroactive @unchecked Sendable {}
12-
13-
extension Xoshiro {
14-
mutating func perturb() {
15-
for _ in 0..<10 {
16-
_ = next()
17-
}
18-
}
19-
}
20-
2112
extension Xoshiro: @retroactive Hashable {
2213
public static func == (lhs: Xoshiro, rhs: Xoshiro) -> Bool {
2314
lhs.currentState == rhs.currentState
@@ -35,17 +26,21 @@ extension Xoshiro: SeededRandomNumberGenerator {
3526
public typealias Seed = String
3627

3728
public init?(seed: String) {
38-
guard seed.count == 32,
39-
let s1 = UInt64(seed.prefix(8), radix: 16),
40-
let s2 = UInt64(seed.dropFirst(8).prefix(8), radix: 16),
41-
let s3 = UInt64(seed.dropFirst(16).prefix(8), radix: 16),
42-
let s4 = UInt64(seed.dropFirst(24).prefix(8), radix: 16)
29+
guard let data = Data(base64Encoded: seed),
30+
data.count == MemoryLayout<UInt64>.size * 4
4331
else { return nil }
4432

45-
self.init(state: (s1, s2, s3, s4))
33+
let state = data.withUnsafeBytes {
34+
let pointer = $0.bindMemory(to: UInt64.self)
35+
return (pointer[0], pointer[1], pointer[2], pointer[3])
36+
}
37+
38+
self.init(state: state)
4639
}
4740

4841
public var currentSeed: String {
49-
String(format: "%.8x%.8x%.8x%.8x", currentState.0, currentState.1, currentState.2, currentState.3)
42+
let bytes: ContiguousArray = [currentState.0, currentState.1, currentState.2, currentState.3]
43+
let data = bytes.withUnsafeBufferPointer { Data(buffer: $0) }
44+
return data.base64EncodedString()
5045
}
5146
}

Tests/PropertyBasedTests/FixedSeedTests.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ import Gen
3535
}))
3636
}
3737

38-
@Test(.fixedSeed("11223344556677889900aabbccddeeff"))
38+
@Test(.fixedSeed("4tPCyvymNncnc+napVCI0T4Jc6IYw1lXOQbXlIqyHck="))
3939
func testIdenticalInputs() async throws {
4040
let issues = await gatherIssues {
4141
await propertyCheck(input: .int(in: 0...1000000)) { n in
42-
#expect(n == 960927)
42+
#expect(n == 480813)
4343
}
4444
}
4545
#expect(issues.count == 1)
@@ -49,13 +49,12 @@ import Gen
4949
}
5050

5151
@Test func testXoshiro() throws {
52-
var rng = try #require(Xoshiro(seed: "532896ad4a02a15e184a6be2ad433acd"))
52+
var rng = try #require(Xoshiro(seed: "I9kE/glCt1MIxbFsddPUSiKFAAJBGKPHSre93c+Wz9E="))
5353

54-
try #require(rng.currentSeed == "532896ad4a02a15e184a6be2ad433acd")
55-
rng.perturb()
56-
try #require(rng.next() == 4810111524902131809)
57-
try #require(rng.next() == 8977626393675964846)
58-
try #require(rng.next() == 1239627430587387523)
59-
try #require(rng.currentSeed == "68fb55a91e543b7a00c18b38f77fb1b6")
54+
try #require(rng.currentSeed == "I9kE/glCt1MIxbFsddPUSiKFAAJBGKPHSre93c+Wz9E=")
55+
try #require(rng.next() == 13012537654314612243)
56+
try #require(rng.next() == 17010070744160488460)
57+
try #require(rng.next() == 15434697293255949747)
58+
try #require(rng.currentSeed == "INXoBVnaU+VA/a0Vu3WfVCmT1mBMK0ZrXJb24K4vLVY=")
6059
}
6160
}

Tests/PropertyBasedTests/IntegerAdditionTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,4 @@ import Gen
3434
#expect(first == second)
3535
}
3636
}
37-
3837
}

0 commit comments

Comments
 (0)