hypothesis-specs 0.0.9 → 0.0.10
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/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:
|