golly-utils 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +8 -0
  3. data/.simplecov +14 -0
  4. data/.travis.yml +5 -0
  5. data/.yardopts +6 -0
  6. data/CHANGELOG.md +10 -0
  7. data/Gemfile +12 -0
  8. data/Gemfile.corvid +27 -0
  9. data/Gemfile.lock +86 -0
  10. data/Guardfile +44 -0
  11. data/README.md +3 -0
  12. data/RELEASE.md +13 -0
  13. data/Rakefile +6 -0
  14. data/bin/guard +16 -0
  15. data/bin/rake +16 -0
  16. data/bin/rspec +16 -0
  17. data/bin/yard +16 -0
  18. data/bin/yardoc +16 -0
  19. data/bin/yri +16 -0
  20. data/golly-utils.gemspec +17 -0
  21. data/lib/golly-utils/attr_declarative.rb +74 -0
  22. data/lib/golly-utils/callbacks.rb +92 -0
  23. data/lib/golly-utils/child_process.rb +124 -0
  24. data/lib/golly-utils/colourer.rb +50 -0
  25. data/lib/golly-utils/delegator.rb +81 -0
  26. data/lib/golly-utils/multi_io.rb +13 -0
  27. data/lib/golly-utils/ruby_ext.rb +2 -0
  28. data/lib/golly-utils/ruby_ext/array_to_hash.rb +29 -0
  29. data/lib/golly-utils/ruby_ext/deep_dup.rb +33 -0
  30. data/lib/golly-utils/ruby_ext/env_helpers.rb +27 -0
  31. data/lib/golly-utils/ruby_ext/hash_combinations.rb +49 -0
  32. data/lib/golly-utils/ruby_ext/pretty_error_messages.rb +9 -0
  33. data/lib/golly-utils/ruby_ext/subclasses.rb +17 -0
  34. data/lib/golly-utils/test/spec/deferrable_specs.rb +85 -0
  35. data/lib/golly-utils/test/spec/within_time.rb +56 -0
  36. data/lib/golly-utils/version.rb +3 -0
  37. data/test/bootstrap/all.rb +4 -0
  38. data/test/bootstrap/spec.rb +5 -0
  39. data/test/bootstrap/unit.rb +4 -0
  40. data/test/spec/child_process_mock_target.rb +28 -0
  41. data/test/spec/child_process_spec.rb +41 -0
  42. data/test/unit/attr_declarative_test.rb +54 -0
  43. data/test/unit/callbacks_test.rb +76 -0
  44. data/test/unit/delegator_test.rb +99 -0
  45. data/test/unit/multi_io_test.rb +18 -0
  46. data/test/unit/ruby_ext/env_helpers_test.rb +48 -0
  47. data/test/unit/ruby_ext/hash_combinations_test.rb +31 -0
  48. data/test/unit/ruby_ext/subclasses_test.rb +24 -0
  49. 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,9 @@
1
+ class StandardError
2
+
3
+ # Creates a string that provides more information (a backtrace) than the default `to_s` method.
4
+ #
5
+ # @return String
6
+ def to_pretty_s
7
+ "#{self.inspect}\n#{self.backtrace.map{|b| " #{b}"}.join "\n"}"
8
+ end
9
+ 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,3 @@
1
+ module GollyUtils
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ APP_ROOT = File.expand_path('../../..',__FILE__)
3
+ require 'rubygems'
4
+ require 'corvid/test/bootstrap/all'
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ $coverage_name = 'specs'
3
+ require_relative 'all'
4
+ require 'corvid/test/bootstrap/spec'
5
+
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ $coverage_name = 'unit tests'
3
+ require_relative 'all'
4
+ require 'corvid/test/bootstrap/unit'
@@ -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