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.
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