hypothesis-specs 0.0.8 → 0.0.9
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 +8 -0
- data/lib/hypothesis/testcase.rb +2 -0
- data/src/data.rs +96 -17
- data/src/distributions.rs +20 -16
- data/src/engine.rs +198 -24
- data/src/lib.rs +12 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e8eb07704e06280a79469b76b6b7267cf356797
|
4
|
+
data.tar.gz: edd8bb7476a3a84dedf2751016385dbe2d6afcac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e72ef276034266146155092d14827e75f5f20ec3986dd57245f94694e1f7da09d5cc62b74fced3829e0d8e5dbff522ad44d6fc88e2e49d274979eaae3f06e0e
|
7
|
+
data.tar.gz: b6917a3a24f98febffaab647ecec0393827e3f116390762d3163cd9d2fe7e1cac49cfd79bc2db1e448beb044e862b0c96c79a5e3e8d7aed986ff718eef755d40
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## Hypothesis for Ruby 0.0.9 (2018-04-20)
|
2
|
+
|
3
|
+
This improves Hypothesis for Ruby's shrinking to be much closer
|
4
|
+
to Hypothesis for Python's. It's still far from complete, and even
|
5
|
+
in cases where it has the same level of quality it will often be
|
6
|
+
significantly slower, but examples should now be much more consistent,
|
7
|
+
especially in cases where you are using e.g. `built_as`.
|
8
|
+
|
1
9
|
## Hypothesis for Ruby 0.0.8 (2018-02-20)
|
2
10
|
|
3
11
|
This release fixes the dependency on Rake to be in a more sensible range.
|
data/lib/hypothesis/testcase.rb
CHANGED
data/src/data.rs
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
// needs.
|
3
3
|
|
4
4
|
use rand::{ChaChaRng, Rng};
|
5
|
+
use std::collections::HashSet;
|
5
6
|
|
6
7
|
pub type DataStream = Vec<u64>;
|
7
8
|
|
@@ -14,6 +15,22 @@ enum BitGenerator {
|
|
14
15
|
Recorded(DataStream),
|
15
16
|
}
|
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
|
+
|
17
34
|
// Main entry point for running a test:
|
18
35
|
// A test function takes a DataSource, uses it to
|
19
36
|
// produce some data, and the DataSource records the
|
@@ -22,9 +39,61 @@ enum BitGenerator {
|
|
22
39
|
pub struct DataSource {
|
23
40
|
bitgenerator: BitGenerator,
|
24
41
|
record: DataStream,
|
42
|
+
draws: Vec<DrawInProgress>,
|
43
|
+
draw_stack: Vec<usize>,
|
44
|
+
written_indices: HashSet<usize>,
|
25
45
|
}
|
26
46
|
|
27
47
|
impl DataSource {
|
48
|
+
fn new(generator: BitGenerator) -> DataSource {
|
49
|
+
return DataSource {
|
50
|
+
bitgenerator: generator,
|
51
|
+
record: DataStream::new(),
|
52
|
+
draws: Vec::new(),
|
53
|
+
draw_stack: Vec::new(),
|
54
|
+
written_indices: HashSet::new(),
|
55
|
+
};
|
56
|
+
}
|
57
|
+
|
58
|
+
pub fn from_random(random: ChaChaRng) -> DataSource {
|
59
|
+
return DataSource::new(BitGenerator::Random(random));
|
60
|
+
}
|
61
|
+
|
62
|
+
pub fn from_vec(record: DataStream) -> DataSource {
|
63
|
+
return DataSource::new(BitGenerator::Recorded(record));
|
64
|
+
}
|
65
|
+
|
66
|
+
pub fn start_draw(&mut self) {
|
67
|
+
let i = self.draws.len();
|
68
|
+
let depth = self.draw_stack.len();
|
69
|
+
let start = self.record.len();
|
70
|
+
|
71
|
+
self.draw_stack.push(i);
|
72
|
+
self.draws.push(DrawInProgress {
|
73
|
+
start: start,
|
74
|
+
end: None,
|
75
|
+
depth: depth,
|
76
|
+
});
|
77
|
+
}
|
78
|
+
|
79
|
+
pub fn stop_draw(&mut self) {
|
80
|
+
assert!(self.draws.len() > 0);
|
81
|
+
assert!(self.draw_stack.len() > 0);
|
82
|
+
let i = self.draw_stack.pop().unwrap();
|
83
|
+
let end = self.record.len();
|
84
|
+
self.draws[i].end = Some(end);
|
85
|
+
}
|
86
|
+
|
87
|
+
pub fn write(&mut self, value: u64) -> Result<(), FailedDraw> {
|
88
|
+
match self.bitgenerator {
|
89
|
+
BitGenerator::Recorded(ref mut v) if self.record.len() >= v.len() => Err(FailedDraw),
|
90
|
+
_ => {
|
91
|
+
self.record.push(value);
|
92
|
+
Ok(())
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
28
97
|
pub fn bits(&mut self, n_bits: u64) -> Result<u64, FailedDraw> {
|
29
98
|
let mut result = match self.bitgenerator {
|
30
99
|
BitGenerator::Random(ref mut random) => random.next_u64(),
|
@@ -45,32 +114,40 @@ impl DataSource {
|
|
45
114
|
return Ok(result);
|
46
115
|
}
|
47
116
|
|
48
|
-
fn
|
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 {
|
117
|
+
pub fn to_result(mut self, status: Status) -> TestResult {
|
64
118
|
TestResult {
|
65
119
|
record: self.record,
|
66
120
|
status: status,
|
121
|
+
written_indices: self.written_indices,
|
122
|
+
draws: self.draws
|
123
|
+
.drain(..)
|
124
|
+
.filter_map(|d| match d {
|
125
|
+
DrawInProgress {
|
126
|
+
depth,
|
127
|
+
start,
|
128
|
+
end: Some(end),
|
129
|
+
} if start < end =>
|
130
|
+
{
|
131
|
+
Some(Draw {
|
132
|
+
start: start,
|
133
|
+
end: end,
|
134
|
+
depth: depth,
|
135
|
+
})
|
136
|
+
}
|
137
|
+
DrawInProgress { end: None, .. } => {
|
138
|
+
assert!(status == Status::Invalid || status == Status::Overflow);
|
139
|
+
None
|
140
|
+
}
|
141
|
+
_ => None,
|
142
|
+
})
|
143
|
+
.collect(),
|
67
144
|
}
|
68
145
|
}
|
69
146
|
}
|
70
147
|
|
71
148
|
// Status indicates the result that we got from completing
|
72
149
|
// a single test execution.
|
73
|
-
#[derive(Debug, Clone, Eq, PartialEq)]
|
150
|
+
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
74
151
|
pub enum Status {
|
75
152
|
// The test tried to read more data than we had for it.
|
76
153
|
Overflow,
|
@@ -96,4 +173,6 @@ pub enum Status {
|
|
96
173
|
pub struct TestResult {
|
97
174
|
pub record: DataStream,
|
98
175
|
pub status: Status,
|
176
|
+
pub draws: Vec<Draw>,
|
177
|
+
pub written_indices: HashSet<usize>,
|
99
178
|
}
|
data/src/distributions.rs
CHANGED
@@ -4,6 +4,8 @@ use std::collections::BinaryHeap;
|
|
4
4
|
use std::mem;
|
5
5
|
use std::cmp::{Ord, Ordering, PartialOrd, Reverse};
|
6
6
|
|
7
|
+
use std::u64::MAX as MAX64;
|
8
|
+
|
7
9
|
type Draw<T> = Result<T, FailedDraw>;
|
8
10
|
|
9
11
|
pub fn weighted(source: &mut DataSource, probability: f64) -> Result<bool, FailedDraw> {
|
@@ -11,12 +13,19 @@ pub fn weighted(source: &mut DataSource, probability: f64) -> Result<bool, Faile
|
|
11
13
|
|
12
14
|
let truthy = (probability * (u64::max_value() as f64 + 1.0)).floor() as u64;
|
13
15
|
let probe = source.bits(64)?;
|
14
|
-
|
16
|
+
Ok(match (truthy, probe) {
|
17
|
+
(0, _) => false,
|
18
|
+
(MAX64, _) => true,
|
19
|
+
(_, 0) => false,
|
20
|
+
(_, 1) => true,
|
21
|
+
_ => probe >= MAX64 - truthy,
|
22
|
+
})
|
15
23
|
}
|
16
24
|
|
17
25
|
pub fn bounded_int(source: &mut DataSource, max: u64) -> Draw<u64> {
|
18
26
|
let bitlength = 64 - max.leading_zeros() as u64;
|
19
27
|
if bitlength == 0 {
|
28
|
+
source.write(0)?;
|
20
29
|
return Ok(0);
|
21
30
|
}
|
22
31
|
loop {
|
@@ -46,30 +55,25 @@ impl Repeat {
|
|
46
55
|
}
|
47
56
|
}
|
48
57
|
|
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
58
|
pub fn reject(&mut self) {
|
62
59
|
assert!(self.current_count > 0);
|
63
60
|
self.current_count -= 1;
|
64
61
|
}
|
65
62
|
|
66
63
|
pub fn should_continue(&mut self, source: &mut DataSource) -> Result<bool, FailedDraw> {
|
67
|
-
if self.
|
68
|
-
self.
|
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)?;
|
69
73
|
self.current_count += 1;
|
70
74
|
return Ok(true);
|
71
75
|
} else if self.current_count >= self.max_count {
|
72
|
-
|
76
|
+
source.write(0)?;
|
73
77
|
return Ok(false);
|
74
78
|
}
|
75
79
|
|
data/src/engine.rs
CHANGED
@@ -13,9 +13,7 @@ use data::{DataSource, DataStream, Status, TestResult};
|
|
13
13
|
enum LoopExitReason {
|
14
14
|
Complete,
|
15
15
|
MaxExamples,
|
16
|
-
//MaxShrinks,
|
17
16
|
Shutdown,
|
18
|
-
//Error(String),
|
19
17
|
}
|
20
18
|
|
21
19
|
#[derive(Debug)]
|
@@ -108,6 +106,7 @@ struct Shrinker<'owner, Predicate> {
|
|
108
106
|
_predicate: Predicate,
|
109
107
|
shrink_target: TestResult,
|
110
108
|
changes: u64,
|
109
|
+
expensive_passes_enabled: bool,
|
111
110
|
main_loop: &'owner mut MainGenerationLoop,
|
112
111
|
}
|
113
112
|
|
@@ -126,12 +125,22 @@ where
|
|
126
125
|
_predicate: predicate,
|
127
126
|
shrink_target: shrink_target,
|
128
127
|
changes: 0,
|
128
|
+
expensive_passes_enabled: false,
|
129
129
|
}
|
130
130
|
}
|
131
131
|
|
132
132
|
fn predicate(&mut self, result: &TestResult) -> bool {
|
133
133
|
let succeeded = (self._predicate)(result);
|
134
|
-
if succeeded
|
134
|
+
if succeeded
|
135
|
+
&& (
|
136
|
+
// In the presence of writes it may be the case that we thought
|
137
|
+
// we were going to shrink this but didn't actually succeed because
|
138
|
+
// the written value was used.
|
139
|
+
result.record.len() < self.shrink_target.record.len() || (
|
140
|
+
result.record.len() == self.shrink_target.record.len() &&
|
141
|
+
result.record < self.shrink_target.record
|
142
|
+
)
|
143
|
+
) {
|
135
144
|
self.changes += 1;
|
136
145
|
self.shrink_target = result.clone();
|
137
146
|
}
|
@@ -143,15 +152,167 @@ where
|
|
143
152
|
|
144
153
|
while prev != self.changes {
|
145
154
|
prev = self.changes;
|
155
|
+
self.adaptive_delete()?;
|
146
156
|
self.binary_search_blocks()?;
|
147
|
-
self.
|
157
|
+
if prev == self.changes {
|
158
|
+
self.expensive_passes_enabled = true;
|
159
|
+
}
|
160
|
+
if !self.expensive_passes_enabled {
|
161
|
+
continue;
|
162
|
+
}
|
163
|
+
|
164
|
+
self.reorder_blocks()?;
|
165
|
+
self.lower_and_delete()?;
|
166
|
+
self.delete_all_ranges()?;
|
148
167
|
}
|
149
168
|
Ok(())
|
150
169
|
}
|
151
170
|
|
152
|
-
fn
|
153
|
-
|
154
|
-
|
171
|
+
fn lower_and_delete(&mut self) -> StepResult {
|
172
|
+
let mut i = 0;
|
173
|
+
while i < self.shrink_target.record.len() {
|
174
|
+
if self.shrink_target.record[i] > 0 {
|
175
|
+
let mut attempt = self.shrink_target.record.clone();
|
176
|
+
attempt[i] -= 1;
|
177
|
+
let (succeeded, result) = self.execute(&attempt)?;
|
178
|
+
if !succeeded && result.record.len() < self.shrink_target.record.len() {
|
179
|
+
let mut j = 0;
|
180
|
+
while j < self.shrink_target.draws.len() {
|
181
|
+
// Having to copy this is an annoying consequence of lexical lifetimes -
|
182
|
+
// if we borrowed it immutably then we'd not be allowed to call self.incorporate
|
183
|
+
// down below. Fortunately these things are tiny structs of integers so it doesn't
|
184
|
+
// really matter.
|
185
|
+
let d = self.shrink_target.draws[j].clone();
|
186
|
+
if d.start > i {
|
187
|
+
let mut attempt2 = attempt.clone();
|
188
|
+
attempt2.drain(d.start..d.end);
|
189
|
+
if self.incorporate(&attempt2)? {
|
190
|
+
break;
|
191
|
+
}
|
192
|
+
}
|
193
|
+
j += 1;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
}
|
197
|
+
i += 1;
|
198
|
+
}
|
199
|
+
Ok(())
|
200
|
+
}
|
201
|
+
|
202
|
+
fn reorder_blocks(&mut self) -> StepResult {
|
203
|
+
let mut i = 0;
|
204
|
+
while i < self.shrink_target.record.len() {
|
205
|
+
let mut j = i + 1;
|
206
|
+
while j < self.shrink_target.record.len() {
|
207
|
+
assert!(i < self.shrink_target.record.len());
|
208
|
+
if self.shrink_target.record[i] == 0 {
|
209
|
+
break;
|
210
|
+
}
|
211
|
+
if self.shrink_target.record[j] < self.shrink_target.record[i] {
|
212
|
+
let mut attempt = self.shrink_target.record.clone();
|
213
|
+
attempt.swap(i, j);
|
214
|
+
self.incorporate(&attempt)?;
|
215
|
+
}
|
216
|
+
j += 1;
|
217
|
+
}
|
218
|
+
i += 1;
|
219
|
+
}
|
220
|
+
Ok(())
|
221
|
+
}
|
222
|
+
|
223
|
+
fn try_delete_range(
|
224
|
+
&mut self,
|
225
|
+
target: &TestResult,
|
226
|
+
i: usize,
|
227
|
+
k: usize,
|
228
|
+
) -> Result<bool, LoopExitReason> {
|
229
|
+
// Attempts to delete k non-overlapping draws starting from the draw at index i.
|
230
|
+
|
231
|
+
let mut stack: Vec<(usize, usize)> = Vec::new();
|
232
|
+
let mut j = i;
|
233
|
+
while j < target.draws.len() && stack.len() < k {
|
234
|
+
let m = target.draws[j].start;
|
235
|
+
let n = target.draws[j].end;
|
236
|
+
assert!(m < n);
|
237
|
+
if m < n && (stack.len() == 0 || stack[stack.len() - 1].1 <= m) {
|
238
|
+
stack.push((m, n))
|
239
|
+
}
|
240
|
+
j += 1;
|
241
|
+
}
|
242
|
+
|
243
|
+
let mut attempt = target.record.clone();
|
244
|
+
while stack.len() > 0 {
|
245
|
+
let (m, n) = stack.pop().unwrap();
|
246
|
+
attempt.drain(m..n);
|
247
|
+
}
|
248
|
+
|
249
|
+
if attempt.len() >= self.shrink_target.record.len() {
|
250
|
+
Ok(false)
|
251
|
+
} else {
|
252
|
+
self.incorporate(&attempt)
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
fn adaptive_delete(&mut self) -> StepResult {
|
257
|
+
let mut i = 0;
|
258
|
+
let target = self.shrink_target.clone();
|
259
|
+
|
260
|
+
while i < target.draws.len() {
|
261
|
+
// This is an adaptive pass loosely modelled after timsort. If
|
262
|
+
// little or nothing is deletable here then we don't try any more
|
263
|
+
// deletions than the naive greedy algorithm would, but if it looks
|
264
|
+
// like we have an opportunity to delete a lot then we try to do so.
|
265
|
+
|
266
|
+
// What we're trying to do is to find a large k such that we can
|
267
|
+
// delete k but not k + 1 draws starting from this point, and we
|
268
|
+
// want to do that in O(log(k)) rather than O(k) test executions.
|
269
|
+
|
270
|
+
// We try a quite careful sequence of small shrinks here before we
|
271
|
+
// move on to anything big. This is because if we try to be
|
272
|
+
// aggressive too early on we'll tend to find that we lose out when
|
273
|
+
// the example is "nearly minimal".
|
274
|
+
if self.try_delete_range(&target, i, 2)? {
|
275
|
+
if self.try_delete_range(&target, i, 3)? && self.try_delete_range(&target, i, 4)? {
|
276
|
+
let mut hi = 5;
|
277
|
+
// At this point it looks like we've got a pretty good
|
278
|
+
// opportunity for a long run here. We do an exponential
|
279
|
+
// probe upwards to try and find some k where we can't
|
280
|
+
// delete many intervals. We do this rather than choosing
|
281
|
+
// that upper bound to immediately be large because we
|
282
|
+
// don't really expect k to be huge. If it turns out that
|
283
|
+
// it is, the subsequent example is going to be so tiny that
|
284
|
+
// it doesn't really matter if we waste a bit of extra time
|
285
|
+
// here.
|
286
|
+
while self.try_delete_range(&target, i, hi)? {
|
287
|
+
assert!(hi <= target.draws.len());
|
288
|
+
hi *= 2;
|
289
|
+
}
|
290
|
+
// We now know that we can delete the first lo intervals but
|
291
|
+
// not the first hi. We preserve that property while doing
|
292
|
+
// a binary search to find the point at which we stop being
|
293
|
+
// able to delete intervals.
|
294
|
+
let mut lo = 4;
|
295
|
+
while lo + 1 < hi {
|
296
|
+
let mid = lo + (hi - lo) / 2;
|
297
|
+
if self.try_delete_range(&target, i, mid)? {
|
298
|
+
lo = mid;
|
299
|
+
} else {
|
300
|
+
hi = mid;
|
301
|
+
}
|
302
|
+
}
|
303
|
+
}
|
304
|
+
} else {
|
305
|
+
self.try_delete_range(&target, i, 1)?;
|
306
|
+
}
|
307
|
+
// We unconditionally bump i because we have always tried deleting
|
308
|
+
// one more example than we succeeded at deleting, so we expect the
|
309
|
+
// next example to be undeletable.
|
310
|
+
i += 1;
|
311
|
+
}
|
312
|
+
return Ok(());
|
313
|
+
}
|
314
|
+
|
315
|
+
fn delete_all_ranges(&mut self) -> StepResult {
|
155
316
|
let mut i = 0;
|
156
317
|
while i < self.shrink_target.record.len() {
|
157
318
|
let start_length = self.shrink_target.record.len();
|
@@ -174,38 +335,45 @@ where
|
|
174
335
|
Ok(())
|
175
336
|
}
|
176
337
|
|
177
|
-
fn
|
178
|
-
|
338
|
+
fn try_lowering_value(&mut self, i: usize, v: u64) -> Result<bool, LoopExitReason> {
|
339
|
+
if v >= self.shrink_target.record[i] {
|
340
|
+
return Ok(false);
|
341
|
+
}
|
179
342
|
|
180
343
|
let mut attempt = self.shrink_target.record.clone();
|
344
|
+
attempt[i] = v;
|
345
|
+
let (succeeded, result) = self.execute(&attempt)?;
|
346
|
+
assert!(result.record.len() <= self.shrink_target.record.len());
|
347
|
+
let lost_bytes = self.shrink_target.record.len() - result.record.len();
|
348
|
+
if !succeeded && result.status == Status::Valid && lost_bytes > 0 {
|
349
|
+
attempt.drain(i + 1..i + lost_bytes + 1);
|
350
|
+
assert!(attempt.len() + lost_bytes == self.shrink_target.record.len());
|
351
|
+
self.incorporate(&attempt)
|
352
|
+
} else {
|
353
|
+
Ok(succeeded)
|
354
|
+
}
|
355
|
+
}
|
181
356
|
|
182
|
-
|
183
|
-
|
357
|
+
fn binary_search_blocks(&mut self) -> StepResult {
|
358
|
+
let mut i = 0;
|
184
359
|
|
360
|
+
while i < self.shrink_target.record.len() {
|
185
361
|
let mut hi = self.shrink_target.record[i];
|
186
362
|
|
187
|
-
if hi > 0 {
|
188
|
-
|
189
|
-
let zeroed = self.incorporate(&attempt)?;
|
363
|
+
if hi > 0 && !self.shrink_target.written_indices.contains(&i) {
|
364
|
+
let zeroed = self.try_lowering_value(i, 0)?;
|
190
365
|
if !zeroed {
|
191
366
|
let mut lo = 0;
|
192
367
|
// Binary search to find the smallest value we can
|
193
368
|
// replace this with.
|
194
369
|
while lo + 1 < hi {
|
195
370
|
let mid = lo + (hi - lo) / 2;
|
196
|
-
|
197
|
-
let succeeded = self.incorporate(&attempt)?;
|
198
|
-
if succeeded {
|
199
|
-
attempt = self.shrink_target.record.clone();
|
371
|
+
if self.try_lowering_value(i, mid)? {
|
200
372
|
hi = mid;
|
201
373
|
} else {
|
202
|
-
attempt[i] = self.shrink_target.record[i];
|
203
374
|
lo = mid;
|
204
375
|
}
|
205
376
|
}
|
206
|
-
attempt[i] = hi;
|
207
|
-
} else {
|
208
|
-
attempt = self.shrink_target.record.clone();
|
209
377
|
}
|
210
378
|
}
|
211
379
|
|
@@ -215,6 +383,12 @@ where
|
|
215
383
|
Ok(())
|
216
384
|
}
|
217
385
|
|
386
|
+
fn execute(&mut self, buf: &DataStream) -> Result<(bool, TestResult), LoopExitReason> {
|
387
|
+
// TODO: Later there will be caching here
|
388
|
+
let result = self.main_loop.execute(DataSource::from_vec(buf.clone()))?;
|
389
|
+
Ok((self.predicate(&result), result))
|
390
|
+
}
|
391
|
+
|
218
392
|
fn incorporate(&mut self, buf: &DataStream) -> Result<bool, LoopExitReason> {
|
219
393
|
assert!(
|
220
394
|
buf.len() <= self.shrink_target.record.len(),
|
@@ -229,8 +403,8 @@ where
|
|
229
403
|
if self.shrink_target.record.starts_with(buf) {
|
230
404
|
return Ok(false);
|
231
405
|
}
|
232
|
-
let
|
233
|
-
|
406
|
+
let (succeeded, _) = self.execute(buf)?;
|
407
|
+
Ok(succeeded)
|
234
408
|
}
|
235
409
|
}
|
236
410
|
|
data/src/lib.rs
CHANGED
@@ -33,6 +33,18 @@ ruby! {
|
|
33
33
|
mem::swap(&mut result.source, &mut engine.pending);
|
34
34
|
return result;
|
35
35
|
}
|
36
|
+
|
37
|
+
def start_draw(&mut self){
|
38
|
+
if let &mut Some(ref mut source) = &mut self.source {
|
39
|
+
source.start_draw();
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
def stop_draw(&mut self){
|
44
|
+
if let &mut Some(ref mut source) = &mut self.source {
|
45
|
+
source.stop_draw();
|
46
|
+
}
|
47
|
+
}
|
36
48
|
}
|
37
49
|
|
38
50
|
class HypothesisCoreEngine {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David R. Maciver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: helix_runtime
|