mirror of
https://github.com/lloeki/normandy.git
synced 2025-12-06 10:04:39 +01:00
first commit
This commit is contained in:
commit
87915c8568
9 changed files with 315 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.ruby-version
|
||||||
11
.rubocop.yml
Normal file
11
.rubocop.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
Style/Documentation:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Style/FileName:
|
||||||
|
Exclude: [lib/zipcode-fr.rb]
|
||||||
|
|
||||||
|
Style/Semicolon:
|
||||||
|
AllowAsExpressionSeparator: true
|
||||||
|
|
||||||
|
Style/TrailingComma:
|
||||||
|
EnforcedStyleForMultiline: comma
|
||||||
8
.travis.yml
Normal file
8
.travis.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
language: ruby
|
||||||
|
rvm:
|
||||||
|
- '2.1'
|
||||||
|
- '2.2'
|
||||||
|
- 'rbx-2.5'
|
||||||
|
script:
|
||||||
|
- bundle exec rubocop
|
||||||
|
- bundle exec rake test
|
||||||
3
Gemfile
Normal file
3
Gemfile
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gemspec
|
||||||
46
Gemfile.lock
Normal file
46
Gemfile.lock
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
PATH
|
||||||
|
remote: .
|
||||||
|
specs:
|
||||||
|
channel (0.1.0)
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
ast (2.0.0)
|
||||||
|
astrolabe (1.3.1)
|
||||||
|
parser (~> 2.2)
|
||||||
|
coderay (1.1.0)
|
||||||
|
method_source (0.8.2)
|
||||||
|
parser (2.2.2.6)
|
||||||
|
ast (>= 1.1, < 3.0)
|
||||||
|
power_assert (0.2.4)
|
||||||
|
powerpack (0.1.1)
|
||||||
|
pry (0.10.1)
|
||||||
|
coderay (~> 1.1.0)
|
||||||
|
method_source (~> 0.8.1)
|
||||||
|
slop (~> 3.4)
|
||||||
|
rainbow (2.0.0)
|
||||||
|
rake (10.4.2)
|
||||||
|
rubocop (0.32.1)
|
||||||
|
astrolabe (~> 1.3)
|
||||||
|
parser (>= 2.2.2.5, < 3.0)
|
||||||
|
powerpack (~> 0.1)
|
||||||
|
rainbow (>= 1.99.1, < 3.0)
|
||||||
|
ruby-progressbar (~> 1.4)
|
||||||
|
ruby-progressbar (1.7.5)
|
||||||
|
slop (3.6.0)
|
||||||
|
test-unit (3.1.3)
|
||||||
|
power_assert
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
channel!
|
||||||
|
pry
|
||||||
|
rake
|
||||||
|
rubocop
|
||||||
|
test-unit
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.10.6
|
||||||
10
Rakefile
Normal file
10
Rakefile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
require 'rake/testtask'
|
||||||
|
require 'bundler'
|
||||||
|
|
||||||
|
Bundler::GemHelper.install_tasks
|
||||||
|
|
||||||
|
Rake::TestTask.new do |t|
|
||||||
|
t.libs << 'test'
|
||||||
|
t.test_files = FileList['test/test_*.rb']
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
16
channel.gemspec
Normal file
16
channel.gemspec
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = 'channel'
|
||||||
|
s.version = '0.1.0'
|
||||||
|
s.licenses = ['MIT']
|
||||||
|
s.summary = 'Channels'
|
||||||
|
s.description = 'Share memory by communicating'
|
||||||
|
s.authors = ['Loic Nageleisen']
|
||||||
|
s.email = 'loic.nageleisen@gmail.com'
|
||||||
|
s.files = ['lib/channel.rb']
|
||||||
|
s.homepage = 'https://github.com/lloeki/channel'
|
||||||
|
|
||||||
|
s.add_development_dependency 'pry'
|
||||||
|
s.add_development_dependency 'rubocop'
|
||||||
|
s.add_development_dependency 'rake'
|
||||||
|
s.add_development_dependency 'test-unit'
|
||||||
|
end
|
||||||
90
lib/channel.rb
Normal file
90
lib/channel.rb
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
require 'thread'
|
||||||
|
|
||||||
|
module Kernel
|
||||||
|
def go(prc, *args)
|
||||||
|
Thread.new { prc.call(*args) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Channel
|
||||||
|
class Closed < StandardError; end
|
||||||
|
|
||||||
|
def initialize(size = nil)
|
||||||
|
@q = size ? SizedQueue.new(size) : Queue.new
|
||||||
|
@closed = false
|
||||||
|
@mutex = Mutex.new
|
||||||
|
@waiting = []
|
||||||
|
end
|
||||||
|
|
||||||
|
private def lock!(&block)
|
||||||
|
@mutex.synchronize(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def wait!
|
||||||
|
@waiting << Thread.current
|
||||||
|
@mutex.sleep
|
||||||
|
end
|
||||||
|
|
||||||
|
private def next!
|
||||||
|
loop do
|
||||||
|
thr = @waiting.shift
|
||||||
|
break if thr.nil?
|
||||||
|
next unless thr.alive?
|
||||||
|
break thr.wakeup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def all!
|
||||||
|
@waiting.dup.each { next! }
|
||||||
|
end
|
||||||
|
|
||||||
|
def recv
|
||||||
|
lock! do
|
||||||
|
loop do
|
||||||
|
closed! if closed?
|
||||||
|
wait! && next if @q.empty?
|
||||||
|
break @q.pop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias_method :pop, :recv
|
||||||
|
|
||||||
|
def send(val)
|
||||||
|
lock! do
|
||||||
|
fail Closed if closed?
|
||||||
|
@q << val
|
||||||
|
next!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias_method :push, :send
|
||||||
|
alias_method :<<, :push
|
||||||
|
|
||||||
|
def close
|
||||||
|
lock! do
|
||||||
|
return if closed?
|
||||||
|
@closed = true
|
||||||
|
all!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def closed?
|
||||||
|
@closed
|
||||||
|
end
|
||||||
|
|
||||||
|
private def closed!
|
||||||
|
fail Closed
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def select(*channels)
|
||||||
|
selector = new
|
||||||
|
threads = channels.map do |c|
|
||||||
|
Thread.new { selector << [c.recv, c] }
|
||||||
|
end
|
||||||
|
yield selector.recv
|
||||||
|
ensure
|
||||||
|
selector.close
|
||||||
|
threads.each(&:kill).each(&:join)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
130
test/test_channel.rb
Normal file
130
test/test_channel.rb
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
require 'test/unit'
|
||||||
|
require 'thread'
|
||||||
|
require 'channel'
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
# rubocop:disable Metrics/MethodLength
|
||||||
|
|
||||||
|
class TestChannel < Test::Unit::TestCase
|
||||||
|
module Util
|
||||||
|
def meanwhile(*procs, &blk)
|
||||||
|
threads = procs.map { |p| Thread.new(&p) }
|
||||||
|
blk.call
|
||||||
|
threads.each(&:join)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Assert
|
||||||
|
def assert_raise_with_message(exc, msg, &block)
|
||||||
|
e = assert_raise(exc, &block)
|
||||||
|
assert_match(msg, e.message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
include Util
|
||||||
|
include Assert
|
||||||
|
|
||||||
|
def test_channel
|
||||||
|
c = Channel.new
|
||||||
|
result = nil
|
||||||
|
meanwhile(-> { result = c.recv }) do
|
||||||
|
c << 'foo'
|
||||||
|
end
|
||||||
|
assert_equal('foo', result)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_closed_channel
|
||||||
|
c = Channel.new
|
||||||
|
c.close
|
||||||
|
assert_equal(true, c.closed?)
|
||||||
|
assert_raise(Channel::Closed) { c.recv }
|
||||||
|
end
|
||||||
|
|
||||||
|
# def test_fail_send_to_unbuffered_channel
|
||||||
|
# c = Channel.new
|
||||||
|
# assert_raise_with_message(ThreadError, /No live threads left/) do
|
||||||
|
# c.send 'foo'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
def test_send_to_unbuffered_channel
|
||||||
|
c = Channel.new
|
||||||
|
go -> { assert_equal('foo', c.recv) }
|
||||||
|
c.send 'foo'
|
||||||
|
end
|
||||||
|
|
||||||
|
# def test_fill_buffered_channel
|
||||||
|
# c = Channel.new(1)
|
||||||
|
# c.send 'foo'
|
||||||
|
# assert_raise_with_message('ThreadError', /No live threads left/) do
|
||||||
|
# c.send 'foo'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
def test_single_thread_send_to_buffered_channel
|
||||||
|
c = Channel.new(1)
|
||||||
|
c.send 'foo'
|
||||||
|
assert_equal('foo', c.recv)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_send_on_closed_channel
|
||||||
|
c = Channel.new
|
||||||
|
c.close
|
||||||
|
assert_raise(Channel::Closed) { c << 'foo' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_close_blocking_channel
|
||||||
|
c = Channel.new
|
||||||
|
meanwhile(-> { assert_raise(Channel::Closed) { c.recv } }) do
|
||||||
|
sleep(0.1)
|
||||||
|
c.close
|
||||||
|
end
|
||||||
|
assert_equal(true, c.closed?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_close_blocking_channels
|
||||||
|
c = Channel.new
|
||||||
|
meanwhile(
|
||||||
|
-> { assert_raise(Channel::Closed) { c.recv } },
|
||||||
|
-> { assert_raise(Channel::Closed) { c.recv } },
|
||||||
|
-> { assert_raise(Channel::Closed) { c.recv } },
|
||||||
|
) do
|
||||||
|
sleep(0.1)
|
||||||
|
c.close
|
||||||
|
end
|
||||||
|
assert_equal(true, c.closed?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_select
|
||||||
|
c1 = Channel.new
|
||||||
|
c2 = Channel.new
|
||||||
|
c3 = Channel.new
|
||||||
|
c4 = Channel.new
|
||||||
|
|
||||||
|
go -> { sleep(0.1); c1 << '1' }
|
||||||
|
go -> { sleep(0.2); c2 << '2' }
|
||||||
|
go -> { sleep(0.3); c3 << '3' }
|
||||||
|
go -> { sleep(0.4); c4 << '4' }
|
||||||
|
|
||||||
|
4.times do
|
||||||
|
Channel.select(c1, c2, c3, c4) do |msg, c|
|
||||||
|
case c
|
||||||
|
when c1 then assert_equal('1', msg)
|
||||||
|
when c2 then assert_equal('2', msg)
|
||||||
|
when c3 then assert_equal('3', msg)
|
||||||
|
when c4 then assert_equal('4', msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_buffered_channel
|
||||||
|
messages = Channel.new(2)
|
||||||
|
|
||||||
|
messages << 'buffered'
|
||||||
|
messages << 'channel'
|
||||||
|
|
||||||
|
assert_equal('buffered', messages.recv)
|
||||||
|
assert_equal('channel', messages.recv)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue