golly-utils 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +8 -0
- data/.simplecov +14 -0
- data/.travis.yml +5 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +12 -0
- data/Gemfile.corvid +27 -0
- data/Gemfile.lock +86 -0
- data/Guardfile +44 -0
- data/README.md +3 -0
- data/RELEASE.md +13 -0
- data/Rakefile +6 -0
- data/bin/guard +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/bin/yard +16 -0
- data/bin/yardoc +16 -0
- data/bin/yri +16 -0
- data/golly-utils.gemspec +17 -0
- data/lib/golly-utils/attr_declarative.rb +74 -0
- data/lib/golly-utils/callbacks.rb +92 -0
- data/lib/golly-utils/child_process.rb +124 -0
- data/lib/golly-utils/colourer.rb +50 -0
- data/lib/golly-utils/delegator.rb +81 -0
- data/lib/golly-utils/multi_io.rb +13 -0
- data/lib/golly-utils/ruby_ext.rb +2 -0
- data/lib/golly-utils/ruby_ext/array_to_hash.rb +29 -0
- data/lib/golly-utils/ruby_ext/deep_dup.rb +33 -0
- data/lib/golly-utils/ruby_ext/env_helpers.rb +27 -0
- data/lib/golly-utils/ruby_ext/hash_combinations.rb +49 -0
- data/lib/golly-utils/ruby_ext/pretty_error_messages.rb +9 -0
- data/lib/golly-utils/ruby_ext/subclasses.rb +17 -0
- data/lib/golly-utils/test/spec/deferrable_specs.rb +85 -0
- data/lib/golly-utils/test/spec/within_time.rb +56 -0
- data/lib/golly-utils/version.rb +3 -0
- data/test/bootstrap/all.rb +4 -0
- data/test/bootstrap/spec.rb +5 -0
- data/test/bootstrap/unit.rb +4 -0
- data/test/spec/child_process_mock_target.rb +28 -0
- data/test/spec/child_process_spec.rb +41 -0
- data/test/unit/attr_declarative_test.rb +54 -0
- data/test/unit/callbacks_test.rb +76 -0
- data/test/unit/delegator_test.rb +99 -0
- data/test/unit/multi_io_test.rb +18 -0
- data/test/unit/ruby_ext/env_helpers_test.rb +48 -0
- data/test/unit/ruby_ext/hash_combinations_test.rb +31 -0
- data/test/unit/ruby_ext/subclasses_test.rb +24 -0
- metadata +107 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Given a hash where each value can be a single value or an array of potential values, this generate a hash for each
|
4
|
+
# combination of values across all keys.
|
5
|
+
#
|
6
|
+
# @return [Array<Hash>]
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # Here key :a has 3 possible values, :b has 2, and :c and :d have only 1 each.
|
10
|
+
# x = {a:%w[x y z], b:[1,2], c:'true', d:nil}
|
11
|
+
#
|
12
|
+
# x.value_combinations
|
13
|
+
# # will return:
|
14
|
+
#
|
15
|
+
# [
|
16
|
+
# {a:'x', b:1, c:'true', d:nil},
|
17
|
+
# {a:'y', b:1, c:'true', d:nil},
|
18
|
+
# {a:'z', b:1, c:'true', d:nil},
|
19
|
+
# {a:'x', b:2, c:'true', d:nil},
|
20
|
+
# {a:'y', b:2, c:'true', d:nil},
|
21
|
+
# {a:'z', b:2, c:'true', d:nil},
|
22
|
+
# ]
|
23
|
+
def value_combinations
|
24
|
+
collect_value_combinations [], {}, keys
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def collect_value_combinations(results, tgt, remaining_keys)
|
30
|
+
if remaining_keys.empty?
|
31
|
+
results<< tgt
|
32
|
+
else
|
33
|
+
next_set_of_keys= remaining_keys[1..-1]
|
34
|
+
k= remaining_keys[0]
|
35
|
+
v= self[k]
|
36
|
+
if v.kind_of?(Enumerable)
|
37
|
+
v.each_entry {|v2|
|
38
|
+
tgt2= tgt.dup
|
39
|
+
tgt2[k]= v2
|
40
|
+
collect_value_combinations results, tgt2, next_set_of_keys
|
41
|
+
}
|
42
|
+
else
|
43
|
+
tgt[k]= v
|
44
|
+
collect_value_combinations results, tgt, next_set_of_keys
|
45
|
+
end
|
46
|
+
end
|
47
|
+
results
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Class
|
2
|
+
def inherited other
|
3
|
+
super if defined? super
|
4
|
+
ensure
|
5
|
+
( @subclasses ||= [] ).push(other).uniq!
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns a list of classes that extend this class, directly or indirectly (as in subclasses of subclasses).
|
9
|
+
#
|
10
|
+
# @return [Array<Class>]
|
11
|
+
def subclasses(include_subclassed_nodes = false)
|
12
|
+
@subclasses ||= []
|
13
|
+
classes= @subclasses.inject( [] ) {|list, subclass| list.push subclass, *subclass.subclasses }
|
14
|
+
classes.reject! {|c| classes.any?{|i| c != i and c.subclasses.include?(i) }} unless include_subclassed_nodes
|
15
|
+
classes
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module GollyUtils
|
2
|
+
|
3
|
+
# With this module you can create specs that don't start running until manually started elsewhere in another spec.
|
4
|
+
#
|
5
|
+
# This is only really useful when writing integration tests in spec format, where specs (aka examples) are not
|
6
|
+
# isolated tests but single components of a larger-scale test. This is especially the case when checking
|
7
|
+
# asynchronous events, or managing dependencies on state of an external entity.
|
8
|
+
#
|
9
|
+
# ### Usage:
|
10
|
+
# * Extend `GollyUtils::DeferrableSpecs`
|
11
|
+
# * Create specs/examples using `deferrable_spec`
|
12
|
+
# * In other specs/examples, call `start_deferred_test` to start deferred tests.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# describe 'Integration test #2' do
|
16
|
+
# extend GollyUtils::DeferrableSpecs
|
17
|
+
#
|
18
|
+
# it("Register client, notices and default prefs") do
|
19
|
+
# scenario.register_client
|
20
|
+
# scenario.register_notification_groups
|
21
|
+
# scenario.register_notices
|
22
|
+
# scenario.set_default_preferences
|
23
|
+
#
|
24
|
+
# start_deferred_test :email1
|
25
|
+
# start_deferred_test :email2
|
26
|
+
# start_deferred_test :mq
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # Deferred until registration of client, notices and preferences are complete
|
30
|
+
# deferrable_spec(:email1, "Sends emails (to unregistered contacts)") do
|
31
|
+
# assert_sends_email ...
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# ...
|
35
|
+
#
|
36
|
+
# end
|
37
|
+
module DeferrableSpecs
|
38
|
+
def self.extended(base)
|
39
|
+
base.send :include, InstanceMethods
|
40
|
+
base.extend ClassMethods
|
41
|
+
base.instance_eval{ @deferrable_specs= {} }
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
attr_reader :deferrable_specs
|
46
|
+
|
47
|
+
def deferrable_spec(key, name, &block)
|
48
|
+
deferrable_specs[key]= {block: block}
|
49
|
+
class_eval <<-EOB
|
50
|
+
it(#{name.inspect}){ deferred_join #{key.inspect} }
|
51
|
+
EOB
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
module InstanceMethods
|
57
|
+
|
58
|
+
def start_deferred_tests(first_key,*other_keys)
|
59
|
+
([first_key]+other_keys).flatten.uniq.each do |key|
|
60
|
+
raise "Unknown defferable test: #{key}" unless d= self.class.deferrable_specs[key]
|
61
|
+
raise "Test already started: #{key}" if d[:thread]
|
62
|
+
raise "Test block missing." unless b= d[:block]
|
63
|
+
s= self.dup
|
64
|
+
d[:thread]= Thread.new{ s.instance_eval &b }
|
65
|
+
end
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
alias start_deferred_test start_deferred_tests
|
70
|
+
|
71
|
+
def deferred_join(key)
|
72
|
+
raise "Unknown defferable test: #{key}" unless d= self.class.deferrable_specs[key]
|
73
|
+
if t= d[:thread]
|
74
|
+
t.join
|
75
|
+
else
|
76
|
+
#raise "Test hasn't started: #{key}"
|
77
|
+
warn "Deferrable spec #{key.inspect} wasn't deferred. Start it elsewhere with start_deferred_test #{key.inspect}"
|
78
|
+
raise "Test block missing." unless b= d[:block]
|
79
|
+
b.call
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module TestHelpers
|
2
|
+
|
3
|
+
# @example
|
4
|
+
# within(5).seconds{ File.exists?(f).should == true }
|
5
|
+
# @see WithinTime
|
6
|
+
def within(timeout)
|
7
|
+
WithinTime.new(timeout)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Re-runs a given block until it:
|
11
|
+
#
|
12
|
+
# * indicates success by returning `true`
|
13
|
+
# * fails by not returning `true` within a given time period.
|
14
|
+
#
|
15
|
+
# @see #within
|
16
|
+
class WithinTime
|
17
|
+
def initialize(timeout, sleep_time=0.1)
|
18
|
+
timeout= timeout.to_f
|
19
|
+
raise unless timeout > 0
|
20
|
+
@timeout= timeout
|
21
|
+
|
22
|
+
sleep_time= sleep_time.to_f
|
23
|
+
raise unless sleep_time > 0
|
24
|
+
@sleep_time= sleep_time
|
25
|
+
end
|
26
|
+
def ms (&block) run 0.001, block end
|
27
|
+
def seconds(&block) run 1 , block end
|
28
|
+
def minutes(&block) run 60 , block end
|
29
|
+
def hours (&block) run 3600 , block end
|
30
|
+
alias :msec :ms
|
31
|
+
alias :second :seconds
|
32
|
+
alias :sec :seconds
|
33
|
+
alias :minute :minutes
|
34
|
+
alias :min :minutes
|
35
|
+
alias :hour :hours
|
36
|
+
alias :hr :hours
|
37
|
+
alias :hrs :hours
|
38
|
+
protected
|
39
|
+
def run(factor_to_sec, block)
|
40
|
+
timeout= @timeout * factor_to_sec
|
41
|
+
sleep_time= @sleep_time * factor_to_sec
|
42
|
+
start= Time.now
|
43
|
+
while true
|
44
|
+
|
45
|
+
begin
|
46
|
+
block.call
|
47
|
+
return true
|
48
|
+
rescue RSpec::Expectations::ExpectationNotMetError => err
|
49
|
+
sleep sleep_time
|
50
|
+
raise err if (Time.now - start) > timeout
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
raise 'no bro' unless ARGV.count == 1
|
4
|
+
|
5
|
+
file= ARGV[0]
|
6
|
+
raise "#{file} already exists." if File.exists?(file)
|
7
|
+
$file= file
|
8
|
+
|
9
|
+
#puts "Starting..."
|
10
|
+
#puts "Delete #{file} to stop."
|
11
|
+
#while true
|
12
|
+
# File.open(file,"a") {|fout| fout<< '.'}
|
13
|
+
# sleep 0.2
|
14
|
+
# break if !File.exists?(file)
|
15
|
+
#end
|
16
|
+
#
|
17
|
+
#puts "File deleted by external process. Shutting down."
|
18
|
+
|
19
|
+
at_exit { bye }
|
20
|
+
#Kernel.trap('INT') { bye }
|
21
|
+
def bye
|
22
|
+
puts "Bye!"
|
23
|
+
File.unlink $file if $file
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "Starting..."
|
27
|
+
File.write(file,"a")
|
28
|
+
while sleep 0.1; end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require_relative '../bootstrap/spec'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'golly-utils/child_process'
|
5
|
+
require 'golly-utils/test/spec/within_time'
|
6
|
+
|
7
|
+
describe GollyUtils::ChildProcess do
|
8
|
+
after :each do
|
9
|
+
cp.shutdown
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:cp){
|
13
|
+
described_class.new \
|
14
|
+
quiet: true,
|
15
|
+
start_command: "#{File.expand_path '../child_process_mock_target.rb', __FILE__} #{tmpfile}"
|
16
|
+
}
|
17
|
+
let(:tmpfile){
|
18
|
+
"#{Dir.tmpdir}/live_tester-#{Time.now.strftime '%Y%m%d%H%M%S%L'}-#{$$}.tmp"
|
19
|
+
}
|
20
|
+
|
21
|
+
it("shall start and stop server processes"){
|
22
|
+
cp.startup
|
23
|
+
within(2).seconds{ File.exists?(tmpfile).should == true }
|
24
|
+
cp.alive?.should == true
|
25
|
+
cp.shutdown.should == true
|
26
|
+
within(2).seconds{ File.exists?(tmpfile).should == false }
|
27
|
+
cp.alive?.should == false
|
28
|
+
}
|
29
|
+
|
30
|
+
it("shall do nothing when startup called and already started"){
|
31
|
+
cp.startup
|
32
|
+
(pid= cp.pid).should_not be_nil
|
33
|
+
cp.startup
|
34
|
+
cp.pid.should == pid
|
35
|
+
}
|
36
|
+
|
37
|
+
it("shall do nothing when shutdown called and not started"){
|
38
|
+
cp.alive?.should == false
|
39
|
+
cp.shutdown.should == true # mostly just asserting it doesn't fail
|
40
|
+
}
|
41
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require_relative '../bootstrap/unit'
|
3
|
+
require 'golly-utils/attr_declarative'
|
4
|
+
|
5
|
+
class AttrDeclarativeTest < MiniTest::Unit::TestCase
|
6
|
+
|
7
|
+
class Abc
|
8
|
+
include GollyUtils::AttrDeclarative
|
9
|
+
attr_declarative :cow
|
10
|
+
attr_declarative :horse, default: 246
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_acts_like_attribute
|
14
|
+
a= Abc.new
|
15
|
+
assert_nil a.cow
|
16
|
+
a.cow{ 12 }
|
17
|
+
assert_equal 12, a.cow
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_default
|
21
|
+
a= Abc.new
|
22
|
+
assert_equal 246, a.horse
|
23
|
+
a.horse{ 'hehe' }
|
24
|
+
assert_equal 'hehe', a.horse
|
25
|
+
end
|
26
|
+
|
27
|
+
class Abc2 < Abc
|
28
|
+
cow{ 777 }
|
29
|
+
end
|
30
|
+
def test_subclasses_can_change_defaults
|
31
|
+
a= Abc2.new
|
32
|
+
assert_equal 777, a.cow
|
33
|
+
a.cow{ 12 }
|
34
|
+
assert_equal 12, a.cow
|
35
|
+
assert_equal 777, Abc2.new.cow
|
36
|
+
assert_nil Abc.new.cow
|
37
|
+
end
|
38
|
+
|
39
|
+
class Abc3 < Abc2; end
|
40
|
+
def test_subclasses_inherit_parents_declarations
|
41
|
+
assert_equal 777, Abc3.new.cow
|
42
|
+
end
|
43
|
+
|
44
|
+
class Dynamic < Abc
|
45
|
+
$cow= 123
|
46
|
+
cow{ $cow }
|
47
|
+
end
|
48
|
+
def test_class_declarations_arent_evaluated_until_needed
|
49
|
+
a= Dynamic.new
|
50
|
+
$cow= 456
|
51
|
+
assert_equal 456, a.cow
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require_relative '../bootstrap/unit'
|
3
|
+
require 'golly-utils/callbacks'
|
4
|
+
|
5
|
+
class CallbacksTest < MiniTest::Unit::TestCase
|
6
|
+
|
7
|
+
VALUES= []
|
8
|
+
|
9
|
+
def setup
|
10
|
+
VALUES.clear
|
11
|
+
end
|
12
|
+
|
13
|
+
class Base
|
14
|
+
include GollyUtils::Callbacks
|
15
|
+
define_callback :base
|
16
|
+
def self.record(v); CallbacksTest::VALUES << v end
|
17
|
+
def self.run
|
18
|
+
o= self.new
|
19
|
+
o.run
|
20
|
+
CallbacksTest::VALUES
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Omg < Base
|
25
|
+
define_callback :wow
|
26
|
+
def run
|
27
|
+
run_callback :wow
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def test_none; assert_equal [], Omg.run; end
|
31
|
+
|
32
|
+
class Omg1 < Omg
|
33
|
+
wow{ record 1 }
|
34
|
+
end
|
35
|
+
def test_one; assert_equal [1], Omg1.run; end
|
36
|
+
|
37
|
+
class Omg2 < Omg
|
38
|
+
wow{ record 1 }
|
39
|
+
wow{ record 2 }
|
40
|
+
end
|
41
|
+
def test_multiple_callees_for_single_callback; assert_equal [1,2], Omg2.run; end
|
42
|
+
|
43
|
+
class Omg1H < Omg1
|
44
|
+
wow{ record 3 }
|
45
|
+
end
|
46
|
+
def test_callees_inherited; assert_equal [3,1], Omg1H.run; end
|
47
|
+
|
48
|
+
class Sweet < Omg1
|
49
|
+
define_callbacks :dude, :sweet
|
50
|
+
wow{ record 7 }
|
51
|
+
dude{ record 60 }
|
52
|
+
sweet{ record 100 }
|
53
|
+
dude{ record 50 }
|
54
|
+
def run
|
55
|
+
run_callback :wow
|
56
|
+
run_callback :base
|
57
|
+
self.class.record 666
|
58
|
+
run_callbacks :sweet, :dude
|
59
|
+
end
|
60
|
+
end
|
61
|
+
def test_everything; assert_equal [7,1,666,100,60,50], Sweet.run; end
|
62
|
+
|
63
|
+
class Fail < Base
|
64
|
+
def self.what; 135 end
|
65
|
+
end
|
66
|
+
def test_callbacks_dont_overwrite_class_methods
|
67
|
+
Fail.send :define_callback, :this_should_work
|
68
|
+
assert_equal 135, Fail.what
|
69
|
+
Fail.send :define_callback, :what
|
70
|
+
flunk 'Exception expected. Callbacks are supposed to raise an error a method with the callback name already exists.'
|
71
|
+
rescue => e
|
72
|
+
assert e.to_s['what'], "Error message doesn't include the conflicting method/callback name.\nErrMsg: #{e}"
|
73
|
+
assert_equal 135, Fail.what
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|