-
Notifications
You must be signed in to change notification settings - Fork 1
/
testcase.jl
126 lines (109 loc) · 3.35 KB
/
testcase.jl
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
"""
for_choices(prefix; rng=Random.default_rng())
Create a `TestCase` for a given set of known choices.
"""
function for_choices(prefix::Vector{UInt64}, rng::Random.AbstractRNG, generation::UInt, max_generation::Int)
return TestCase(
prefix,
rng,
convert(UInt, length(prefix)),
nothing,
Attempt(UInt64[],
generation,
max_generation,
Pair{AbstractString,Any}[])
)
end
for_choices(prefix::Vector{UInt64}; rng::Random.AbstractRNG=Random.default_rng(), generation=1, max_generation=-1) =
for_choices(prefix, rng, convert(UInt, generation), max_generation)
for_choices(attempt::Attempt, rng::Random.AbstractRNG) = for_choices(attempt.choices, rng, attempt.generation, attempt.max_generation)
"""
forced_choice(tc::TestCase, n::UInt64)
Insert a definite choice in the choice sequence.
Note that all integrity checks happen here!
"""
function forced_choice!(tc::TestCase, n::UInt64)
if length(tc.attempt.choices) >= tc.max_size
throw(Overrun())
else
push!(tc.attempt.choices, n)
return n
end
end
"""
weighted(tc::TestCase, p::Float64)
Return `true` with probability `p`, `false` otherwise.
"""
function weighted!(tc::TestCase, p::Float64)
if length(tc.attempt.choices) < length(tc.prefix)
preordained = tc.prefix[length(tc.attempt.choices)+1]
if preordained > 1
throw(Invalid())
else
isone(forced_choice!(tc, preordained))
end
else
# the interval drawn from here is [0, 1.0)
result = rand(tc.rng) < p
forced_choice!(tc, UInt64(result))
result
end
end
"""
choice!(tc::TestCase, n)
Force a number of choices to occur, taking from the existing prefix first.
If the prefix is exhausted, draw from `[zero(n), n]` instead.
"""
function choice!(tc::TestCase, n::UInt)
if length(tc.attempt.choices) < length(tc.prefix)
preordained = tc.prefix[length(tc.attempt.choices)+1]
if preordained > n
throw(Invalid())
else
forced_choice!(tc, preordained)
end
else
result = rand(tc.rng, zero(n):n)
forced_choice!(tc, UInt(result))
end
end
function choice!(tc::TestCase, n::Int)
n >= 0 || throw(ArgumentError("Can't make a negative number of choices!"))
choice!(tc, n % UInt) % Int
end
function choice!(tc::TestCase, values::AbstractVector)
n = length(values)
forced_i = choice!(tc, n - 1) + 1
values[forced_i]
end
"""
reject(::TestCase)
Mark this test case as invalid.
"""
function reject(::TestCase)
throw(Invalid())
end
"""
assume!(::TestCase, precondition::Bool)
Reject this `TestCase` if `precondition` is `false`.
"""
function assume!(::TestCase, precondition::Bool)
if !precondition
throw(Invalid())
else
nothing
end
end
"""
target!(tc::TestCase, score::Float64)
Update `tc` to use `score` as the score this `TestCase` achieves during optimization.
!!! warning "Multiple Updates"
This score can only be set once! Repeated calls will be ignored.
"""
function target!(tc::TestCase, score::Float64)
if !isnothing(tc.targeting_score)
@warn "`target!` called twice on test case object. Overwriting score." OldScore=@something(tc.targeting_score) NewScore=score
end
tc.targeting_score = Some(score)
score
end