Skip to content

Commit

Permalink
Add implementation of list provider
Browse files Browse the repository at this point in the history
  • Loading branch information
DRMacIver committed Feb 13, 2018
1 parent dd888d7 commit 9c69be6
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/hypothesis/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def given(provider = nil, name: nil, &block)
begin
@depth += 1
provider ||= block
result = provider.provide(self)
result = provider.provide(self, &block)
if top_level
draws&.push(result)
print_log&.push([name, result.inspect])
Expand Down
37 changes: 37 additions & 0 deletions lib/hypothesis/providers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ def composite(&block)
Hypothesis::Provider::Implementations::CompositeProvider.new(block)
end

def repeated(min_count: 0, max_count: 10, average_count: 5.0)
local_provider_implementation do |source, block|
rep = HypothesisCoreRepeatValues.new(
min_count, max_count, average_count
)
block.call while rep.should_continue(source.wrapped_data)
end
end

def lists(element, min_size: 0, max_size: 10, average_size: 10)
composite do
result = []
given repeated(
min_count: min_size, max_count: max_size, average_count: average_size
) do
result.push given(element)
end
result
end
end

def integers
composite do |source|
if source.given(bits(1)).positive?
Expand Down Expand Up @@ -37,6 +58,12 @@ def from_hypothesis_core(core)
core
)
end

def local_provider_implementation(&block)
Hypothesis::Provider::Implementations::ProviderFromBlock.new(
block
)
end
end

class Provider
Expand All @@ -62,6 +89,16 @@ def provide(data)
result
end
end

class ProviderFromBlock < Provider
def initialize(block)
@block = block
end

def provide(data, &block)
@block.call(data, block)
end
end
end
end
end
26 changes: 26 additions & 0 deletions spec/provided_list_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

RSpec.describe 'shrinking' do
include Hypothesis::Debug
include Hypothesis::Providers

it 'finds a small list' do
ls, = find { given(lists(integers)).length >= 2 }
expect(ls).to eq([0, 0])
end

it 'shrinks a list to its last element' do
@original_target = nil

ls, = find do
v = given(lists(integers))

if v.length >= 5 && @original_target.nil? && v[-1] > 0
@original_target = v
end
!@original_target.nil? && v && v[-1] == @original_target[-1]
end

expect(ls.length).to eq(1)
end
end
59 changes: 59 additions & 0 deletions src/distributions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use data::{DataSource, FailedDraw};

pub fn weighted(source: &mut DataSource, probability: f64) -> Result<bool, FailedDraw> {
// TODO: Less bit-hungry implementation.

let truthy = (probability * (u64::max_value() as f64 + 1.0)).floor() as u64;
let probe = source.bits(64)?;
return Ok(probe >= u64::max_value() - truthy + 1);
}

#[derive(Debug, Clone)]
pub struct Repeat {
min_count: u64,
max_count: u64,
p_continue: f64,

current_count: u64,
}

impl Repeat {
pub fn new(min_count: u64, max_count: u64, expected_count: f64) -> Repeat {
Repeat {
min_count: min_count,
max_count: max_count,
p_continue: 1.0 - 1.0 / (1.0 + expected_count),
current_count: 0,
}
}

fn draw_until(&self, source: &mut DataSource, value: bool) -> Result<(), FailedDraw> {
// Force a draw until we get the desired outcome. By having this we get much better
// shrinking when min_size or max_size are set because all decisions are represented
// somewhere in the bit stream.
loop {
let d = weighted(source, self.p_continue)?;
if d == value {
return Ok(());
}
}
}

pub fn should_continue(&mut self, source: &mut DataSource) -> Result<bool, FailedDraw> {
let result = if self.current_count < self.min_count {
self.draw_until(source, true)?;
return Ok(true);
} else if self.current_count >= self.max_count {
self.draw_until(source, false)?;
return Ok(false);
} else {
weighted(source, self.p_continue)
};

match result {
Ok(true) => self.current_count += 1,
_ => (),
}
return result;
}
}
31 changes: 31 additions & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ impl MainGenerationLoop {

fn shrink_examples(&mut self) -> StepResult {
assert!(self.shrink_target.status == Status::Interesting);
self.binary_search_blocks()?;
self.remove_intervals()?;
Ok(())
}

fn remove_intervals(&mut self) -> StepResult {
// TODO: Actually track the data we need to make this
// not quadratic.
let mut i = 0;
while i < self.shrink_target.record.len() {
let start_length = self.shrink_target.record.len();

let mut j = i + 1;
while j < self.shrink_target.record.len() {
assert!(j > i);
let mut attempt = self.shrink_target.record.clone();
attempt.drain(i..j);
assert!(attempt.len() + (j - i) == self.shrink_target.record.len());
let deleted = self.incorporate(&attempt)?;
if !deleted {
j += 1;
}
}
if start_length == self.shrink_target.record.len() {
i += 1;
}
}
Ok(())
}

fn binary_search_blocks(&mut self) -> StepResult {
let mut i = 0;

let mut attempt = self.shrink_target.record.clone();
Expand Down
21 changes: 21 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
#![recursion_limit = "128"]
#![deny(warnings, missing_debug_implementations)]

extern crate core;
#[macro_use]
extern crate helix;
extern crate rand;

mod engine;
mod data;
mod distributions;

use std::mem;

use engine::Engine;
use data::{DataSource, Status};
use distributions::Repeat;

ruby! {
class HypothesisCoreDataSource {
Expand Down Expand Up @@ -103,6 +106,24 @@ ruby! {
}
}
}

class HypothesisCoreRepeatValues{
struct {
repeat: Repeat,
}

def initialize(helix, min_count: u64, max_count: u64, expected_count: f64){
return HypothesisCoreRepeatValues{
helix, repeat: Repeat::new(min_count, max_count, expected_count)
}
}

def should_continue(&mut self, data: &mut HypothesisCoreDataSource) -> Option<bool>{
return data.source.as_mut().and_then(|ref mut source| {
self.repeat.should_continue(source).ok()
})
}
}
}

fn mark_child_status(engine: &mut Engine, child: &mut HypothesisCoreDataSource, status: Status) {
Expand Down

0 comments on commit 9c69be6

Please sign in to comment.