hypothesis-specs 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Rakefile +5 -1
- data/src/data.rs +6 -0
- data/src/distributions.rs +66 -11
- data/src/engine.rs +66 -20
- data/src/intminimize.rs +149 -0
- data/src/lib.rs +3 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e49a69e84d999619c8b73873bc8dc92f4e9e534
|
4
|
+
data.tar.gz: d51bc24f3c230ec37a4a19ddcbd43ca5532646ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fc1e55701ebf1b24184f243ee232eb4528d85d6306fbe83e95bfd37a0f6f1328077181496620214f3674a2e3f8063bd628178539cbf3eca5af28a7dea86e1a1
|
7
|
+
data.tar.gz: 87772f4660cdef5a8a93df5eb81d2322896c58536d3aff5f2ea9a93ec973b83c0ee99c26b4e08467a91af7948dc46ec73a556cbdc3af3a725f075bcdd2b1e148
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## Hypothesis for Ruby 0.0.10 (2018-04-26)
|
2
|
+
|
3
|
+
This release is another update to shrinking:
|
4
|
+
|
5
|
+
* Cases where the value may be simplified without necessarily
|
6
|
+
becoming smaller will have better results.
|
7
|
+
* Duplicated values can now sometimes be simultaneously shrunk.
|
8
|
+
|
1
9
|
## Hypothesis for Ruby 0.0.9 (2018-04-20)
|
2
10
|
|
3
11
|
This improves Hypothesis for Ruby's shrinking to be much closer
|
data/Rakefile
CHANGED
data/src/data.rs
CHANGED
@@ -39,6 +39,7 @@ pub struct Draw {
|
|
39
39
|
pub struct DataSource {
|
40
40
|
bitgenerator: BitGenerator,
|
41
41
|
record: DataStream,
|
42
|
+
sizes: Vec<u64>,
|
42
43
|
draws: Vec<DrawInProgress>,
|
43
44
|
draw_stack: Vec<usize>,
|
44
45
|
written_indices: HashSet<usize>,
|
@@ -49,6 +50,7 @@ impl DataSource {
|
|
49
50
|
return DataSource {
|
50
51
|
bitgenerator: generator,
|
51
52
|
record: DataStream::new(),
|
53
|
+
sizes: Vec::new(),
|
52
54
|
draws: Vec::new(),
|
53
55
|
draw_stack: Vec::new(),
|
54
56
|
written_indices: HashSet::new(),
|
@@ -88,6 +90,7 @@ impl DataSource {
|
|
88
90
|
match self.bitgenerator {
|
89
91
|
BitGenerator::Recorded(ref mut v) if self.record.len() >= v.len() => Err(FailedDraw),
|
90
92
|
_ => {
|
93
|
+
self.sizes.push(0);
|
91
94
|
self.record.push(value);
|
92
95
|
Ok(())
|
93
96
|
}
|
@@ -95,6 +98,7 @@ impl DataSource {
|
|
95
98
|
}
|
96
99
|
|
97
100
|
pub fn bits(&mut self, n_bits: u64) -> Result<u64, FailedDraw> {
|
101
|
+
self.sizes.push(n_bits);
|
98
102
|
let mut result = match self.bitgenerator {
|
99
103
|
BitGenerator::Random(ref mut random) => random.next_u64(),
|
100
104
|
BitGenerator::Recorded(ref mut v) => if self.record.len() >= v.len() {
|
@@ -119,6 +123,7 @@ impl DataSource {
|
|
119
123
|
record: self.record,
|
120
124
|
status: status,
|
121
125
|
written_indices: self.written_indices,
|
126
|
+
sizes: self.sizes,
|
122
127
|
draws: self.draws
|
123
128
|
.drain(..)
|
124
129
|
.filter_map(|d| match d {
|
@@ -174,5 +179,6 @@ pub struct TestResult {
|
|
174
179
|
pub record: DataStream,
|
175
180
|
pub status: Status,
|
176
181
|
pub draws: Vec<Draw>,
|
182
|
+
pub sizes: Vec<u64>,
|
177
183
|
pub written_indices: HashSet<usize>,
|
178
184
|
}
|
data/src/distributions.rs
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
use data::{DataSource, FailedDraw};
|
2
2
|
|
3
|
+
use std::cmp::{Ord, Ordering, PartialOrd, Reverse};
|
3
4
|
use std::collections::BinaryHeap;
|
4
5
|
use std::mem;
|
5
|
-
use std::cmp::{Ord, Ordering, PartialOrd, Reverse};
|
6
6
|
|
7
7
|
use std::u64::MAX as MAX64;
|
8
8
|
|
@@ -216,16 +216,71 @@ impl Sampler {
|
|
216
216
|
}
|
217
217
|
|
218
218
|
pub fn good_bitlengths() -> Sampler {
|
219
|
-
let weights = vec!
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
+
];
|
229
284
|
assert!(weights.len() == 63);
|
230
285
|
Sampler::new(weights)
|
231
286
|
}
|
data/src/engine.rs
CHANGED
@@ -3,11 +3,14 @@
|
|
3
3
|
|
4
4
|
use rand::{ChaChaRng, Rng, SeedableRng};
|
5
5
|
|
6
|
+
use std::cmp::Reverse;
|
7
|
+
use std::collections::HashMap;
|
8
|
+
use std::mem;
|
6
9
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
7
10
|
use std::thread;
|
8
|
-
use std::mem;
|
9
11
|
|
10
12
|
use data::{DataSource, DataStream, Status, TestResult};
|
13
|
+
use intminimize::minimize_integer;
|
11
14
|
|
12
15
|
#[derive(Debug, Clone)]
|
13
16
|
enum LoopExitReason {
|
@@ -153,7 +156,8 @@ where
|
|
153
156
|
while prev != self.changes {
|
154
157
|
prev = self.changes;
|
155
158
|
self.adaptive_delete()?;
|
156
|
-
self.
|
159
|
+
self.minimize_individual_blocks()?;
|
160
|
+
self.minimize_duplicated_blocks()?;
|
157
161
|
if prev == self.changes {
|
158
162
|
self.expensive_passes_enabled = true;
|
159
163
|
}
|
@@ -354,27 +358,14 @@ where
|
|
354
358
|
}
|
355
359
|
}
|
356
360
|
|
357
|
-
fn
|
361
|
+
fn minimize_individual_blocks(&mut self) -> StepResult {
|
358
362
|
let mut i = 0;
|
359
363
|
|
360
364
|
while i < self.shrink_target.record.len() {
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
if !zeroed {
|
366
|
-
let mut lo = 0;
|
367
|
-
// Binary search to find the smallest value we can
|
368
|
-
// replace this with.
|
369
|
-
while lo + 1 < hi {
|
370
|
-
let mid = lo + (hi - lo) / 2;
|
371
|
-
if self.try_lowering_value(i, mid)? {
|
372
|
-
hi = mid;
|
373
|
-
} else {
|
374
|
-
lo = mid;
|
375
|
-
}
|
376
|
-
}
|
377
|
-
}
|
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
|
+
})?;
|
378
369
|
}
|
379
370
|
|
380
371
|
i += 1;
|
@@ -383,6 +374,61 @@ where
|
|
383
374
|
Ok(())
|
384
375
|
}
|
385
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
|
+
|
386
432
|
fn execute(&mut self, buf: &DataStream) -> Result<(bool, TestResult), LoopExitReason> {
|
387
433
|
// TODO: Later there will be caching here
|
388
434
|
let result = self.main_loop.execute(DataSource::from_vec(buf.clone()))?;
|
data/src/intminimize.rs
ADDED
@@ -0,0 +1,149 @@
|
|
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
|
+
}
|
data/src/lib.rs
CHANGED
@@ -12,15 +12,16 @@ extern crate core;
|
|
12
12
|
extern crate helix;
|
13
13
|
extern crate rand;
|
14
14
|
|
15
|
-
mod engine;
|
16
15
|
mod data;
|
17
16
|
mod distributions;
|
17
|
+
mod engine;
|
18
|
+
mod intminimize;
|
18
19
|
|
19
20
|
use std::mem;
|
20
21
|
|
21
|
-
use engine::Engine;
|
22
22
|
use data::{DataSource, Status};
|
23
23
|
use distributions::Repeat;
|
24
|
+
use engine::Engine;
|
24
25
|
|
25
26
|
ruby! {
|
26
27
|
class HypothesisCoreDataSource {
|
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.10
|
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-04-
|
11
|
+
date: 2018-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: helix_runtime
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- src/data.rs
|
71
71
|
- src/distributions.rs
|
72
72
|
- src/engine.rs
|
73
|
+
- src/intminimize.rs
|
73
74
|
- src/lib.rs
|
74
75
|
homepage: http://github.com/HypothesisWorks/hypothesis-ruby
|
75
76
|
licenses:
|