hypothesis-specs 0.0.12 → 0.0.13
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 +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
|
-
}
|