Skip to content

Commit

Permalink
[maint] extract celluloid sieve version for test overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
AJ Christensen committed Aug 17, 2012
0 parents commit fc9ee6a
Show file tree
Hide file tree
Showing 16 changed files with 253 additions and 0 deletions.
1 change: 1 addition & 0 deletions .rbenv-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jruby-1.6.7.2
9 changes: 9 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
source :rubygems

gem "celluloid"

group :test do
gem "rake"
gem "guard"
gem "guard-minitest"
end
34 changes: 34 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
GEM
remote: http://rubygems.org/
specs:
celluloid (0.11.1)
timers (>= 1.0.0)
ffi (1.1.5)
ffi (1.1.5-java)
guard (1.3.2)
listen (>= 0.4.2)
thor (>= 0.14.6)
guard-minitest (0.5.0)
guard (>= 0.4)
listen (0.4.7)
rb-fchange (~> 0.0.5)
rb-fsevent (~> 0.9.1)
rb-inotify (~> 0.8.8)
rake (0.8.7)
rb-fchange (0.0.5)
ffi
rb-fsevent (0.9.1)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
thor (0.16.0)
timers (1.0.1)

PLATFORMS
java
ruby

DEPENDENCIES
celluloid
guard
guard-minitest
rake
9 changes: 9 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'minitest' do
watch(%r|^test/(.*)\/?test_(.*)\.rb|)
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
watch(%r|^test/test_helper\.rb|) { "test" }
end
10 changes: 10 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'rake/testtask'

Rake::TestTask.new do |t|
t.libs = ["lib"]
t.warning = true
t.verbose = true
t.test_files = FileList['test/**/*_test.rb']
end

task :default => :test
5 changes: 5 additions & 0 deletions lib/sieve.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'bundler/setup'

module Sieve
require 'sieve/version'
end
22 changes: 22 additions & 0 deletions lib/sieve/candidates.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Sieve
# Enumerable implementation representing the set of prime number candidates > 10. Use of an
# enumerable here allows us to isolate the state associated with candidate selection to this
# class, freeing up the model and controller actors to focus on other parts of the computation
class Candidates
include Enumerable

def initialize
# Note that this initial value is never actually returned; we're only setting the stage for the
# first increment to generate the first candidate
@next = 9
end

# Primes must be a number ending in 1, 3, 7 or 9... a bit of reflection will make it clear why
def each
loop do
@next += (@next % 10 == 3) ? 4 : 2
yield @next
end
end
end
end
41 changes: 41 additions & 0 deletions lib/sieve/controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require 'celluloid'
require_relative "candidates"

module Sieve
class Controller
include Celluloid

def initialize
@models = 0.upto(3).map { |i| Model.new }

# Seed models with a few initial values... just to get things going
@seeds = [2,3,5,7]
0.upto(3).each { |idx| @models[idx].add! @seeds[idx] }

@candidates = Candidates.new
end

# Revert back to the old future-based semantics here. This function has to support both
# synchronous and async execution so it always has to return an answer when called.
def next
# If we still have seeds to return do so up front
seed = @seeds.shift
if seed
return seed
end

# Otherwise loop through candidates and build a collection of futures (one for each model) for each
# of those candidates. The first value that returns all true values wins!
nextprime = @candidates.find do |candidate|
@models.map { |m| m.future :is_prime,candidate }.all? { |f| f.value }
end

# We found our next prime so update one of the models...
@models[(rand @models.size)].add! nextprime

# ... and return
nextprime
end
end

end
31 changes: 31 additions & 0 deletions lib/sieve/model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Sieve
# A model represents a fragment of the state of our sieve, specifically some subset
# of the primes discovered so far.
#
# In the Celluloid model there is no one unified entry point for message handling. Each handled
# message type is now implemented as a distinct method that can be called synchronously or
# asynchronously. Since method calls are logically equivalent to messages (see Smalltalk)
# this should work reasonably well.
class Model
include Celluloid

def initialize
@primes = []
end

def add newprime
@primes << newprime
end

def is_prime candidate
# Upfront validation; make sure we have some primes
if @primes.empty?
return nil
end

# The model only considers a value prime if it doesn't equal or divide evenly into any previously
# observed prime.
@primes.none? { |prime| candidate != prime and candidate % prime == 0 }
end
end
end
17 changes: 17 additions & 0 deletions lib/sieve/primes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require_relative "controller"

module Sieve
class Primes
include Enumerable

def initialize
@controller = Controller.new
end

def each
loop do
yield @controller.next
end
end
end
end
3 changes: 3 additions & 0 deletions lib/sieve/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Sieve
VERSION = "0.0.1"
end
1 change: 1 addition & 0 deletions test/sieve/candidates_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'minitest/autorun'
2 changes: 2 additions & 0 deletions test/sieve/controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require 'minitest/autorun'
require 'sieve/controller'
48 changes: 48 additions & 0 deletions test/sieve/model_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require 'minitest/autorun'

require 'sieve/model'

class ModelTest < MiniTest::Unit::TestCase

# Empty models shouldn't match anything
def test_model_empty

model = Sieve::Model.new

1.upto(10).each { |val| assert_equal(model.is_prime(val),nil) }
end

# Verify that we can match some data after adding it
def test_model_add

model = Sieve::Model.new

seeds = [2,3,5,7]

# Add in a few known primes
seeds.each { |seed| model.add seed }

# Verify that the values themselves show up as prime
seeds.each { |seed| assert(model.is_prime(seed),"Seed #{seed} failed") }

# Verify that multiples of the known primes are NOT marked as primes
seeds.each do |seed|
2.upto(100) do |multiplier|
testval = seed * multiplier
resp = model.is_prime testval
assert(!resp.nil?,"Multiplier #{multiplier} for seed #{seed} failed unexpectedly")
assert(!resp,"Multiplier #{multiplier} for seed #{seed} unexpectedly indicated to be prime")
end
end

# Verify that integers which aren't multiples of these primes aren't marked
# as primes
10.upto(1000) do |c|

resp = model.is_prime c
assert(!resp.nil?,"Candidate #{c} failed unexpectedly")
assert(resp,"Candidate #{c} should be prime based on seeds but returned false") if seeds.all? { |seed| c % seed != 0 }
assert(!resp,"Candidate #{c} should not be prime based on seeds but returned true") if seeds.any? { |seed| c % seed == 0 }
end
end
end
18 changes: 18 additions & 0 deletions test/sieve/primes_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'minitest/autorun'
require 'prime'
require 'sieve/primes'

class PrimesTest < MiniTest::Unit::TestCase

# Verify that we can at least obtain the seeded values from the controller
def test_controller_seeds_only
assert_equal([2,3,5,7],Sieve::Primes.new.take(4))
end

# Now verify that the controller correctly computes new primes. Don't forget
# to skip past the seeded values!
def test_controller_computed_primes

assert_equal(Sieve::Primes.new.take(104)[4,-1], Prime.each.take(104)[4,-1])
end
end
2 changes: 2 additions & 0 deletions test/sieve/version_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require 'minitest/autorun'

0 comments on commit fc9ee6a

Please sign in to comment.