hypothesis-specs 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|