hypothesis-specs 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/Cargo.toml +11 -0
- data/LICENSE.txt +8 -0
- data/README.markdown +86 -0
- data/Rakefile +145 -0
- data/ext/Makefile +7 -0
- data/ext/extconf.rb +5 -0
- data/lib/hypothesis.rb +223 -0
- data/lib/hypothesis/engine.rb +85 -0
- data/lib/hypothesis/errors.rb +28 -0
- data/lib/hypothesis/possible.rb +369 -0
- data/lib/hypothesis/testcase.rb +44 -0
- data/lib/hypothesis/world.rb +9 -0
- data/src/data.rs +99 -0
- data/src/distributions.rs +238 -0
- data/src/engine.rs +400 -0
- data/src/lib.rs +170 -0
- metadata +91 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hypothesis
|
4
|
+
# A TestCase class provides a concrete representation of
|
5
|
+
# an executing test case. You do not normally need to use this
|
6
|
+
# within the body of the test, but it exists to be used as
|
7
|
+
# an argument to {Hypothesis::Possibilities::built_as}.
|
8
|
+
# @!visibility private
|
9
|
+
class TestCase
|
10
|
+
# @!visibility private
|
11
|
+
attr_reader :draws, :print_log, :print_draws, :wrapped_data
|
12
|
+
|
13
|
+
# @!visibility private
|
14
|
+
def initialize(wrapped_data, print_draws: false, record_draws: false)
|
15
|
+
@wrapped_data = wrapped_data
|
16
|
+
|
17
|
+
@draws = [] if record_draws
|
18
|
+
@print_log = [] if print_draws
|
19
|
+
@depth = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def assume(condition)
|
23
|
+
raise UnsatisfiedAssumption unless condition
|
24
|
+
end
|
25
|
+
|
26
|
+
# @!visibility private
|
27
|
+
def any(possible = nil, name: nil, &block)
|
28
|
+
top_level = @depth.zero?
|
29
|
+
|
30
|
+
begin
|
31
|
+
@depth += 1
|
32
|
+
possible ||= block
|
33
|
+
result = possible.provide(&block)
|
34
|
+
if top_level
|
35
|
+
draws&.push(result)
|
36
|
+
print_log&.push([name, result.inspect])
|
37
|
+
end
|
38
|
+
result
|
39
|
+
ensure
|
40
|
+
@depth -= 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/src/data.rs
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
// Module representing core data types that Hypothesis
|
2
|
+
// needs.
|
3
|
+
|
4
|
+
use rand::{ChaChaRng, Rng};
|
5
|
+
|
6
|
+
pub type DataStream = Vec<u64>;
|
7
|
+
|
8
|
+
#[derive(Debug, Clone)]
|
9
|
+
pub struct FailedDraw;
|
10
|
+
|
11
|
+
#[derive(Debug, Clone)]
|
12
|
+
enum BitGenerator {
|
13
|
+
Random(ChaChaRng),
|
14
|
+
Recorded(DataStream),
|
15
|
+
}
|
16
|
+
|
17
|
+
// Main entry point for running a test:
|
18
|
+
// A test function takes a DataSource, uses it to
|
19
|
+
// produce some data, and the DataSource records the
|
20
|
+
// relevant information about what they did.
|
21
|
+
#[derive(Debug, Clone)]
|
22
|
+
pub struct DataSource {
|
23
|
+
bitgenerator: BitGenerator,
|
24
|
+
record: DataStream,
|
25
|
+
}
|
26
|
+
|
27
|
+
impl DataSource {
|
28
|
+
pub fn bits(&mut self, n_bits: u64) -> Result<u64, FailedDraw> {
|
29
|
+
let mut result = match self.bitgenerator {
|
30
|
+
BitGenerator::Random(ref mut random) => random.next_u64(),
|
31
|
+
BitGenerator::Recorded(ref mut v) => if self.record.len() >= v.len() {
|
32
|
+
return Err(FailedDraw);
|
33
|
+
} else {
|
34
|
+
v[self.record.len()]
|
35
|
+
},
|
36
|
+
};
|
37
|
+
|
38
|
+
if n_bits < 64 {
|
39
|
+
let mask = (1 << n_bits) - 1;
|
40
|
+
result &= mask;
|
41
|
+
};
|
42
|
+
|
43
|
+
self.record.push(result);
|
44
|
+
|
45
|
+
return Ok(result);
|
46
|
+
}
|
47
|
+
|
48
|
+
fn new(generator: BitGenerator) -> DataSource {
|
49
|
+
return DataSource {
|
50
|
+
bitgenerator: generator,
|
51
|
+
record: DataStream::new(),
|
52
|
+
};
|
53
|
+
}
|
54
|
+
|
55
|
+
pub fn from_random(random: ChaChaRng) -> DataSource {
|
56
|
+
return DataSource::new(BitGenerator::Random(random));
|
57
|
+
}
|
58
|
+
|
59
|
+
pub fn from_vec(record: DataStream) -> DataSource {
|
60
|
+
return DataSource::new(BitGenerator::Recorded(record));
|
61
|
+
}
|
62
|
+
|
63
|
+
pub fn to_result(self, status: Status) -> TestResult {
|
64
|
+
TestResult {
|
65
|
+
record: self.record,
|
66
|
+
status: status,
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
// Status indicates the result that we got from completing
|
72
|
+
// a single test execution.
|
73
|
+
#[derive(Debug, Clone, Eq, PartialEq)]
|
74
|
+
pub enum Status {
|
75
|
+
// The test tried to read more data than we had for it.
|
76
|
+
Overflow,
|
77
|
+
|
78
|
+
// Some important precondition of the test was not
|
79
|
+
// satisfied.
|
80
|
+
Invalid,
|
81
|
+
|
82
|
+
// This test ran successfully to completion without
|
83
|
+
// anything of note happening.
|
84
|
+
Valid,
|
85
|
+
|
86
|
+
// This was an interesting test execution! (Usually this
|
87
|
+
// means failing, but for things like find it may not).
|
88
|
+
Interesting,
|
89
|
+
}
|
90
|
+
|
91
|
+
// Once a data source is finished it "decays" to a
|
92
|
+
// TestResult, that retains a trace of all the information
|
93
|
+
// we needed from the DataSource. It is these we keep around,
|
94
|
+
// not the original DataSource objects.
|
95
|
+
#[derive(Debug, Clone)]
|
96
|
+
pub struct TestResult {
|
97
|
+
pub record: DataStream,
|
98
|
+
pub status: Status,
|
99
|
+
}
|
@@ -0,0 +1,238 @@
|
|
1
|
+
use data::{DataSource, FailedDraw};
|
2
|
+
|
3
|
+
use std::collections::BinaryHeap;
|
4
|
+
use std::mem;
|
5
|
+
use std::cmp::{Ord, Ordering, PartialOrd, Reverse};
|
6
|
+
|
7
|
+
type Draw<T> = Result<T, FailedDraw>;
|
8
|
+
|
9
|
+
pub fn weighted(source: &mut DataSource, probability: f64) -> Result<bool, FailedDraw> {
|
10
|
+
// TODO: Less bit-hungry implementation.
|
11
|
+
|
12
|
+
let truthy = (probability * (u64::max_value() as f64 + 1.0)).floor() as u64;
|
13
|
+
let probe = source.bits(64)?;
|
14
|
+
return Ok(probe >= u64::max_value() - truthy + 1);
|
15
|
+
}
|
16
|
+
|
17
|
+
pub fn bounded_int(source: &mut DataSource, max: u64) -> Draw<u64> {
|
18
|
+
let bitlength = 64 - max.leading_zeros() as u64;
|
19
|
+
if bitlength == 0 {
|
20
|
+
return Ok(0);
|
21
|
+
}
|
22
|
+
loop {
|
23
|
+
let probe = source.bits(bitlength)?;
|
24
|
+
if probe <= max {
|
25
|
+
return Ok(probe);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
#[derive(Debug, Clone)]
|
31
|
+
pub struct Repeat {
|
32
|
+
min_count: u64,
|
33
|
+
max_count: u64,
|
34
|
+
p_continue: f64,
|
35
|
+
|
36
|
+
current_count: u64,
|
37
|
+
}
|
38
|
+
|
39
|
+
impl Repeat {
|
40
|
+
pub fn new(min_count: u64, max_count: u64, expected_count: f64) -> Repeat {
|
41
|
+
Repeat {
|
42
|
+
min_count: min_count,
|
43
|
+
max_count: max_count,
|
44
|
+
p_continue: 1.0 - 1.0 / (1.0 + expected_count),
|
45
|
+
current_count: 0,
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
fn draw_until(&self, source: &mut DataSource, value: bool) -> Result<(), FailedDraw> {
|
50
|
+
// Force a draw until we get the desired outcome. By having this we get much better
|
51
|
+
// shrinking when min_size or max_size are set because all decisions are represented
|
52
|
+
// somewhere in the bit stream.
|
53
|
+
loop {
|
54
|
+
let d = weighted(source, self.p_continue)?;
|
55
|
+
if d == value {
|
56
|
+
return Ok(());
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
pub fn reject(&mut self) {
|
62
|
+
assert!(self.current_count > 0);
|
63
|
+
self.current_count -= 1;
|
64
|
+
}
|
65
|
+
|
66
|
+
pub fn should_continue(&mut self, source: &mut DataSource) -> Result<bool, FailedDraw> {
|
67
|
+
if self.current_count < self.min_count {
|
68
|
+
self.draw_until(source, true)?;
|
69
|
+
self.current_count += 1;
|
70
|
+
return Ok(true);
|
71
|
+
} else if self.current_count >= self.max_count {
|
72
|
+
self.draw_until(source, false)?;
|
73
|
+
return Ok(false);
|
74
|
+
}
|
75
|
+
|
76
|
+
let result = weighted(source, self.p_continue)?;
|
77
|
+
if result {
|
78
|
+
self.current_count += 1;
|
79
|
+
} else {
|
80
|
+
}
|
81
|
+
return Ok(result);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
#[derive(Debug, Clone)]
|
86
|
+
struct SamplerEntry {
|
87
|
+
primary: usize,
|
88
|
+
alternate: usize,
|
89
|
+
use_alternate: f32,
|
90
|
+
}
|
91
|
+
|
92
|
+
impl SamplerEntry {
|
93
|
+
fn single(i: usize) -> SamplerEntry {
|
94
|
+
SamplerEntry {
|
95
|
+
primary: i,
|
96
|
+
alternate: i,
|
97
|
+
use_alternate: 0.0,
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
impl Ord for SamplerEntry {
|
103
|
+
fn cmp(&self, other: &SamplerEntry) -> Ordering {
|
104
|
+
return self.primary
|
105
|
+
.cmp(&other.primary)
|
106
|
+
.then(self.alternate.cmp(&other.alternate));
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
impl PartialOrd for SamplerEntry {
|
111
|
+
fn partial_cmp(&self, other: &SamplerEntry) -> Option<Ordering> {
|
112
|
+
return Some(self.cmp(other));
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
impl PartialEq for SamplerEntry {
|
117
|
+
fn eq(&self, other: &SamplerEntry) -> bool {
|
118
|
+
return self.cmp(other) == Ordering::Equal;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
impl Eq for SamplerEntry {}
|
123
|
+
|
124
|
+
#[derive(Debug, Clone)]
|
125
|
+
pub struct Sampler {
|
126
|
+
table: Vec<SamplerEntry>,
|
127
|
+
}
|
128
|
+
|
129
|
+
impl Sampler {
|
130
|
+
pub fn new(weights: Vec<f32>) -> Sampler {
|
131
|
+
// FIXME: The correct thing to do here is to allow this,
|
132
|
+
// return early, and make this reject the data, but we don't
|
133
|
+
// currently have the status built into our data properly...
|
134
|
+
assert!(weights.len() > 0);
|
135
|
+
|
136
|
+
let mut table = Vec::new();
|
137
|
+
|
138
|
+
let mut small = BinaryHeap::new();
|
139
|
+
let mut large = BinaryHeap::new();
|
140
|
+
|
141
|
+
let total: f32 = weights.iter().sum();
|
142
|
+
|
143
|
+
let mut scaled_probabilities = Vec::new();
|
144
|
+
|
145
|
+
let n = weights.len() as f32;
|
146
|
+
|
147
|
+
for (i, w) in weights.iter().enumerate() {
|
148
|
+
let scaled = n * w / total;
|
149
|
+
scaled_probabilities.push(scaled);
|
150
|
+
if scaled == 1.0 {
|
151
|
+
table.push(SamplerEntry::single(i))
|
152
|
+
} else if scaled > 1.0 {
|
153
|
+
large.push(Reverse(i));
|
154
|
+
} else {
|
155
|
+
assert!(scaled < 1.0);
|
156
|
+
small.push(Reverse(i));
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
while !(small.is_empty() || large.is_empty()) {
|
161
|
+
let Reverse(lo) = small.pop().unwrap();
|
162
|
+
let Reverse(hi) = large.pop().unwrap();
|
163
|
+
|
164
|
+
assert!(lo != hi);
|
165
|
+
assert!(scaled_probabilities[hi] > 1.0);
|
166
|
+
assert!(scaled_probabilities[lo] < 1.0);
|
167
|
+
scaled_probabilities[hi] = (scaled_probabilities[hi] + scaled_probabilities[lo]) - 1.0;
|
168
|
+
table.push(SamplerEntry {
|
169
|
+
primary: lo,
|
170
|
+
alternate: hi,
|
171
|
+
use_alternate: 1.0 - scaled_probabilities[lo],
|
172
|
+
});
|
173
|
+
|
174
|
+
if scaled_probabilities[hi] < 1.0 {
|
175
|
+
small.push(Reverse(hi))
|
176
|
+
} else if scaled_probabilities[hi] > 1.0 {
|
177
|
+
large.push(Reverse(hi))
|
178
|
+
} else {
|
179
|
+
table.push(SamplerEntry::single(hi))
|
180
|
+
}
|
181
|
+
}
|
182
|
+
for &Reverse(i) in small.iter() {
|
183
|
+
table.push(SamplerEntry::single(i))
|
184
|
+
}
|
185
|
+
for &Reverse(i) in large.iter() {
|
186
|
+
table.push(SamplerEntry::single(i))
|
187
|
+
}
|
188
|
+
|
189
|
+
for ref mut entry in table.iter_mut() {
|
190
|
+
if entry.alternate < entry.primary {
|
191
|
+
mem::swap(&mut entry.primary, &mut entry.alternate);
|
192
|
+
entry.use_alternate = 1.0 - entry.use_alternate;
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
table.sort();
|
197
|
+
assert!(table.len() > 0);
|
198
|
+
return Sampler { table: table };
|
199
|
+
}
|
200
|
+
|
201
|
+
pub fn sample(&self, source: &mut DataSource) -> Draw<usize> {
|
202
|
+
assert!(self.table.len() > 0);
|
203
|
+
let i = bounded_int(source, self.table.len() as u64 - 1)? as usize;
|
204
|
+
let entry = &self.table[i];
|
205
|
+
let use_alternate = weighted(source, entry.use_alternate as f64)?;
|
206
|
+
if use_alternate {
|
207
|
+
Ok(entry.alternate)
|
208
|
+
} else {
|
209
|
+
Ok(entry.primary)
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
pub fn good_bitlengths() -> Sampler {
|
215
|
+
let weights = vec!(
|
216
|
+
4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, // 1 byte
|
217
|
+
2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, // 2 bytes
|
218
|
+
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // 3 bytes
|
219
|
+
0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, // 4 bytes
|
220
|
+
0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, // 5 bytes
|
221
|
+
0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, // 6 bytes
|
222
|
+
0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, // 7 bytes
|
223
|
+
0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, // 8 bytes (last bit spare for sign)
|
224
|
+
);
|
225
|
+
assert!(weights.len() == 63);
|
226
|
+
Sampler::new(weights)
|
227
|
+
}
|
228
|
+
|
229
|
+
pub fn integer_from_bitlengths(source: &mut DataSource, bitlengths: &Sampler) -> Draw<i64> {
|
230
|
+
let bitlength = bitlengths.sample(source)? as u64 + 1;
|
231
|
+
let base = source.bits(bitlength)? as i64;
|
232
|
+
let sign = source.bits(1)?;
|
233
|
+
if sign > 0 {
|
234
|
+
Ok(-base)
|
235
|
+
} else {
|
236
|
+
Ok(base)
|
237
|
+
}
|
238
|
+
}
|
data/src/engine.rs
ADDED
@@ -0,0 +1,400 @@
|
|
1
|
+
// Core module that provides a main execution loop and
|
2
|
+
// the API that can be used to get test data from it.
|
3
|
+
|
4
|
+
use rand::{ChaChaRng, Rng, SeedableRng};
|
5
|
+
|
6
|
+
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
7
|
+
use std::thread;
|
8
|
+
use std::mem;
|
9
|
+
|
10
|
+
use data::{DataSource, DataStream, Status, TestResult};
|
11
|
+
|
12
|
+
#[derive(Debug, Clone)]
|
13
|
+
enum LoopExitReason {
|
14
|
+
Complete,
|
15
|
+
MaxExamples,
|
16
|
+
//MaxShrinks,
|
17
|
+
Shutdown,
|
18
|
+
//Error(String),
|
19
|
+
}
|
20
|
+
|
21
|
+
#[derive(Debug)]
|
22
|
+
enum LoopCommand {
|
23
|
+
RunThis(DataSource),
|
24
|
+
Finished(LoopExitReason, MainGenerationLoop),
|
25
|
+
}
|
26
|
+
|
27
|
+
#[derive(Debug)]
|
28
|
+
struct MainGenerationLoop {
|
29
|
+
receiver: Receiver<TestResult>,
|
30
|
+
sender: SyncSender<LoopCommand>,
|
31
|
+
max_examples: u64,
|
32
|
+
random: ChaChaRng,
|
33
|
+
|
34
|
+
best_example: Option<TestResult>,
|
35
|
+
|
36
|
+
valid_examples: u64,
|
37
|
+
invalid_examples: u64,
|
38
|
+
interesting_examples: u64,
|
39
|
+
}
|
40
|
+
|
41
|
+
type StepResult = Result<(), LoopExitReason>;
|
42
|
+
|
43
|
+
impl MainGenerationLoop {
|
44
|
+
fn run(mut self) {
|
45
|
+
let result = self.loop_body();
|
46
|
+
match result {
|
47
|
+
// Silent shutdown when the main thread terminates
|
48
|
+
Err(LoopExitReason::Shutdown) => (),
|
49
|
+
Err(reason) => {
|
50
|
+
// Must clone because otherwise it is borrowed.
|
51
|
+
let shutdown_sender = self.sender.clone();
|
52
|
+
shutdown_sender
|
53
|
+
.send(LoopCommand::Finished(reason, self))
|
54
|
+
.unwrap()
|
55
|
+
}
|
56
|
+
Ok(_) => panic!("BUG: Generation loop was not supposed to return normally."),
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
fn loop_body(&mut self) -> StepResult {
|
61
|
+
let interesting_example = self.generate_examples()?;
|
62
|
+
|
63
|
+
let mut shrinker = Shrinker::new(self, interesting_example, |r| {
|
64
|
+
r.status == Status::Interesting
|
65
|
+
});
|
66
|
+
|
67
|
+
shrinker.run()?;
|
68
|
+
|
69
|
+
return Err(LoopExitReason::Complete);
|
70
|
+
}
|
71
|
+
|
72
|
+
fn generate_examples(&mut self) -> Result<TestResult, LoopExitReason> {
|
73
|
+
while self.valid_examples < self.max_examples
|
74
|
+
&& self.invalid_examples < 10 * self.max_examples
|
75
|
+
{
|
76
|
+
let r = self.random.gen();
|
77
|
+
let result = self.execute(DataSource::from_random(r))?;
|
78
|
+
if result.status == Status::Interesting {
|
79
|
+
return Ok(result);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return Err(LoopExitReason::MaxExamples);
|
83
|
+
}
|
84
|
+
|
85
|
+
fn execute(&mut self, source: DataSource) -> Result<TestResult, LoopExitReason> {
|
86
|
+
let result = match self.sender.send(LoopCommand::RunThis(source)) {
|
87
|
+
Ok(_) => match self.receiver.recv() {
|
88
|
+
Ok(t) => t,
|
89
|
+
Err(_) => return Err(LoopExitReason::Shutdown),
|
90
|
+
},
|
91
|
+
Err(_) => return Err(LoopExitReason::Shutdown),
|
92
|
+
};
|
93
|
+
match result.status {
|
94
|
+
Status::Overflow => (),
|
95
|
+
Status::Invalid => self.invalid_examples += 1,
|
96
|
+
Status::Valid => self.valid_examples += 1,
|
97
|
+
Status::Interesting => {
|
98
|
+
self.best_example = Some(result.clone());
|
99
|
+
self.interesting_examples += 1;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
Ok(result)
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
struct Shrinker<'owner, Predicate> {
|
108
|
+
_predicate: Predicate,
|
109
|
+
shrink_target: TestResult,
|
110
|
+
changes: u64,
|
111
|
+
main_loop: &'owner mut MainGenerationLoop,
|
112
|
+
}
|
113
|
+
|
114
|
+
impl<'owner, Predicate> Shrinker<'owner, Predicate>
|
115
|
+
where
|
116
|
+
Predicate: Fn(&TestResult) -> bool,
|
117
|
+
{
|
118
|
+
fn new(
|
119
|
+
main_loop: &'owner mut MainGenerationLoop,
|
120
|
+
shrink_target: TestResult,
|
121
|
+
predicate: Predicate,
|
122
|
+
) -> Shrinker<'owner, Predicate> {
|
123
|
+
assert!(predicate(&shrink_target));
|
124
|
+
Shrinker {
|
125
|
+
main_loop: main_loop,
|
126
|
+
_predicate: predicate,
|
127
|
+
shrink_target: shrink_target,
|
128
|
+
changes: 0,
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
fn predicate(&mut self, result: &TestResult) -> bool {
|
133
|
+
let succeeded = (self._predicate)(result);
|
134
|
+
if succeeded {
|
135
|
+
self.changes += 1;
|
136
|
+
self.shrink_target = result.clone();
|
137
|
+
}
|
138
|
+
succeeded
|
139
|
+
}
|
140
|
+
|
141
|
+
fn run(&mut self) -> StepResult {
|
142
|
+
let mut prev = self.changes + 1;
|
143
|
+
|
144
|
+
while prev != self.changes {
|
145
|
+
prev = self.changes;
|
146
|
+
self.binary_search_blocks()?;
|
147
|
+
self.remove_intervals()?;
|
148
|
+
}
|
149
|
+
Ok(())
|
150
|
+
}
|
151
|
+
|
152
|
+
fn remove_intervals(&mut self) -> StepResult {
|
153
|
+
// TODO: Actually track the data we need to make this
|
154
|
+
// not quadratic.
|
155
|
+
let mut i = 0;
|
156
|
+
while i < self.shrink_target.record.len() {
|
157
|
+
let start_length = self.shrink_target.record.len();
|
158
|
+
|
159
|
+
let mut j = i + 1;
|
160
|
+
while j < self.shrink_target.record.len() {
|
161
|
+
assert!(j > i);
|
162
|
+
let mut attempt = self.shrink_target.record.clone();
|
163
|
+
attempt.drain(i..j);
|
164
|
+
assert!(attempt.len() + (j - i) == self.shrink_target.record.len());
|
165
|
+
let deleted = self.incorporate(&attempt)?;
|
166
|
+
if !deleted {
|
167
|
+
j += 1;
|
168
|
+
}
|
169
|
+
}
|
170
|
+
if start_length == self.shrink_target.record.len() {
|
171
|
+
i += 1;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
Ok(())
|
175
|
+
}
|
176
|
+
|
177
|
+
fn binary_search_blocks(&mut self) -> StepResult {
|
178
|
+
let mut i = 0;
|
179
|
+
|
180
|
+
let mut attempt = self.shrink_target.record.clone();
|
181
|
+
|
182
|
+
while i < self.shrink_target.record.len() {
|
183
|
+
assert!(attempt.len() >= self.shrink_target.record.len());
|
184
|
+
|
185
|
+
let mut hi = self.shrink_target.record[i];
|
186
|
+
|
187
|
+
if hi > 0 {
|
188
|
+
attempt[i] = 0;
|
189
|
+
let zeroed = self.incorporate(&attempt)?;
|
190
|
+
if !zeroed {
|
191
|
+
let mut lo = 0;
|
192
|
+
// Binary search to find the smallest value we can
|
193
|
+
// replace this with.
|
194
|
+
while lo + 1 < hi {
|
195
|
+
let mid = lo + (hi - lo) / 2;
|
196
|
+
attempt[i] = mid;
|
197
|
+
let succeeded = self.incorporate(&attempt)?;
|
198
|
+
if succeeded {
|
199
|
+
attempt = self.shrink_target.record.clone();
|
200
|
+
hi = mid;
|
201
|
+
} else {
|
202
|
+
attempt[i] = self.shrink_target.record[i];
|
203
|
+
lo = mid;
|
204
|
+
}
|
205
|
+
}
|
206
|
+
attempt[i] = hi;
|
207
|
+
} else {
|
208
|
+
attempt = self.shrink_target.record.clone();
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
i += 1;
|
213
|
+
}
|
214
|
+
|
215
|
+
Ok(())
|
216
|
+
}
|
217
|
+
|
218
|
+
fn incorporate(&mut self, buf: &DataStream) -> Result<bool, LoopExitReason> {
|
219
|
+
assert!(
|
220
|
+
buf.len() <= self.shrink_target.record.len(),
|
221
|
+
"Expected incorporate to not increase length, but buf.len() = {} \
|
222
|
+
while shrink target was {}",
|
223
|
+
buf.len(),
|
224
|
+
self.shrink_target.record.len()
|
225
|
+
);
|
226
|
+
if buf.len() == self.shrink_target.record.len() {
|
227
|
+
assert!(buf < &self.shrink_target.record);
|
228
|
+
}
|
229
|
+
if self.shrink_target.record.starts_with(buf) {
|
230
|
+
return Ok(false);
|
231
|
+
}
|
232
|
+
let result = self.main_loop.execute(DataSource::from_vec(buf.clone()))?;
|
233
|
+
return Ok(self.predicate(&result));
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
#[derive(Debug, Clone, Eq, PartialEq)]
|
238
|
+
enum EngineState {
|
239
|
+
AwaitingCompletion,
|
240
|
+
ReadyToProvide,
|
241
|
+
}
|
242
|
+
|
243
|
+
#[derive(Debug)]
|
244
|
+
pub struct Engine {
|
245
|
+
// The next response from the main loop. Once
|
246
|
+
// this is set to Some(Finished(_)) it stays that way,
|
247
|
+
// otherwise it is cleared on access.
|
248
|
+
loop_response: Option<LoopCommand>,
|
249
|
+
|
250
|
+
state: EngineState,
|
251
|
+
|
252
|
+
// Communication channels with the main testing loop
|
253
|
+
handle: Option<thread::JoinHandle<()>>,
|
254
|
+
receiver: Receiver<LoopCommand>,
|
255
|
+
sender: SyncSender<TestResult>,
|
256
|
+
}
|
257
|
+
|
258
|
+
impl Clone for Engine {
|
259
|
+
fn clone(&self) -> Engine {
|
260
|
+
panic!("BUG: The Engine was unexpectedly cloned");
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
impl Engine {
|
265
|
+
pub fn new(max_examples: u64, seed: &[u32]) -> Engine {
|
266
|
+
let (send_local, recv_remote) = sync_channel(1);
|
267
|
+
let (send_remote, recv_local) = sync_channel(1);
|
268
|
+
|
269
|
+
let main_loop = MainGenerationLoop {
|
270
|
+
max_examples: max_examples,
|
271
|
+
random: ChaChaRng::from_seed(seed),
|
272
|
+
sender: send_remote,
|
273
|
+
receiver: recv_remote,
|
274
|
+
best_example: None,
|
275
|
+
valid_examples: 0,
|
276
|
+
invalid_examples: 0,
|
277
|
+
interesting_examples: 0,
|
278
|
+
};
|
279
|
+
|
280
|
+
let handle = thread::Builder::new()
|
281
|
+
.name("Hypothesis main loop".to_string())
|
282
|
+
.spawn(move || {
|
283
|
+
main_loop.run();
|
284
|
+
})
|
285
|
+
.unwrap();
|
286
|
+
|
287
|
+
Engine {
|
288
|
+
loop_response: None,
|
289
|
+
sender: send_local,
|
290
|
+
receiver: recv_local,
|
291
|
+
handle: Some(handle),
|
292
|
+
state: EngineState::ReadyToProvide,
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
pub fn mark_finished(&mut self, source: DataSource, status: Status) -> () {
|
297
|
+
self.consume_test_result(source.to_result(status))
|
298
|
+
}
|
299
|
+
|
300
|
+
pub fn next_source(&mut self) -> Option<DataSource> {
|
301
|
+
assert!(self.state == EngineState::ReadyToProvide);
|
302
|
+
self.state = EngineState::AwaitingCompletion;
|
303
|
+
|
304
|
+
self.await_loop_response();
|
305
|
+
|
306
|
+
let mut local_result = None;
|
307
|
+
mem::swap(&mut local_result, &mut self.loop_response);
|
308
|
+
|
309
|
+
match local_result {
|
310
|
+
Some(LoopCommand::RunThis(source)) => return Some(source),
|
311
|
+
None => panic!("BUG: Loop response should not be empty at this point"),
|
312
|
+
_ => {
|
313
|
+
self.loop_response = local_result;
|
314
|
+
return None;
|
315
|
+
}
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
pub fn best_source(&self) -> Option<DataSource> {
|
320
|
+
match &self.loop_response {
|
321
|
+
&Some(LoopCommand::Finished(
|
322
|
+
_,
|
323
|
+
MainGenerationLoop {
|
324
|
+
best_example: Some(ref result),
|
325
|
+
..
|
326
|
+
},
|
327
|
+
)) => Some(DataSource::from_vec(result.record.clone())),
|
328
|
+
_ => None,
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
fn consume_test_result(&mut self, result: TestResult) -> () {
|
333
|
+
assert!(self.state == EngineState::AwaitingCompletion);
|
334
|
+
self.state = EngineState::ReadyToProvide;
|
335
|
+
|
336
|
+
if self.has_shutdown() {
|
337
|
+
return ();
|
338
|
+
}
|
339
|
+
|
340
|
+
// NB: Deliberately not matching on result. If this fails,
|
341
|
+
// that's OK - it means the loop has shut down and when we ask
|
342
|
+
// for data from it we'll get its shutdown response.
|
343
|
+
let _ = self.sender.send(result);
|
344
|
+
}
|
345
|
+
|
346
|
+
pub fn was_unsatisfiable(&self) -> bool {
|
347
|
+
match &self.loop_response {
|
348
|
+
&Some(LoopCommand::Finished(_, ref main_loop)) => {
|
349
|
+
main_loop.interesting_examples == 0 && main_loop.valid_examples == 0
|
350
|
+
}
|
351
|
+
_ => false,
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
fn has_shutdown(&mut self) -> bool {
|
356
|
+
match &self.loop_response {
|
357
|
+
&Some(LoopCommand::Finished(..)) => true,
|
358
|
+
_ => false,
|
359
|
+
}
|
360
|
+
}
|
361
|
+
|
362
|
+
fn await_thread_termination(&mut self) {
|
363
|
+
let mut maybe_handle = None;
|
364
|
+
mem::swap(&mut self.handle, &mut maybe_handle);
|
365
|
+
if let Some(handle) = maybe_handle {
|
366
|
+
if let Err(boxed_msg) = handle.join() {
|
367
|
+
// FIXME: This is awful but as far as I can tell this is
|
368
|
+
// genuinely the only way to get the actual message out of the
|
369
|
+
// panic in the child thread! It's boxed as an Any, and the
|
370
|
+
// debug of Any just says "Any". Fortunately the main loop is
|
371
|
+
// very much under our control so this doesn't matter too much
|
372
|
+
// here, but yuck!
|
373
|
+
if let Some(msg) = boxed_msg.downcast_ref::<&str>() {
|
374
|
+
panic!(msg.to_string());
|
375
|
+
} else if let Some(msg) = boxed_msg.downcast_ref::<String>() {
|
376
|
+
panic!(msg.clone());
|
377
|
+
} else {
|
378
|
+
panic!("BUG: Unexpected panic format in main loop");
|
379
|
+
}
|
380
|
+
}
|
381
|
+
}
|
382
|
+
}
|
383
|
+
|
384
|
+
fn await_loop_response(&mut self) -> () {
|
385
|
+
if self.loop_response.is_none() {
|
386
|
+
match self.receiver.recv() {
|
387
|
+
Ok(response) => {
|
388
|
+
self.loop_response = Some(response);
|
389
|
+
if self.has_shutdown() {
|
390
|
+
self.await_thread_termination();
|
391
|
+
}
|
392
|
+
}
|
393
|
+
Err(_) => {
|
394
|
+
self.await_thread_termination();
|
395
|
+
panic!("BUG: Unexpected silent termination of generation loop.")
|
396
|
+
}
|
397
|
+
}
|
398
|
+
}
|
399
|
+
}
|
400
|
+
}
|