Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add implementation of list provider #16

Merged
merged 1 commit into from
Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add implementation of list provider
  • Loading branch information
DRMacIver committed Feb 13, 2018
commit 9c69be6a859a55fd39b2dd745de76441e37cdef9
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