hypothesis-specs 0.0.12 → 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Cargo.toml +1 -1
- data/Rakefile +1 -5
- data/src/lib.rs +5 -8
- metadata +1 -5
- data/src/data.rs +0 -184
- data/src/distributions.rs +0 -297
- data/src/engine.rs +0 -620
- data/src/intminimize.rs +0 -149
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70d4e8207879b336d81af1a5c591b0eb184068114617a87f75640cc729e2152f
|
4
|
+
data.tar.gz: 1cd583baf6d78a411ddc81756ceee110566b05025671cdc50fd8c210d41b444f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5811a1bf2bd16b8ca011e40cc25e007afcccd5432d082dea9d4d7fd3f042e1c835d0b42f3808bf6915fe4042fafabdbd12ed7f5f7fb4dc2f46cb5cd94e6a2e1
|
7
|
+
data.tar.gz: 7be4261c0ee96f7d22eb107dba0e9ee485ec5510bbf769841fc50e0cb9d13a0dda56ef4b100b4e27b601aaccbfba1fe07c7b35a36006029ff03950df14bc3d70
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# Hypothesis for Ruby 0.0.13 (2018-06-25)
|
2
|
+
|
3
|
+
This release moves the core Rust engine into the separate Conjecture crate. It
|
4
|
+
should have no user visible effect.
|
5
|
+
|
1
6
|
# Hypothesis for Ruby 0.0.12 (2018-06-23)
|
2
7
|
|
3
8
|
This release is the beginning of splitting out the Rust core of Hypothesis
|
data/Cargo.toml
CHANGED
data/Rakefile
CHANGED
data/src/lib.rs
CHANGED
@@ -11,17 +11,14 @@ extern crate core;
|
|
11
11
|
#[macro_use]
|
12
12
|
extern crate helix;
|
13
13
|
extern crate rand;
|
14
|
-
|
15
|
-
mod data;
|
16
|
-
mod distributions;
|
17
|
-
mod engine;
|
18
|
-
mod intminimize;
|
14
|
+
extern crate conjecture;
|
19
15
|
|
20
16
|
use std::mem;
|
21
17
|
|
22
|
-
use data::{DataSource, Status};
|
23
|
-
use distributions::Repeat;
|
24
|
-
use
|
18
|
+
use conjecture::data::{DataSource, Status};
|
19
|
+
use conjecture::distributions::Repeat;
|
20
|
+
use conjecture::distributions;
|
21
|
+
use conjecture::engine::Engine;
|
25
22
|
|
26
23
|
ruby! {
|
27
24
|
class HypothesisCoreDataSource {
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hypothesis-specs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David R. Maciver
|
@@ -67,10 +67,6 @@ files:
|
|
67
67
|
- lib/hypothesis/possible.rb
|
68
68
|
- lib/hypothesis/testcase.rb
|
69
69
|
- lib/hypothesis/world.rb
|
70
|
-
- src/data.rs
|
71
|
-
- src/distributions.rs
|
72
|
-
- src/engine.rs
|
73
|
-
- src/intminimize.rs
|
74
70
|
- src/lib.rs
|
75
71
|
homepage: https://github.com/HypothesisWorks/hypothesis/tree/master/hypothesis-ruby
|
76
72
|
licenses:
|
data/src/data.rs
DELETED
@@ -1,184 +0,0 @@
|
|
1
|
-
// Module representing core data types that Hypothesis
|
2
|
-
// needs.
|
3
|
-
|
4
|
-
use rand::{ChaChaRng, Rng};
|
5
|
-
use std::collections::HashSet;
|
6
|
-
|
7
|
-
pub type DataStream = Vec<u64>;
|
8
|
-
|
9
|
-
#[derive(Debug, Clone)]
|
10
|
-
pub struct FailedDraw;
|
11
|
-
|
12
|
-
#[derive(Debug, Clone)]
|
13
|
-
enum BitGenerator {
|
14
|
-
Random(ChaChaRng),
|
15
|
-
Recorded(DataStream),
|
16
|
-
}
|
17
|
-
|
18
|
-
// Records information corresponding to a single draw call.
|
19
|
-
#[derive(Debug, Clone)]
|
20
|
-
pub struct DrawInProgress {
|
21
|
-
depth: usize,
|
22
|
-
start: usize,
|
23
|
-
end: Option<usize>,
|
24
|
-
}
|
25
|
-
|
26
|
-
// Records information corresponding to a single draw call.
|
27
|
-
#[derive(Debug, Clone)]
|
28
|
-
pub struct Draw {
|
29
|
-
pub depth: usize,
|
30
|
-
pub start: usize,
|
31
|
-
pub end: usize,
|
32
|
-
}
|
33
|
-
|
34
|
-
// Main entry point for running a test:
|
35
|
-
// A test function takes a DataSource, uses it to
|
36
|
-
// produce some data, and the DataSource records the
|
37
|
-
// relevant information about what they did.
|
38
|
-
#[derive(Debug, Clone)]
|
39
|
-
pub struct DataSource {
|
40
|
-
bitgenerator: BitGenerator,
|
41
|
-
record: DataStream,
|
42
|
-
sizes: Vec<u64>,
|
43
|
-
draws: Vec<DrawInProgress>,
|
44
|
-
draw_stack: Vec<usize>,
|
45
|
-
written_indices: HashSet<usize>,
|
46
|
-
}
|
47
|
-
|
48
|
-
impl DataSource {
|
49
|
-
fn new(generator: BitGenerator) -> DataSource {
|
50
|
-
return DataSource {
|
51
|
-
bitgenerator: generator,
|
52
|
-
record: DataStream::new(),
|
53
|
-
sizes: Vec::new(),
|
54
|
-
draws: Vec::new(),
|
55
|
-
draw_stack: Vec::new(),
|
56
|
-
written_indices: HashSet::new(),
|
57
|
-
};
|
58
|
-
}
|
59
|
-
|
60
|
-
pub fn from_random(random: ChaChaRng) -> DataSource {
|
61
|
-
return DataSource::new(BitGenerator::Random(random));
|
62
|
-
}
|
63
|
-
|
64
|
-
pub fn from_vec(record: DataStream) -> DataSource {
|
65
|
-
return DataSource::new(BitGenerator::Recorded(record));
|
66
|
-
}
|
67
|
-
|
68
|
-
pub fn start_draw(&mut self) {
|
69
|
-
let i = self.draws.len();
|
70
|
-
let depth = self.draw_stack.len();
|
71
|
-
let start = self.record.len();
|
72
|
-
|
73
|
-
self.draw_stack.push(i);
|
74
|
-
self.draws.push(DrawInProgress {
|
75
|
-
start: start,
|
76
|
-
end: None,
|
77
|
-
depth: depth,
|
78
|
-
});
|
79
|
-
}
|
80
|
-
|
81
|
-
pub fn stop_draw(&mut self) {
|
82
|
-
assert!(self.draws.len() > 0);
|
83
|
-
assert!(self.draw_stack.len() > 0);
|
84
|
-
let i = self.draw_stack.pop().unwrap();
|
85
|
-
let end = self.record.len();
|
86
|
-
self.draws[i].end = Some(end);
|
87
|
-
}
|
88
|
-
|
89
|
-
pub fn write(&mut self, value: u64) -> Result<(), FailedDraw> {
|
90
|
-
match self.bitgenerator {
|
91
|
-
BitGenerator::Recorded(ref mut v) if self.record.len() >= v.len() => Err(FailedDraw),
|
92
|
-
_ => {
|
93
|
-
self.sizes.push(0);
|
94
|
-
self.record.push(value);
|
95
|
-
Ok(())
|
96
|
-
}
|
97
|
-
}
|
98
|
-
}
|
99
|
-
|
100
|
-
pub fn bits(&mut self, n_bits: u64) -> Result<u64, FailedDraw> {
|
101
|
-
self.sizes.push(n_bits);
|
102
|
-
let mut result = match self.bitgenerator {
|
103
|
-
BitGenerator::Random(ref mut random) => random.next_u64(),
|
104
|
-
BitGenerator::Recorded(ref mut v) => if self.record.len() >= v.len() {
|
105
|
-
return Err(FailedDraw);
|
106
|
-
} else {
|
107
|
-
v[self.record.len()]
|
108
|
-
},
|
109
|
-
};
|
110
|
-
|
111
|
-
if n_bits < 64 {
|
112
|
-
let mask = (1 << n_bits) - 1;
|
113
|
-
result &= mask;
|
114
|
-
};
|
115
|
-
|
116
|
-
self.record.push(result);
|
117
|
-
|
118
|
-
return Ok(result);
|
119
|
-
}
|
120
|
-
|
121
|
-
pub fn to_result(mut self, status: Status) -> TestResult {
|
122
|
-
TestResult {
|
123
|
-
record: self.record,
|
124
|
-
status: status,
|
125
|
-
written_indices: self.written_indices,
|
126
|
-
sizes: self.sizes,
|
127
|
-
draws: self.draws
|
128
|
-
.drain(..)
|
129
|
-
.filter_map(|d| match d {
|
130
|
-
DrawInProgress {
|
131
|
-
depth,
|
132
|
-
start,
|
133
|
-
end: Some(end),
|
134
|
-
} if start < end =>
|
135
|
-
{
|
136
|
-
Some(Draw {
|
137
|
-
start: start,
|
138
|
-
end: end,
|
139
|
-
depth: depth,
|
140
|
-
})
|
141
|
-
}
|
142
|
-
DrawInProgress { end: None, .. } => {
|
143
|
-
assert!(status == Status::Invalid || status == Status::Overflow);
|
144
|
-
None
|
145
|
-
}
|
146
|
-
_ => None,
|
147
|
-
})
|
148
|
-
.collect(),
|
149
|
-
}
|
150
|
-
}
|
151
|
-
}
|
152
|
-
|
153
|
-
// Status indicates the result that we got from completing
|
154
|
-
// a single test execution.
|
155
|
-
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
156
|
-
pub enum Status {
|
157
|
-
// The test tried to read more data than we had for it.
|
158
|
-
Overflow,
|
159
|
-
|
160
|
-
// Some important precondition of the test was not
|
161
|
-
// satisfied.
|
162
|
-
Invalid,
|
163
|
-
|
164
|
-
// This test ran successfully to completion without
|
165
|
-
// anything of note happening.
|
166
|
-
Valid,
|
167
|
-
|
168
|
-
// This was an interesting test execution! (Usually this
|
169
|
-
// means failing, but for things like find it may not).
|
170
|
-
Interesting,
|
171
|
-
}
|
172
|
-
|
173
|
-
// Once a data source is finished it "decays" to a
|
174
|
-
// TestResult, that retains a trace of all the information
|
175
|
-
// we needed from the DataSource. It is these we keep around,
|
176
|
-
// not the original DataSource objects.
|
177
|
-
#[derive(Debug, Clone)]
|
178
|
-
pub struct TestResult {
|
179
|
-
pub record: DataStream,
|
180
|
-
pub status: Status,
|
181
|
-
pub draws: Vec<Draw>,
|
182
|
-
pub sizes: Vec<u64>,
|
183
|
-
pub written_indices: HashSet<usize>,
|
184
|
-
}
|
data/src/distributions.rs
DELETED
@@ -1,297 +0,0 @@
|
|
1
|
-
use data::{DataSource, FailedDraw};
|
2
|
-
|
3
|
-
use std::cmp::{Ord, Ordering, PartialOrd, Reverse};
|
4
|
-
use std::collections::BinaryHeap;
|
5
|
-
use std::mem;
|
6
|
-
|
7
|
-
use std::u64::MAX as MAX64;
|
8
|
-
|
9
|
-
type Draw<T> = Result<T, FailedDraw>;
|
10
|
-
|
11
|
-
pub fn weighted(source: &mut DataSource, probability: f64) -> Result<bool, FailedDraw> {
|
12
|
-
// TODO: Less bit-hungry implementation.
|
13
|
-
|
14
|
-
let truthy = (probability * (u64::max_value() as f64 + 1.0)).floor() as u64;
|
15
|
-
let probe = source.bits(64)?;
|
16
|
-
Ok(match (truthy, probe) {
|
17
|
-
(0, _) => false,
|
18
|
-
(MAX64, _) => true,
|
19
|
-
(_, 0) => false,
|
20
|
-
(_, 1) => true,
|
21
|
-
_ => probe >= MAX64 - truthy,
|
22
|
-
})
|
23
|
-
}
|
24
|
-
|
25
|
-
pub fn bounded_int(source: &mut DataSource, max: u64) -> Draw<u64> {
|
26
|
-
let bitlength = 64 - max.leading_zeros() as u64;
|
27
|
-
if bitlength == 0 {
|
28
|
-
source.write(0)?;
|
29
|
-
return Ok(0);
|
30
|
-
}
|
31
|
-
loop {
|
32
|
-
let probe = source.bits(bitlength)?;
|
33
|
-
if probe <= max {
|
34
|
-
return Ok(probe);
|
35
|
-
}
|
36
|
-
}
|
37
|
-
}
|
38
|
-
|
39
|
-
#[derive(Debug, Clone)]
|
40
|
-
pub struct Repeat {
|
41
|
-
min_count: u64,
|
42
|
-
max_count: u64,
|
43
|
-
p_continue: f64,
|
44
|
-
|
45
|
-
current_count: u64,
|
46
|
-
}
|
47
|
-
|
48
|
-
impl Repeat {
|
49
|
-
pub fn new(min_count: u64, max_count: u64, expected_count: f64) -> Repeat {
|
50
|
-
Repeat {
|
51
|
-
min_count: min_count,
|
52
|
-
max_count: max_count,
|
53
|
-
p_continue: 1.0 - 1.0 / (1.0 + expected_count),
|
54
|
-
current_count: 0,
|
55
|
-
}
|
56
|
-
}
|
57
|
-
|
58
|
-
pub fn reject(&mut self) {
|
59
|
-
assert!(self.current_count > 0);
|
60
|
-
self.current_count -= 1;
|
61
|
-
}
|
62
|
-
|
63
|
-
pub fn should_continue(&mut self, source: &mut DataSource) -> Result<bool, FailedDraw> {
|
64
|
-
if self.min_count == self.max_count {
|
65
|
-
if self.current_count < self.max_count {
|
66
|
-
self.current_count += 1;
|
67
|
-
return Ok(true);
|
68
|
-
} else {
|
69
|
-
return Ok(false);
|
70
|
-
}
|
71
|
-
} else if self.current_count < self.min_count {
|
72
|
-
source.write(1)?;
|
73
|
-
self.current_count += 1;
|
74
|
-
return Ok(true);
|
75
|
-
} else if self.current_count >= self.max_count {
|
76
|
-
source.write(0)?;
|
77
|
-
return Ok(false);
|
78
|
-
}
|
79
|
-
|
80
|
-
let result = weighted(source, self.p_continue)?;
|
81
|
-
if result {
|
82
|
-
self.current_count += 1;
|
83
|
-
} else {
|
84
|
-
}
|
85
|
-
return Ok(result);
|
86
|
-
}
|
87
|
-
}
|
88
|
-
|
89
|
-
#[derive(Debug, Clone)]
|
90
|
-
struct SamplerEntry {
|
91
|
-
primary: usize,
|
92
|
-
alternate: usize,
|
93
|
-
use_alternate: f32,
|
94
|
-
}
|
95
|
-
|
96
|
-
impl SamplerEntry {
|
97
|
-
fn single(i: usize) -> SamplerEntry {
|
98
|
-
SamplerEntry {
|
99
|
-
primary: i,
|
100
|
-
alternate: i,
|
101
|
-
use_alternate: 0.0,
|
102
|
-
}
|
103
|
-
}
|
104
|
-
}
|
105
|
-
|
106
|
-
impl Ord for SamplerEntry {
|
107
|
-
fn cmp(&self, other: &SamplerEntry) -> Ordering {
|
108
|
-
return self.primary
|
109
|
-
.cmp(&other.primary)
|
110
|
-
.then(self.alternate.cmp(&other.alternate));
|
111
|
-
}
|
112
|
-
}
|
113
|
-
|
114
|
-
impl PartialOrd for SamplerEntry {
|
115
|
-
fn partial_cmp(&self, other: &SamplerEntry) -> Option<Ordering> {
|
116
|
-
return Some(self.cmp(other));
|
117
|
-
}
|
118
|
-
}
|
119
|
-
|
120
|
-
impl PartialEq for SamplerEntry {
|
121
|
-
fn eq(&self, other: &SamplerEntry) -> bool {
|
122
|
-
return self.cmp(other) == Ordering::Equal;
|
123
|
-
}
|
124
|
-
}
|
125
|
-
|
126
|
-
impl Eq for SamplerEntry {}
|
127
|
-
|
128
|
-
#[derive(Debug, Clone)]
|
129
|
-
pub struct Sampler {
|
130
|
-
table: Vec<SamplerEntry>,
|
131
|
-
}
|
132
|
-
|
133
|
-
impl Sampler {
|
134
|
-
pub fn new(weights: Vec<f32>) -> Sampler {
|
135
|
-
// FIXME: The correct thing to do here is to allow this,
|
136
|
-
// return early, and make this reject the data, but we don't
|
137
|
-
// currently have the status built into our data properly...
|
138
|
-
assert!(weights.len() > 0);
|
139
|
-
|
140
|
-
let mut table = Vec::new();
|
141
|
-
|
142
|
-
let mut small = BinaryHeap::new();
|
143
|
-
let mut large = BinaryHeap::new();
|
144
|
-
|
145
|
-
let total: f32 = weights.iter().sum();
|
146
|
-
|
147
|
-
let mut scaled_probabilities = Vec::new();
|
148
|
-
|
149
|
-
let n = weights.len() as f32;
|
150
|
-
|
151
|
-
for (i, w) in weights.iter().enumerate() {
|
152
|
-
let scaled = n * w / total;
|
153
|
-
scaled_probabilities.push(scaled);
|
154
|
-
if scaled == 1.0 {
|
155
|
-
table.push(SamplerEntry::single(i))
|
156
|
-
} else if scaled > 1.0 {
|
157
|
-
large.push(Reverse(i));
|
158
|
-
} else {
|
159
|
-
assert!(scaled < 1.0);
|
160
|
-
small.push(Reverse(i));
|
161
|
-
}
|
162
|
-
}
|
163
|
-
|
164
|
-
while !(small.is_empty() || large.is_empty()) {
|
165
|
-
let Reverse(lo) = small.pop().unwrap();
|
166
|
-
let Reverse(hi) = large.pop().unwrap();
|
167
|
-
|
168
|
-
assert!(lo != hi);
|
169
|
-
assert!(scaled_probabilities[hi] > 1.0);
|
170
|
-
assert!(scaled_probabilities[lo] < 1.0);
|
171
|
-
scaled_probabilities[hi] = (scaled_probabilities[hi] + scaled_probabilities[lo]) - 1.0;
|
172
|
-
table.push(SamplerEntry {
|
173
|
-
primary: lo,
|
174
|
-
alternate: hi,
|
175
|
-
use_alternate: 1.0 - scaled_probabilities[lo],
|
176
|
-
});
|
177
|
-
|
178
|
-
if scaled_probabilities[hi] < 1.0 {
|
179
|
-
small.push(Reverse(hi))
|
180
|
-
} else if scaled_probabilities[hi] > 1.0 {
|
181
|
-
large.push(Reverse(hi))
|
182
|
-
} else {
|
183
|
-
table.push(SamplerEntry::single(hi))
|
184
|
-
}
|
185
|
-
}
|
186
|
-
for &Reverse(i) in small.iter() {
|
187
|
-
table.push(SamplerEntry::single(i))
|
188
|
-
}
|
189
|
-
for &Reverse(i) in large.iter() {
|
190
|
-
table.push(SamplerEntry::single(i))
|
191
|
-
}
|
192
|
-
|
193
|
-
for ref mut entry in table.iter_mut() {
|
194
|
-
if entry.alternate < entry.primary {
|
195
|
-
mem::swap(&mut entry.primary, &mut entry.alternate);
|
196
|
-
entry.use_alternate = 1.0 - entry.use_alternate;
|
197
|
-
}
|
198
|
-
}
|
199
|
-
|
200
|
-
table.sort();
|
201
|
-
assert!(table.len() > 0);
|
202
|
-
return Sampler { table: table };
|
203
|
-
}
|
204
|
-
|
205
|
-
pub fn sample(&self, source: &mut DataSource) -> Draw<usize> {
|
206
|
-
assert!(self.table.len() > 0);
|
207
|
-
let i = bounded_int(source, self.table.len() as u64 - 1)? as usize;
|
208
|
-
let entry = &self.table[i];
|
209
|
-
let use_alternate = weighted(source, entry.use_alternate as f64)?;
|
210
|
-
if use_alternate {
|
211
|
-
Ok(entry.alternate)
|
212
|
-
} else {
|
213
|
-
Ok(entry.primary)
|
214
|
-
}
|
215
|
-
}
|
216
|
-
}
|
217
|
-
|
218
|
-
pub fn good_bitlengths() -> Sampler {
|
219
|
-
let weights = vec![
|
220
|
-
4.0,
|
221
|
-
4.0,
|
222
|
-
4.0,
|
223
|
-
4.0,
|
224
|
-
4.0,
|
225
|
-
4.0,
|
226
|
-
4.0,
|
227
|
-
4.0, // 1 byte
|
228
|
-
2.0,
|
229
|
-
2.0,
|
230
|
-
2.0,
|
231
|
-
2.0,
|
232
|
-
2.0,
|
233
|
-
2.0,
|
234
|
-
2.0,
|
235
|
-
2.0, // 2 bytes
|
236
|
-
1.0,
|
237
|
-
1.0,
|
238
|
-
1.0,
|
239
|
-
1.0,
|
240
|
-
1.0,
|
241
|
-
1.0,
|
242
|
-
1.0,
|
243
|
-
1.0, // 3 bytes
|
244
|
-
0.5,
|
245
|
-
0.5,
|
246
|
-
0.5,
|
247
|
-
0.5,
|
248
|
-
0.5,
|
249
|
-
0.5,
|
250
|
-
0.5,
|
251
|
-
0.5, // 4 bytes
|
252
|
-
0.1,
|
253
|
-
0.1,
|
254
|
-
0.1,
|
255
|
-
0.1,
|
256
|
-
0.1,
|
257
|
-
0.1,
|
258
|
-
0.1,
|
259
|
-
0.1, // 5 bytes
|
260
|
-
0.1,
|
261
|
-
0.1,
|
262
|
-
0.1,
|
263
|
-
0.1,
|
264
|
-
0.1,
|
265
|
-
0.1,
|
266
|
-
0.1,
|
267
|
-
0.1, // 6 bytes
|
268
|
-
0.1,
|
269
|
-
0.1,
|
270
|
-
0.1,
|
271
|
-
0.1,
|
272
|
-
0.1,
|
273
|
-
0.1,
|
274
|
-
0.1,
|
275
|
-
0.1, // 7 bytes
|
276
|
-
0.1,
|
277
|
-
0.1,
|
278
|
-
0.1,
|
279
|
-
0.1,
|
280
|
-
0.1,
|
281
|
-
0.1,
|
282
|
-
0.1, // 8 bytes (last bit spare for sign)
|
283
|
-
];
|
284
|
-
assert!(weights.len() == 63);
|
285
|
-
Sampler::new(weights)
|
286
|
-
}
|
287
|
-
|
288
|
-
pub fn integer_from_bitlengths(source: &mut DataSource, bitlengths: &Sampler) -> Draw<i64> {
|
289
|
-
let bitlength = bitlengths.sample(source)? as u64 + 1;
|
290
|
-
let base = source.bits(bitlength)? as i64;
|
291
|
-
let sign = source.bits(1)?;
|
292
|
-
if sign > 0 {
|
293
|
-
Ok(-base)
|
294
|
-
} else {
|
295
|
-
Ok(base)
|
296
|
-
}
|
297
|
-
}
|
data/src/engine.rs
DELETED
@@ -1,620 +0,0 @@
|
|
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::cmp::Reverse;
|
7
|
-
use std::collections::HashMap;
|
8
|
-
use std::mem;
|
9
|
-
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
10
|
-
use std::thread;
|
11
|
-
|
12
|
-
use data::{DataSource, DataStream, Status, TestResult};
|
13
|
-
use intminimize::minimize_integer;
|
14
|
-
|
15
|
-
#[derive(Debug, Clone)]
|
16
|
-
enum LoopExitReason {
|
17
|
-
Complete,
|
18
|
-
MaxExamples,
|
19
|
-
Shutdown,
|
20
|
-
}
|
21
|
-
|
22
|
-
#[derive(Debug)]
|
23
|
-
enum LoopCommand {
|
24
|
-
RunThis(DataSource),
|
25
|
-
Finished(LoopExitReason, MainGenerationLoop),
|
26
|
-
}
|
27
|
-
|
28
|
-
#[derive(Debug)]
|
29
|
-
struct MainGenerationLoop {
|
30
|
-
receiver: Receiver<TestResult>,
|
31
|
-
sender: SyncSender<LoopCommand>,
|
32
|
-
max_examples: u64,
|
33
|
-
random: ChaChaRng,
|
34
|
-
|
35
|
-
best_example: Option<TestResult>,
|
36
|
-
|
37
|
-
valid_examples: u64,
|
38
|
-
invalid_examples: u64,
|
39
|
-
interesting_examples: u64,
|
40
|
-
}
|
41
|
-
|
42
|
-
type StepResult = Result<(), LoopExitReason>;
|
43
|
-
|
44
|
-
impl MainGenerationLoop {
|
45
|
-
fn run(mut self) {
|
46
|
-
let result = self.loop_body();
|
47
|
-
match result {
|
48
|
-
// Silent shutdown when the main thread terminates
|
49
|
-
Err(LoopExitReason::Shutdown) => (),
|
50
|
-
Err(reason) => {
|
51
|
-
// Must clone because otherwise it is borrowed.
|
52
|
-
let shutdown_sender = self.sender.clone();
|
53
|
-
shutdown_sender
|
54
|
-
.send(LoopCommand::Finished(reason, self))
|
55
|
-
.unwrap()
|
56
|
-
}
|
57
|
-
Ok(_) => panic!("BUG: Generation loop was not supposed to return normally."),
|
58
|
-
}
|
59
|
-
}
|
60
|
-
|
61
|
-
fn loop_body(&mut self) -> StepResult {
|
62
|
-
let interesting_example = self.generate_examples()?;
|
63
|
-
|
64
|
-
let mut shrinker = Shrinker::new(self, interesting_example, |r| {
|
65
|
-
r.status == Status::Interesting
|
66
|
-
});
|
67
|
-
|
68
|
-
shrinker.run()?;
|
69
|
-
|
70
|
-
return Err(LoopExitReason::Complete);
|
71
|
-
}
|
72
|
-
|
73
|
-
fn generate_examples(&mut self) -> Result<TestResult, LoopExitReason> {
|
74
|
-
while self.valid_examples < self.max_examples
|
75
|
-
&& self.invalid_examples < 10 * self.max_examples
|
76
|
-
{
|
77
|
-
let r = self.random.gen();
|
78
|
-
let result = self.execute(DataSource::from_random(r))?;
|
79
|
-
if result.status == Status::Interesting {
|
80
|
-
return Ok(result);
|
81
|
-
}
|
82
|
-
}
|
83
|
-
return Err(LoopExitReason::MaxExamples);
|
84
|
-
}
|
85
|
-
|
86
|
-
fn execute(&mut self, source: DataSource) -> Result<TestResult, LoopExitReason> {
|
87
|
-
let result = match self.sender.send(LoopCommand::RunThis(source)) {
|
88
|
-
Ok(_) => match self.receiver.recv() {
|
89
|
-
Ok(t) => t,
|
90
|
-
Err(_) => return Err(LoopExitReason::Shutdown),
|
91
|
-
},
|
92
|
-
Err(_) => return Err(LoopExitReason::Shutdown),
|
93
|
-
};
|
94
|
-
match result.status {
|
95
|
-
Status::Overflow => (),
|
96
|
-
Status::Invalid => self.invalid_examples += 1,
|
97
|
-
Status::Valid => self.valid_examples += 1,
|
98
|
-
Status::Interesting => {
|
99
|
-
self.best_example = Some(result.clone());
|
100
|
-
self.interesting_examples += 1;
|
101
|
-
}
|
102
|
-
}
|
103
|
-
|
104
|
-
Ok(result)
|
105
|
-
}
|
106
|
-
}
|
107
|
-
|
108
|
-
struct Shrinker<'owner, Predicate> {
|
109
|
-
_predicate: Predicate,
|
110
|
-
shrink_target: TestResult,
|
111
|
-
changes: u64,
|
112
|
-
expensive_passes_enabled: bool,
|
113
|
-
main_loop: &'owner mut MainGenerationLoop,
|
114
|
-
}
|
115
|
-
|
116
|
-
impl<'owner, Predicate> Shrinker<'owner, Predicate>
|
117
|
-
where
|
118
|
-
Predicate: Fn(&TestResult) -> bool,
|
119
|
-
{
|
120
|
-
fn new(
|
121
|
-
main_loop: &'owner mut MainGenerationLoop,
|
122
|
-
shrink_target: TestResult,
|
123
|
-
predicate: Predicate,
|
124
|
-
) -> Shrinker<'owner, Predicate> {
|
125
|
-
assert!(predicate(&shrink_target));
|
126
|
-
Shrinker {
|
127
|
-
main_loop: main_loop,
|
128
|
-
_predicate: predicate,
|
129
|
-
shrink_target: shrink_target,
|
130
|
-
changes: 0,
|
131
|
-
expensive_passes_enabled: false,
|
132
|
-
}
|
133
|
-
}
|
134
|
-
|
135
|
-
fn predicate(&mut self, result: &TestResult) -> bool {
|
136
|
-
let succeeded = (self._predicate)(result);
|
137
|
-
if succeeded
|
138
|
-
&& (
|
139
|
-
// In the presence of writes it may be the case that we thought
|
140
|
-
// we were going to shrink this but didn't actually succeed because
|
141
|
-
// the written value was used.
|
142
|
-
result.record.len() < self.shrink_target.record.len() || (
|
143
|
-
result.record.len() == self.shrink_target.record.len() &&
|
144
|
-
result.record < self.shrink_target.record
|
145
|
-
)
|
146
|
-
) {
|
147
|
-
self.changes += 1;
|
148
|
-
self.shrink_target = result.clone();
|
149
|
-
}
|
150
|
-
succeeded
|
151
|
-
}
|
152
|
-
|
153
|
-
fn run(&mut self) -> StepResult {
|
154
|
-
let mut prev = self.changes + 1;
|
155
|
-
|
156
|
-
while prev != self.changes {
|
157
|
-
prev = self.changes;
|
158
|
-
self.adaptive_delete()?;
|
159
|
-
self.minimize_individual_blocks()?;
|
160
|
-
self.minimize_duplicated_blocks()?;
|
161
|
-
if prev == self.changes {
|
162
|
-
self.expensive_passes_enabled = true;
|
163
|
-
}
|
164
|
-
if !self.expensive_passes_enabled {
|
165
|
-
continue;
|
166
|
-
}
|
167
|
-
|
168
|
-
self.reorder_blocks()?;
|
169
|
-
self.lower_and_delete()?;
|
170
|
-
self.delete_all_ranges()?;
|
171
|
-
}
|
172
|
-
Ok(())
|
173
|
-
}
|
174
|
-
|
175
|
-
fn lower_and_delete(&mut self) -> StepResult {
|
176
|
-
let mut i = 0;
|
177
|
-
while i < self.shrink_target.record.len() {
|
178
|
-
if self.shrink_target.record[i] > 0 {
|
179
|
-
let mut attempt = self.shrink_target.record.clone();
|
180
|
-
attempt[i] -= 1;
|
181
|
-
let (succeeded, result) = self.execute(&attempt)?;
|
182
|
-
if !succeeded && result.record.len() < self.shrink_target.record.len() {
|
183
|
-
let mut j = 0;
|
184
|
-
while j < self.shrink_target.draws.len() {
|
185
|
-
// Having to copy this is an annoying consequence of lexical lifetimes -
|
186
|
-
// if we borrowed it immutably then we'd not be allowed to call self.incorporate
|
187
|
-
// down below. Fortunately these things are tiny structs of integers so it doesn't
|
188
|
-
// really matter.
|
189
|
-
let d = self.shrink_target.draws[j].clone();
|
190
|
-
if d.start > i {
|
191
|
-
let mut attempt2 = attempt.clone();
|
192
|
-
attempt2.drain(d.start..d.end);
|
193
|
-
if self.incorporate(&attempt2)? {
|
194
|
-
break;
|
195
|
-
}
|
196
|
-
}
|
197
|
-
j += 1;
|
198
|
-
}
|
199
|
-
}
|
200
|
-
}
|
201
|
-
i += 1;
|
202
|
-
}
|
203
|
-
Ok(())
|
204
|
-
}
|
205
|
-
|
206
|
-
fn reorder_blocks(&mut self) -> StepResult {
|
207
|
-
let mut i = 0;
|
208
|
-
while i < self.shrink_target.record.len() {
|
209
|
-
let mut j = i + 1;
|
210
|
-
while j < self.shrink_target.record.len() {
|
211
|
-
assert!(i < self.shrink_target.record.len());
|
212
|
-
if self.shrink_target.record[i] == 0 {
|
213
|
-
break;
|
214
|
-
}
|
215
|
-
if self.shrink_target.record[j] < self.shrink_target.record[i] {
|
216
|
-
let mut attempt = self.shrink_target.record.clone();
|
217
|
-
attempt.swap(i, j);
|
218
|
-
self.incorporate(&attempt)?;
|
219
|
-
}
|
220
|
-
j += 1;
|
221
|
-
}
|
222
|
-
i += 1;
|
223
|
-
}
|
224
|
-
Ok(())
|
225
|
-
}
|
226
|
-
|
227
|
-
fn try_delete_range(
|
228
|
-
&mut self,
|
229
|
-
target: &TestResult,
|
230
|
-
i: usize,
|
231
|
-
k: usize,
|
232
|
-
) -> Result<bool, LoopExitReason> {
|
233
|
-
// Attempts to delete k non-overlapping draws starting from the draw at index i.
|
234
|
-
|
235
|
-
let mut stack: Vec<(usize, usize)> = Vec::new();
|
236
|
-
let mut j = i;
|
237
|
-
while j < target.draws.len() && stack.len() < k {
|
238
|
-
let m = target.draws[j].start;
|
239
|
-
let n = target.draws[j].end;
|
240
|
-
assert!(m < n);
|
241
|
-
if m < n && (stack.len() == 0 || stack[stack.len() - 1].1 <= m) {
|
242
|
-
stack.push((m, n))
|
243
|
-
}
|
244
|
-
j += 1;
|
245
|
-
}
|
246
|
-
|
247
|
-
let mut attempt = target.record.clone();
|
248
|
-
while stack.len() > 0 {
|
249
|
-
let (m, n) = stack.pop().unwrap();
|
250
|
-
attempt.drain(m..n);
|
251
|
-
}
|
252
|
-
|
253
|
-
if attempt.len() >= self.shrink_target.record.len() {
|
254
|
-
Ok(false)
|
255
|
-
} else {
|
256
|
-
self.incorporate(&attempt)
|
257
|
-
}
|
258
|
-
}
|
259
|
-
|
260
|
-
fn adaptive_delete(&mut self) -> StepResult {
|
261
|
-
let mut i = 0;
|
262
|
-
let target = self.shrink_target.clone();
|
263
|
-
|
264
|
-
while i < target.draws.len() {
|
265
|
-
// This is an adaptive pass loosely modelled after timsort. If
|
266
|
-
// little or nothing is deletable here then we don't try any more
|
267
|
-
// deletions than the naive greedy algorithm would, but if it looks
|
268
|
-
// like we have an opportunity to delete a lot then we try to do so.
|
269
|
-
|
270
|
-
// What we're trying to do is to find a large k such that we can
|
271
|
-
// delete k but not k + 1 draws starting from this point, and we
|
272
|
-
// want to do that in O(log(k)) rather than O(k) test executions.
|
273
|
-
|
274
|
-
// We try a quite careful sequence of small shrinks here before we
|
275
|
-
// move on to anything big. This is because if we try to be
|
276
|
-
// aggressive too early on we'll tend to find that we lose out when
|
277
|
-
// the example is "nearly minimal".
|
278
|
-
if self.try_delete_range(&target, i, 2)? {
|
279
|
-
if self.try_delete_range(&target, i, 3)? && self.try_delete_range(&target, i, 4)? {
|
280
|
-
let mut hi = 5;
|
281
|
-
// At this point it looks like we've got a pretty good
|
282
|
-
// opportunity for a long run here. We do an exponential
|
283
|
-
// probe upwards to try and find some k where we can't
|
284
|
-
// delete many intervals. We do this rather than choosing
|
285
|
-
// that upper bound to immediately be large because we
|
286
|
-
// don't really expect k to be huge. If it turns out that
|
287
|
-
// it is, the subsequent example is going to be so tiny that
|
288
|
-
// it doesn't really matter if we waste a bit of extra time
|
289
|
-
// here.
|
290
|
-
while self.try_delete_range(&target, i, hi)? {
|
291
|
-
assert!(hi <= target.draws.len());
|
292
|
-
hi *= 2;
|
293
|
-
}
|
294
|
-
// We now know that we can delete the first lo intervals but
|
295
|
-
// not the first hi. We preserve that property while doing
|
296
|
-
// a binary search to find the point at which we stop being
|
297
|
-
// able to delete intervals.
|
298
|
-
let mut lo = 4;
|
299
|
-
while lo + 1 < hi {
|
300
|
-
let mid = lo + (hi - lo) / 2;
|
301
|
-
if self.try_delete_range(&target, i, mid)? {
|
302
|
-
lo = mid;
|
303
|
-
} else {
|
304
|
-
hi = mid;
|
305
|
-
}
|
306
|
-
}
|
307
|
-
}
|
308
|
-
} else {
|
309
|
-
self.try_delete_range(&target, i, 1)?;
|
310
|
-
}
|
311
|
-
// We unconditionally bump i because we have always tried deleting
|
312
|
-
// one more example than we succeeded at deleting, so we expect the
|
313
|
-
// next example to be undeletable.
|
314
|
-
i += 1;
|
315
|
-
}
|
316
|
-
return Ok(());
|
317
|
-
}
|
318
|
-
|
319
|
-
fn delete_all_ranges(&mut self) -> StepResult {
|
320
|
-
let mut i = 0;
|
321
|
-
while i < self.shrink_target.record.len() {
|
322
|
-
let start_length = self.shrink_target.record.len();
|
323
|
-
|
324
|
-
let mut j = i + 1;
|
325
|
-
while j < self.shrink_target.record.len() {
|
326
|
-
assert!(j > i);
|
327
|
-
let mut attempt = self.shrink_target.record.clone();
|
328
|
-
attempt.drain(i..j);
|
329
|
-
assert!(attempt.len() + (j - i) == self.shrink_target.record.len());
|
330
|
-
let deleted = self.incorporate(&attempt)?;
|
331
|
-
if !deleted {
|
332
|
-
j += 1;
|
333
|
-
}
|
334
|
-
}
|
335
|
-
if start_length == self.shrink_target.record.len() {
|
336
|
-
i += 1;
|
337
|
-
}
|
338
|
-
}
|
339
|
-
Ok(())
|
340
|
-
}
|
341
|
-
|
342
|
-
fn try_lowering_value(&mut self, i: usize, v: u64) -> Result<bool, LoopExitReason> {
|
343
|
-
if v >= self.shrink_target.record[i] {
|
344
|
-
return Ok(false);
|
345
|
-
}
|
346
|
-
|
347
|
-
let mut attempt = self.shrink_target.record.clone();
|
348
|
-
attempt[i] = v;
|
349
|
-
let (succeeded, result) = self.execute(&attempt)?;
|
350
|
-
assert!(result.record.len() <= self.shrink_target.record.len());
|
351
|
-
let lost_bytes = self.shrink_target.record.len() - result.record.len();
|
352
|
-
if !succeeded && result.status == Status::Valid && lost_bytes > 0 {
|
353
|
-
attempt.drain(i + 1..i + lost_bytes + 1);
|
354
|
-
assert!(attempt.len() + lost_bytes == self.shrink_target.record.len());
|
355
|
-
self.incorporate(&attempt)
|
356
|
-
} else {
|
357
|
-
Ok(succeeded)
|
358
|
-
}
|
359
|
-
}
|
360
|
-
|
361
|
-
fn minimize_individual_blocks(&mut self) -> StepResult {
|
362
|
-
let mut i = 0;
|
363
|
-
|
364
|
-
while i < self.shrink_target.record.len() {
|
365
|
-
if !self.shrink_target.written_indices.contains(&i) {
|
366
|
-
minimize_integer(self.shrink_target.record[i], |v| {
|
367
|
-
self.try_lowering_value(i, v)
|
368
|
-
})?;
|
369
|
-
}
|
370
|
-
|
371
|
-
i += 1;
|
372
|
-
}
|
373
|
-
|
374
|
-
Ok(())
|
375
|
-
}
|
376
|
-
|
377
|
-
fn calc_duplicates(&self) -> Vec<Vec<usize>> {
|
378
|
-
assert!(self.shrink_target.record.len() == self.shrink_target.sizes.len());
|
379
|
-
let mut duplicates: HashMap<(u64, u64), Vec<usize>> = HashMap::new();
|
380
|
-
for (i, (u, v)) in self.shrink_target
|
381
|
-
.record
|
382
|
-
.iter()
|
383
|
-
.zip(self.shrink_target.sizes.iter())
|
384
|
-
.enumerate()
|
385
|
-
{
|
386
|
-
if !self.shrink_target.written_indices.contains(&i) {
|
387
|
-
duplicates
|
388
|
-
.entry((*u, *v))
|
389
|
-
.or_insert_with(|| Vec::new())
|
390
|
-
.push(i);
|
391
|
-
}
|
392
|
-
}
|
393
|
-
|
394
|
-
let mut result: Vec<Vec<usize>> = duplicates
|
395
|
-
.drain()
|
396
|
-
.filter_map(|(_, elements)| {
|
397
|
-
if elements.len() > 1 {
|
398
|
-
Some(elements)
|
399
|
-
} else {
|
400
|
-
None
|
401
|
-
}
|
402
|
-
})
|
403
|
-
.collect();
|
404
|
-
result.sort_by_key(|v| Reverse(v.len()));
|
405
|
-
result
|
406
|
-
}
|
407
|
-
|
408
|
-
fn minimize_duplicated_blocks(&mut self) -> StepResult {
|
409
|
-
let mut i = 0;
|
410
|
-
let mut targets = self.calc_duplicates();
|
411
|
-
while i < targets.len() {
|
412
|
-
let target = mem::replace(&mut targets[i], Vec::new());
|
413
|
-
i += 1;
|
414
|
-
assert!(target.len() > 0);
|
415
|
-
let v = self.shrink_target.record[target[0]];
|
416
|
-
let base = self.shrink_target.record.clone();
|
417
|
-
|
418
|
-
let w = minimize_integer(v, |t| {
|
419
|
-
let mut attempt = base.clone();
|
420
|
-
for i in &target {
|
421
|
-
attempt[*i] = t
|
422
|
-
}
|
423
|
-
self.incorporate(&attempt)
|
424
|
-
})?;
|
425
|
-
if w != v {
|
426
|
-
targets = self.calc_duplicates();
|
427
|
-
}
|
428
|
-
}
|
429
|
-
Ok(())
|
430
|
-
}
|
431
|
-
|
432
|
-
fn execute(&mut self, buf: &DataStream) -> Result<(bool, TestResult), LoopExitReason> {
|
433
|
-
// TODO: Later there will be caching here
|
434
|
-
let result = self.main_loop.execute(DataSource::from_vec(buf.clone()))?;
|
435
|
-
Ok((self.predicate(&result), result))
|
436
|
-
}
|
437
|
-
|
438
|
-
fn incorporate(&mut self, buf: &DataStream) -> Result<bool, LoopExitReason> {
|
439
|
-
assert!(
|
440
|
-
buf.len() <= self.shrink_target.record.len(),
|
441
|
-
"Expected incorporate to not increase length, but buf.len() = {} \
|
442
|
-
while shrink target was {}",
|
443
|
-
buf.len(),
|
444
|
-
self.shrink_target.record.len()
|
445
|
-
);
|
446
|
-
if buf.len() == self.shrink_target.record.len() {
|
447
|
-
assert!(buf < &self.shrink_target.record);
|
448
|
-
}
|
449
|
-
if self.shrink_target.record.starts_with(buf) {
|
450
|
-
return Ok(false);
|
451
|
-
}
|
452
|
-
let (succeeded, _) = self.execute(buf)?;
|
453
|
-
Ok(succeeded)
|
454
|
-
}
|
455
|
-
}
|
456
|
-
|
457
|
-
#[derive(Debug, Clone, Eq, PartialEq)]
|
458
|
-
enum EngineState {
|
459
|
-
AwaitingCompletion,
|
460
|
-
ReadyToProvide,
|
461
|
-
}
|
462
|
-
|
463
|
-
#[derive(Debug)]
|
464
|
-
pub struct Engine {
|
465
|
-
// The next response from the main loop. Once
|
466
|
-
// this is set to Some(Finished(_)) it stays that way,
|
467
|
-
// otherwise it is cleared on access.
|
468
|
-
loop_response: Option<LoopCommand>,
|
469
|
-
|
470
|
-
state: EngineState,
|
471
|
-
|
472
|
-
// Communication channels with the main testing loop
|
473
|
-
handle: Option<thread::JoinHandle<()>>,
|
474
|
-
receiver: Receiver<LoopCommand>,
|
475
|
-
sender: SyncSender<TestResult>,
|
476
|
-
}
|
477
|
-
|
478
|
-
impl Clone for Engine {
|
479
|
-
fn clone(&self) -> Engine {
|
480
|
-
panic!("BUG: The Engine was unexpectedly cloned");
|
481
|
-
}
|
482
|
-
}
|
483
|
-
|
484
|
-
impl Engine {
|
485
|
-
pub fn new(max_examples: u64, seed: &[u32]) -> Engine {
|
486
|
-
let (send_local, recv_remote) = sync_channel(1);
|
487
|
-
let (send_remote, recv_local) = sync_channel(1);
|
488
|
-
|
489
|
-
let main_loop = MainGenerationLoop {
|
490
|
-
max_examples: max_examples,
|
491
|
-
random: ChaChaRng::from_seed(seed),
|
492
|
-
sender: send_remote,
|
493
|
-
receiver: recv_remote,
|
494
|
-
best_example: None,
|
495
|
-
valid_examples: 0,
|
496
|
-
invalid_examples: 0,
|
497
|
-
interesting_examples: 0,
|
498
|
-
};
|
499
|
-
|
500
|
-
let handle = thread::Builder::new()
|
501
|
-
.name("Hypothesis main loop".to_string())
|
502
|
-
.spawn(move || {
|
503
|
-
main_loop.run();
|
504
|
-
})
|
505
|
-
.unwrap();
|
506
|
-
|
507
|
-
Engine {
|
508
|
-
loop_response: None,
|
509
|
-
sender: send_local,
|
510
|
-
receiver: recv_local,
|
511
|
-
handle: Some(handle),
|
512
|
-
state: EngineState::ReadyToProvide,
|
513
|
-
}
|
514
|
-
}
|
515
|
-
|
516
|
-
pub fn mark_finished(&mut self, source: DataSource, status: Status) -> () {
|
517
|
-
self.consume_test_result(source.to_result(status))
|
518
|
-
}
|
519
|
-
|
520
|
-
pub fn next_source(&mut self) -> Option<DataSource> {
|
521
|
-
assert!(self.state == EngineState::ReadyToProvide);
|
522
|
-
self.state = EngineState::AwaitingCompletion;
|
523
|
-
|
524
|
-
self.await_loop_response();
|
525
|
-
|
526
|
-
let mut local_result = None;
|
527
|
-
mem::swap(&mut local_result, &mut self.loop_response);
|
528
|
-
|
529
|
-
match local_result {
|
530
|
-
Some(LoopCommand::RunThis(source)) => return Some(source),
|
531
|
-
None => panic!("BUG: Loop response should not be empty at this point"),
|
532
|
-
_ => {
|
533
|
-
self.loop_response = local_result;
|
534
|
-
return None;
|
535
|
-
}
|
536
|
-
}
|
537
|
-
}
|
538
|
-
|
539
|
-
pub fn best_source(&self) -> Option<DataSource> {
|
540
|
-
match &self.loop_response {
|
541
|
-
&Some(LoopCommand::Finished(
|
542
|
-
_,
|
543
|
-
MainGenerationLoop {
|
544
|
-
best_example: Some(ref result),
|
545
|
-
..
|
546
|
-
},
|
547
|
-
)) => Some(DataSource::from_vec(result.record.clone())),
|
548
|
-
_ => None,
|
549
|
-
}
|
550
|
-
}
|
551
|
-
|
552
|
-
fn consume_test_result(&mut self, result: TestResult) -> () {
|
553
|
-
assert!(self.state == EngineState::AwaitingCompletion);
|
554
|
-
self.state = EngineState::ReadyToProvide;
|
555
|
-
|
556
|
-
if self.has_shutdown() {
|
557
|
-
return ();
|
558
|
-
}
|
559
|
-
|
560
|
-
// NB: Deliberately not matching on result. If this fails,
|
561
|
-
// that's OK - it means the loop has shut down and when we ask
|
562
|
-
// for data from it we'll get its shutdown response.
|
563
|
-
let _ = self.sender.send(result);
|
564
|
-
}
|
565
|
-
|
566
|
-
pub fn was_unsatisfiable(&self) -> bool {
|
567
|
-
match &self.loop_response {
|
568
|
-
&Some(LoopCommand::Finished(_, ref main_loop)) => {
|
569
|
-
main_loop.interesting_examples == 0 && main_loop.valid_examples == 0
|
570
|
-
}
|
571
|
-
_ => false,
|
572
|
-
}
|
573
|
-
}
|
574
|
-
|
575
|
-
fn has_shutdown(&mut self) -> bool {
|
576
|
-
match &self.loop_response {
|
577
|
-
&Some(LoopCommand::Finished(..)) => true,
|
578
|
-
_ => false,
|
579
|
-
}
|
580
|
-
}
|
581
|
-
|
582
|
-
fn await_thread_termination(&mut self) {
|
583
|
-
let mut maybe_handle = None;
|
584
|
-
mem::swap(&mut self.handle, &mut maybe_handle);
|
585
|
-
if let Some(handle) = maybe_handle {
|
586
|
-
if let Err(boxed_msg) = handle.join() {
|
587
|
-
// FIXME: This is awful but as far as I can tell this is
|
588
|
-
// genuinely the only way to get the actual message out of the
|
589
|
-
// panic in the child thread! It's boxed as an Any, and the
|
590
|
-
// debug of Any just says "Any". Fortunately the main loop is
|
591
|
-
// very much under our control so this doesn't matter too much
|
592
|
-
// here, but yuck!
|
593
|
-
if let Some(msg) = boxed_msg.downcast_ref::<&str>() {
|
594
|
-
panic!(msg.to_string());
|
595
|
-
} else if let Some(msg) = boxed_msg.downcast_ref::<String>() {
|
596
|
-
panic!(msg.clone());
|
597
|
-
} else {
|
598
|
-
panic!("BUG: Unexpected panic format in main loop");
|
599
|
-
}
|
600
|
-
}
|
601
|
-
}
|
602
|
-
}
|
603
|
-
|
604
|
-
fn await_loop_response(&mut self) -> () {
|
605
|
-
if self.loop_response.is_none() {
|
606
|
-
match self.receiver.recv() {
|
607
|
-
Ok(response) => {
|
608
|
-
self.loop_response = Some(response);
|
609
|
-
if self.has_shutdown() {
|
610
|
-
self.await_thread_termination();
|
611
|
-
}
|
612
|
-
}
|
613
|
-
Err(_) => {
|
614
|
-
self.await_thread_termination();
|
615
|
-
panic!("BUG: Unexpected silent termination of generation loop.")
|
616
|
-
}
|
617
|
-
}
|
618
|
-
}
|
619
|
-
}
|
620
|
-
}
|
data/src/intminimize.rs
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
use std::cmp::min;
|
2
|
-
|
3
|
-
const SMALL: u64 = 5;
|
4
|
-
|
5
|
-
struct Minimizer<'a, F: 'a> {
|
6
|
-
criterion: &'a mut F,
|
7
|
-
best: u64,
|
8
|
-
}
|
9
|
-
|
10
|
-
impl<'a, F, T> Minimizer<'a, F>
|
11
|
-
where
|
12
|
-
F: 'a + FnMut(u64) -> Result<bool, T>,
|
13
|
-
{
|
14
|
-
fn test(&mut self, candidate: u64) -> Result<bool, T> {
|
15
|
-
if candidate == self.best {
|
16
|
-
return Ok(true);
|
17
|
-
}
|
18
|
-
if candidate > self.best {
|
19
|
-
return Ok(false);
|
20
|
-
}
|
21
|
-
let result = (self.criterion)(candidate)?;
|
22
|
-
if result {
|
23
|
-
self.best = candidate;
|
24
|
-
}
|
25
|
-
Ok(result)
|
26
|
-
}
|
27
|
-
|
28
|
-
fn modify<G>(&mut self, g: G) -> Result<bool, T>
|
29
|
-
where
|
30
|
-
G: Fn(u64) -> u64,
|
31
|
-
{
|
32
|
-
let x = g(self.best);
|
33
|
-
self.test(x)
|
34
|
-
}
|
35
|
-
}
|
36
|
-
|
37
|
-
pub fn minimize_integer<F, T>(start: u64, mut criterion: F) -> Result<u64, T>
|
38
|
-
where
|
39
|
-
F: FnMut(u64) -> Result<bool, T>,
|
40
|
-
{
|
41
|
-
if start == 0 {
|
42
|
-
return Ok(start);
|
43
|
-
}
|
44
|
-
|
45
|
-
for i in 0..min(start, SMALL) {
|
46
|
-
if criterion(i)? {
|
47
|
-
return Ok(i);
|
48
|
-
}
|
49
|
-
}
|
50
|
-
if start <= SMALL {
|
51
|
-
return Ok(start);
|
52
|
-
}
|
53
|
-
|
54
|
-
let mut minimizer = Minimizer {
|
55
|
-
best: start,
|
56
|
-
criterion: &mut criterion,
|
57
|
-
};
|
58
|
-
|
59
|
-
loop {
|
60
|
-
if !minimizer.modify(|x| x >> 1)? {
|
61
|
-
break;
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
for i in 0..64 {
|
66
|
-
minimizer.modify(|x| x ^ (1 << i))?;
|
67
|
-
}
|
68
|
-
|
69
|
-
assert!(minimizer.best >= SMALL);
|
70
|
-
|
71
|
-
for i in 0..64 {
|
72
|
-
let left_mask = 1 << i;
|
73
|
-
let mut right_mask = left_mask >> 1;
|
74
|
-
while right_mask != 0 {
|
75
|
-
minimizer.modify(|x| {
|
76
|
-
if x & left_mask == 0 || x & right_mask != 0 {
|
77
|
-
x
|
78
|
-
} else {
|
79
|
-
x ^ (right_mask | left_mask)
|
80
|
-
}
|
81
|
-
})?;
|
82
|
-
right_mask >>= 1;
|
83
|
-
}
|
84
|
-
}
|
85
|
-
|
86
|
-
if !minimizer.modify(|x| x - 1)? {
|
87
|
-
return Ok(minimizer.best);
|
88
|
-
}
|
89
|
-
|
90
|
-
let mut lo = 0;
|
91
|
-
let mut hi = minimizer.best;
|
92
|
-
while lo + 1 < hi {
|
93
|
-
let mid = lo + (hi - lo) / 2;
|
94
|
-
if minimizer.test(mid)? {
|
95
|
-
hi = mid;
|
96
|
-
} else {
|
97
|
-
lo = mid;
|
98
|
-
}
|
99
|
-
}
|
100
|
-
|
101
|
-
Ok(minimizer.best)
|
102
|
-
}
|
103
|
-
|
104
|
-
#[cfg(test)]
|
105
|
-
mod tests {
|
106
|
-
use super::*;
|
107
|
-
|
108
|
-
fn non_failing_minimize<F>(start: u64, criterion: F) -> u64
|
109
|
-
where
|
110
|
-
F: Fn(u64) -> bool,
|
111
|
-
{
|
112
|
-
let mut best = start;
|
113
|
-
|
114
|
-
loop {
|
115
|
-
let ran: Result<u64, ()> = minimize_integer(best, |x| Ok(criterion(x)));
|
116
|
-
let result = ran.unwrap();
|
117
|
-
assert!(result <= best);
|
118
|
-
if result == best {
|
119
|
-
return best;
|
120
|
-
}
|
121
|
-
best = result;
|
122
|
-
}
|
123
|
-
}
|
124
|
-
|
125
|
-
#[test]
|
126
|
-
fn minimize_down_to() {
|
127
|
-
let n = non_failing_minimize(100, |x| x >= 10);
|
128
|
-
assert_eq!(n, 10);
|
129
|
-
}
|
130
|
-
|
131
|
-
#[test]
|
132
|
-
fn unset_relevant_bits() {
|
133
|
-
let x = 0b101010101010;
|
134
|
-
let y = 0b111111111111;
|
135
|
-
let n = non_failing_minimize(y, |k| k & x == x);
|
136
|
-
assert_eq!(n, x);
|
137
|
-
}
|
138
|
-
|
139
|
-
#[test]
|
140
|
-
fn sort_bits() {
|
141
|
-
let x: u64 = 0b1011011011000111;
|
142
|
-
let y: u64 = 0b0000001111111111;
|
143
|
-
let c = x.count_ones();
|
144
|
-
assert_eq!(c, y.count_ones());
|
145
|
-
|
146
|
-
let n = non_failing_minimize(x, |k| k.count_ones() == c);
|
147
|
-
assert_eq!(y, n);
|
148
|
-
}
|
149
|
-
}
|