golly-utils 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|