golly-utils 0.0.1 → 0.6.0
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/.corvid/Gemfile +28 -0
- data/.corvid/features.yml +4 -0
- data/.corvid/plugins.yml +4 -0
- data/.corvid/stats_cfg.rb +13 -0
- data/.corvid/todo_cfg.rb +15 -0
- data/.corvid/versions.yml +2 -0
- data/.simplecov +6 -3
- data/CHANGELOG.md +45 -2
- data/Gemfile +6 -3
- data/Gemfile.lock +34 -37
- data/Guardfile +10 -4
- data/RELEASE.md +2 -0
- data/Rakefile +1 -1
- data/golly-utils.gemspec +19 -10
- data/lib/golly-utils/attr_declarative.rb +120 -49
- data/lib/golly-utils/callbacks.rb +211 -19
- data/lib/golly-utils/child_process.rb +28 -8
- data/lib/golly-utils/delegator.rb +14 -3
- data/lib/golly-utils/ruby_ext/classes_and_types.rb +120 -0
- data/lib/golly-utils/ruby_ext/enumerable.rb +16 -0
- data/lib/golly-utils/ruby_ext/env_helpers.rb +17 -0
- data/lib/golly-utils/ruby_ext/kernel.rb +18 -0
- data/lib/golly-utils/ruby_ext/options.rb +28 -0
- data/lib/golly-utils/ruby_ext/pretty_error_messages.rb +1 -1
- data/lib/golly-utils/singleton.rb +130 -0
- data/lib/golly-utils/testing/dynamic_fixtures.rb +268 -0
- data/lib/golly-utils/testing/file_helpers.rb +117 -0
- data/lib/golly-utils/testing/helpers_base.rb +20 -0
- data/lib/golly-utils/testing/rspec/arrays.rb +85 -0
- data/lib/golly-utils/testing/rspec/base.rb +9 -0
- data/lib/golly-utils/testing/rspec/deferrable_specs.rb +111 -0
- data/lib/golly-utils/testing/rspec/files.rb +262 -0
- data/lib/golly-utils/{test/spec → testing/rspec}/within_time.rb +17 -7
- data/lib/golly-utils/version.rb +2 -1
- data/test/bootstrap/all.rb +8 -1
- data/test/bootstrap/spec.rb +1 -1
- data/test/bootstrap/unit.rb +1 -1
- data/test/spec/child_process_spec.rb +1 -1
- data/test/spec/testing/dynamic_fixtures_spec.rb +131 -0
- data/test/spec/testing/rspec/arrays_spec.rb +33 -0
- data/test/spec/testing/rspec/files_spec.rb +300 -0
- data/test/unit/attr_declarative_test.rb +79 -13
- data/test/unit/callbacks_test.rb +103 -5
- data/test/unit/delegator_test.rb +25 -1
- data/test/unit/ruby_ext/classes_and_types_test.rb +103 -0
- data/test/unit/ruby_ext/enumerable_test.rb +12 -0
- data/test/unit/ruby_ext/options_test.rb +29 -0
- data/test/unit/singleton_test.rb +59 -0
- metadata +100 -10
- data/Gemfile.corvid +0 -27
- data/lib/golly-utils/ruby_ext.rb +0 -2
- data/lib/golly-utils/ruby_ext/subclasses.rb +0 -17
- data/lib/golly-utils/test/spec/deferrable_specs.rb +0 -85
- data/test/unit/ruby_ext/subclasses_test.rb +0 -24
@@ -0,0 +1,16 @@
|
|
1
|
+
module Enumerable
|
2
|
+
|
3
|
+
# Creates a map of all elements by the frequency that they occur.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# %w[big house big car].frequency_map # => {"big"=>2, "car"=>1, "house"=>1}
|
7
|
+
#
|
8
|
+
# @return [Hash<Object,Fixnum>] Map of element to frequency.
|
9
|
+
def frequency_map
|
10
|
+
inject Hash.new do |h,e|
|
11
|
+
h[e]= (h[e] || 0) + 1
|
12
|
+
h
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -1,6 +1,19 @@
|
|
1
1
|
module GollyUtils
|
2
|
+
# Mixin that enriches Ruby's `ENV`.
|
3
|
+
#
|
4
|
+
# @note This is mixed-in to `ENV` automatically.
|
2
5
|
module EnvHelpers
|
3
6
|
|
7
|
+
# Parses an environment variable that is expected to be a boolean.
|
8
|
+
#
|
9
|
+
# Regardless of case,
|
10
|
+
#
|
11
|
+
# * the following values are interpretted as positive: `1, y, yes, t, true, on, enabled`
|
12
|
+
# * the following values are interpretted as negative: `0, n, no, f, false, off, disabled`
|
13
|
+
#
|
14
|
+
# @param [String] key The `ENV` key. The environment variable name.
|
15
|
+
# @param [Boolean, nil] default The value to return if there is no environment variable of given key.
|
16
|
+
# @return [Boolean,nil] The result of parsing the env var value, else `default`.
|
4
17
|
def boolean(key, default=nil)
|
5
18
|
return default unless self.has_key?(key)
|
6
19
|
v= self[key]
|
@@ -14,6 +27,10 @@ module GollyUtils
|
|
14
27
|
alias on? boolean
|
15
28
|
alias enabled? boolean
|
16
29
|
|
30
|
+
# Parses an environment variable and checks if it indicates a negative boolean value.
|
31
|
+
#
|
32
|
+
# @param (see #boolean)
|
33
|
+
# @return [Boolean,nil] The result of parsing the env var value and it indicating the negative, else `default`.
|
17
34
|
def no?(key, default=nil)
|
18
35
|
!boolean(key, default)
|
19
36
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kernel
|
2
|
+
|
3
|
+
# Alternate implementation of `at_exit` that preserves the exit status (unless you call `exit` yourself and an error
|
4
|
+
# is raised).
|
5
|
+
#
|
6
|
+
# The initial driver for this was that using `at_exit` to clean up global resources in RSpec tests, RSpec's exit
|
7
|
+
# status would be lost which means CI processes and such were unable to tell whether there were test failures.
|
8
|
+
#
|
9
|
+
# @return [Proc] Whatever `at_exit` returns.
|
10
|
+
def at_exit_preserving_exit_status(&block)
|
11
|
+
at_exit {
|
12
|
+
status= $!.is_a?(::SystemExit) ? $!.status : nil
|
13
|
+
block.()
|
14
|
+
exit status if status
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Checks that all keys in this hash are part of a specific set; fails if not.
|
4
|
+
#
|
5
|
+
# @param [Array] valid_keys A set of valid keys.
|
6
|
+
# @param [String] error_msg A message to include in the error when raised.
|
7
|
+
# @return [self]
|
8
|
+
# @raise Invalid keys are present.
|
9
|
+
def validate_keys(valid_keys, error_msg = "Invalid hash keys")
|
10
|
+
invalid= self.keys - [valid_keys].flatten
|
11
|
+
unless invalid.empty?
|
12
|
+
raise "#{error_msg}: #{invalid.sort_by(&:to_s).map(&:inspect).join ', '}"
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
# Checks that all keys in this hash are part of a specific set; fails if not.
|
18
|
+
#
|
19
|
+
# Differs from {#validate_keys} by providing an option-related error message.
|
20
|
+
#
|
21
|
+
# @param valid_keys A set of valid keys.
|
22
|
+
# @return [self]
|
23
|
+
# @raise Invalid keys are present.
|
24
|
+
def validate_option_keys(*valid_keys)
|
25
|
+
validate_keys valid_keys, "Invalid options provided"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module GollyUtils
|
4
|
+
# Makes the including class a singleton.
|
5
|
+
# Much like Ruby's `Singleton` module, with extra features.
|
6
|
+
#
|
7
|
+
# Features:
|
8
|
+
#
|
9
|
+
# * Target class includes Ruby's `Singleton` module too.
|
10
|
+
# * Class methods are added to the target class that delegate to the singleton instance.
|
11
|
+
# * A convenience method {ClassMethods#def_accessor def_accessor} is provided to create an accessor in the calling
|
12
|
+
# class, as per `attr_accessor`, except that the value defaults to the singleton instance.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# class Stam1na
|
16
|
+
# include GollyUtils::Singleton
|
17
|
+
#
|
18
|
+
# def band_rating
|
19
|
+
# puts 'AWESOME!!'
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Stam1na.instance.band_rating #=> AWESOME!!
|
24
|
+
# Stam1na.band_rating #=> AWESOME!!
|
25
|
+
module Singleton
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def self.included(base)
|
29
|
+
base.send :include, ::Singleton
|
30
|
+
base.extend ClassMethods
|
31
|
+
base.class_eval <<-EOB
|
32
|
+
class << self
|
33
|
+
|
34
|
+
alias :method_missing_before_gu_singleton :method_missing
|
35
|
+
|
36
|
+
def method_missing(method, *args, &block)
|
37
|
+
if method != :__gu_singleton_method_missing
|
38
|
+
|
39
|
+
r= __gu_singleton_method_missing(self, method, *args, &block)
|
40
|
+
unless ::GollyUtils::Singleton::NO_MATCH == r
|
41
|
+
return r
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
method_missing_before_gu_singleton method, *args, &block
|
46
|
+
end
|
47
|
+
|
48
|
+
def __default_singleton_attr_name
|
49
|
+
'#{base.to_s.sub(/^.*::/,'').gsub(/(?<=[a-z])(?=[A-Z])/,'_').downcase}'
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
EOB
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
|
58
|
+
# Creates an instance accessor as `attr_accessor` does, execpt that the default value will be the singleton
|
59
|
+
# instance.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# class HappyDays
|
63
|
+
# include GollyUtils::Singleton
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# class A
|
67
|
+
# # @!attribute [rw] happy_days
|
68
|
+
# # @return [HappyDays]
|
69
|
+
# HappyDays.def_accessor self
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# A.new.happy_days == HappyDays.instance #=> true
|
73
|
+
#
|
74
|
+
# @param [Class|Module] target The object definition to add the attribute methods to.
|
75
|
+
# @param [String|Symbol] name The attribute name. Defaults to the singleton class name, with underscores and in
|
76
|
+
# lowercase.
|
77
|
+
# @return [nil]
|
78
|
+
def def_accessor(target, name=nil)
|
79
|
+
name ||= __default_singleton_attr_name
|
80
|
+
target.class_eval <<-EOB
|
81
|
+
def #{name}
|
82
|
+
@#{name} ||= ::#{self}.instance
|
83
|
+
end
|
84
|
+
def #{name}=(v)
|
85
|
+
@#{name}= v
|
86
|
+
end
|
87
|
+
EOB
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Prevents class-level delegate methods from being created for certain instance methods.
|
92
|
+
#
|
93
|
+
# @param [Array<Regexp|String>] matchers Either the name of the method, or a regex that matches methods.
|
94
|
+
# @return [nil]
|
95
|
+
def hide_singleton_methods(*matchers)
|
96
|
+
r= __gu_singleton_rejects
|
97
|
+
r.concat matchers
|
98
|
+
r.flatten!
|
99
|
+
r.uniq!
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def __gu_singleton_rejects
|
106
|
+
@__gu_singleton_rejects ||= []
|
107
|
+
end
|
108
|
+
|
109
|
+
# Called on `method_missing`. Rather than simply delegating the method call, it will instead see if there are any
|
110
|
+
# delegate methods that haven't been created yet and if so, creates them. This results in improved performance and
|
111
|
+
# less hits to `method_missing`.
|
112
|
+
def __gu_singleton_method_missing(singleton_class ,method, *args, &block)
|
113
|
+
methods= (singleton_class.instance.public_methods - singleton_class.methods)
|
114
|
+
if rej= __gu_singleton_rejects
|
115
|
+
methods.reject!{|m| m= m.to_s; rej.any?{|r| r === m }}
|
116
|
+
end
|
117
|
+
unless methods.empty?
|
118
|
+
code= methods.map {|m| "def self.#{m}(*a,&b); self.instance.send :#{m},*a,&b; end" }
|
119
|
+
singleton_class.class_eval(code.join "\n")
|
120
|
+
return singleton_class.send(method,*args,&block) if methods.include?(method)
|
121
|
+
end
|
122
|
+
::GollyUtils::Singleton::NO_MATCH
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# @!visibility private
|
128
|
+
NO_MATCH= Object.new
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'golly-utils/ruby_ext/kernel'
|
2
|
+
require 'golly-utils/ruby_ext/options'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'monitor'
|
5
|
+
require 'thread'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
module GollyUtils
|
9
|
+
module Testing
|
10
|
+
|
11
|
+
# Provides globally-shared, cached, lazily-loaded fixtures that are generated once on-demand, and copied when access
|
12
|
+
# is required.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# describe 'My Git Utility' do
|
16
|
+
# include GollyUtils::Testing::DynamicFixtures
|
17
|
+
#
|
18
|
+
# def_fixture :git do # Dynamic fixture definition.
|
19
|
+
# system 'git init' # Expensive to create.
|
20
|
+
# File.write 'test.txt', 'hello' # Only runs once.
|
21
|
+
# system 'git add -A && git commit -m x'
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# run_each_in_dynamic_fixture :git # RSpec helper for fixture usage.
|
25
|
+
#
|
26
|
+
# it("detects deleted files") { # Runs in a copy of the fixture.
|
27
|
+
# File.delete 'test.txt' # Free to modify its fixture copy.
|
28
|
+
# subject.deleted_files.should == %w[test.txt] # Other tests isolated from these these changes.
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
# it("detects new files") { # Runs in a clean copy of the fixture.
|
32
|
+
# File.create 'new.txt' # Generated quickly by copying cache.
|
33
|
+
# subject.new_files.should == %w[new.txt] # Unaffected by other tests' fixture modification.
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# end
|
37
|
+
module DynamicFixtures
|
38
|
+
|
39
|
+
# @!visibility private
|
40
|
+
def self.included(base)
|
41
|
+
base.extend ClassMethods
|
42
|
+
end
|
43
|
+
module ClassMethods
|
44
|
+
|
45
|
+
# Defines a dynamic fixture.
|
46
|
+
#
|
47
|
+
# @param [Symbol|String] name The dynamic fixture name.
|
48
|
+
# @param [Hash] options
|
49
|
+
# @option options [nil|String] :cd_into (nil) A fixture subdirectory to change directory into by default when
|
50
|
+
# using the fixture.
|
51
|
+
# @option options [nil|String] :dir_name (nil) A specific name to call the temporary directory used when first
|
52
|
+
# creating the fixture. If `nil` then the name will non-deterministic.
|
53
|
+
# @param [Proc] block Code that when run in an empty, temporary directory, will create the fixture contents.
|
54
|
+
# @yield Yields control in an empty directory at a later point in time.
|
55
|
+
# @return [void]
|
56
|
+
def def_fixture(name, options={}, &block)
|
57
|
+
raise "Block not provided." unless block
|
58
|
+
options.validate_option_keys :cd_into, :dir_name
|
59
|
+
|
60
|
+
name= DynamicFixtures.normalise_dynfix_name(name)
|
61
|
+
STDERR.warn "Dyanmic fixture being redefined: #{name}." if $gu_dynamic_fixtures[name]
|
62
|
+
$gu_dynamic_fixtures[name]= options.merge(block: block)
|
63
|
+
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# RSpec helper that directs that each example be run in it's own clean copy of a given dynamic fixture.
|
68
|
+
#
|
69
|
+
# @param [Symbol|String] name The dynamic fixture name.
|
70
|
+
# @param [Hash] options Options to pass to {DynamicFixtures#inside_dynamic_fixture}. See that method for option
|
71
|
+
# details.
|
72
|
+
# @return [void]
|
73
|
+
# @see DynamicFixtures#inside_dynamic_fixture
|
74
|
+
def run_each_in_dynamic_fixture(name, options={})
|
75
|
+
raise "Block not supported." if block_given?
|
76
|
+
options.validate_option_keys DynamicFixtures::INSIDE_DYNAMIC_FIXTURE_OPTIONS
|
77
|
+
|
78
|
+
class_eval <<-EOB
|
79
|
+
around :each do |ex|
|
80
|
+
inside_dynamic_fixture(#{name.inspect}, #{options.inspect}){ ex.run }
|
81
|
+
end
|
82
|
+
EOB
|
83
|
+
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# RSpec helper that directs that all examples be run in the same (initially-clean) copy of a given dynamic
|
88
|
+
# fixture.
|
89
|
+
#
|
90
|
+
# @param [Symbol|String] name The dynamic fixture name.
|
91
|
+
# @param [Hash] options
|
92
|
+
# @option options [nil|String] :dir_name (nil) A specific name to call the empty directory basename.
|
93
|
+
# @option options [nil|String] :cd_into (nil) A fixture subdirectory to change directory into when running
|
94
|
+
# examples.
|
95
|
+
# @yield Invokes the given block (if one given) once before any examples run, inside the empty dir, to perform
|
96
|
+
# any additional initialisation required.
|
97
|
+
# @return [void]
|
98
|
+
# @see GollyUtils::Testing::Helpers::ClassMethods#run_all_in_empty_dir
|
99
|
+
def run_all_in_dynamic_fixture(name, options={}, &block)
|
100
|
+
options.validate_option_keys :cd_into, :dir_name
|
101
|
+
|
102
|
+
require 'golly-utils/testing/rspec/files'
|
103
|
+
name= DynamicFixtures.normalise_dynfix_name(name) # actually just wanted dup
|
104
|
+
run_all_in_empty_dir(options[:dir_name]) {
|
105
|
+
copy_dynamic_fixture name
|
106
|
+
block.() if block
|
107
|
+
}
|
108
|
+
|
109
|
+
if cd_into= options[:cd_into]
|
110
|
+
class_eval <<-EOB
|
111
|
+
around(:each){|ex|
|
112
|
+
Dir.chdir(#{cd_into.inspect}){ ex.run }
|
113
|
+
}
|
114
|
+
EOB
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
#------------------------------------------------------------------------------------------------------------------
|
121
|
+
|
122
|
+
# Callback invoked just before creating a dynamic fixture for the first time.
|
123
|
+
#
|
124
|
+
# Override to customise.
|
125
|
+
#
|
126
|
+
# @param [Symbol] name The name of the dynamic fixture being created.
|
127
|
+
# @return [void]
|
128
|
+
def before_dynamic_fixture_creation(name)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Callback invoked just after creating a dynamic fixture for the first time.
|
132
|
+
#
|
133
|
+
# Override to customise.
|
134
|
+
#
|
135
|
+
# @param [Symbol] name The name of the dynamic fixture being created.
|
136
|
+
# @param [Float] creation_time_in_sec The number of seconds taken to create the fixture.
|
137
|
+
# @return [void]
|
138
|
+
def after_dynamic_fixture_creation(name, creation_time_in_sec)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Copies the contents of a dynamic fixture to a given directory.
|
142
|
+
#
|
143
|
+
# @param [Symbol|String] name The name of the dynamic fixture to copy.
|
144
|
+
# @param [String] target_dir The (existing) directory to copy the fixture to.
|
145
|
+
# @return [void]
|
146
|
+
def copy_dynamic_fixture(name, target_dir = '.')
|
147
|
+
FileUtils.cp_r "#{dynamic_fixture_dir name}/.", target_dir
|
148
|
+
end
|
149
|
+
|
150
|
+
# Creates a clean copy of a predefined dynamic fixture, changes directory into it and yields. The fixture copy
|
151
|
+
# is removed from the file system after the yield block returns.
|
152
|
+
#
|
153
|
+
# @param [Symbol|String] name The name of the dynamic fixture.
|
154
|
+
# @param [Hash] options
|
155
|
+
# @option options [nil|String] :cd_into (nil) A fixture subdirectory to change directory into before yielding.
|
156
|
+
# @yield Yields control in the directory of a fixture copy.
|
157
|
+
# @return The value of the given block.
|
158
|
+
def inside_dynamic_fixture(name, options={}, &block)
|
159
|
+
options.validate_option_keys INSIDE_DYNAMIC_FIXTURE_OPTIONS
|
160
|
+
|
161
|
+
Dir.mktmpdir {|dir|
|
162
|
+
copy_dynamic_fixture name, dir
|
163
|
+
df= get_dynamic_fixture_data(name)
|
164
|
+
|
165
|
+
if cd_into= options[:cd_into] || df[:cd_into]
|
166
|
+
dir= File.join dir, cd_into
|
167
|
+
end
|
168
|
+
|
169
|
+
$gu_dynamic_fixture_chdir_lock.synchronize {
|
170
|
+
return Dir.chdir dir, &block
|
171
|
+
}
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
# @!visibility private
|
176
|
+
INSIDE_DYNAMIC_FIXTURE_OPTIONS= [:cd_into].freeze
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def get_dynamic_fixture_data(name)
|
181
|
+
name= DynamicFixtures.normalise_dynfix_name(name)
|
182
|
+
$gu_dynamic_fixtures[name]
|
183
|
+
end
|
184
|
+
|
185
|
+
# Creates and provides the global, temporary directory that will serve as the base directory for generating and
|
186
|
+
# storing all dynamic fixtures.
|
187
|
+
#
|
188
|
+
# Once created it will be automatically removed on process exit.
|
189
|
+
#
|
190
|
+
# @return [String] A directory.
|
191
|
+
def dynamic_fixture_root
|
192
|
+
$gu_dynamic_fixture_root || DYNAMIC_FIXTURE_ROOT_LOCK.synchronize {
|
193
|
+
$gu_dynamic_fixture_root ||= (
|
194
|
+
at_exit_preserving_exit_status {
|
195
|
+
FileUtils.remove_entry_secure $gu_dynamic_fixture_root if $gu_dynamic_fixture_root
|
196
|
+
$gu_dynamic_fixtures= $gu_dynamic_fixture_root= nil
|
197
|
+
}
|
198
|
+
Dir.mktmpdir
|
199
|
+
)
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
DYNAMIC_FIXTURE_ROOT_LOCK= Mutex.new
|
204
|
+
|
205
|
+
# Provides the directory name of a generated dynamic fixture. If the fixture hasn't been generated yet, then this
|
206
|
+
# will generate it first.
|
207
|
+
#
|
208
|
+
# @param [Symbol|String] name The dynamic fixture name.
|
209
|
+
# @return [String] A directory.
|
210
|
+
def dynamic_fixture_dir(name)
|
211
|
+
df= get_dynamic_fixture_data(name)
|
212
|
+
raise "Undefined dynamic fixture: #{name}" unless df
|
213
|
+
|
214
|
+
if df[:block]
|
215
|
+
$gu_dynamic_fixture_chdir_lock.synchronize {
|
216
|
+
# df[:lock].synchronize {
|
217
|
+
if df[:block]
|
218
|
+
|
219
|
+
# Start creating dynamic fixture
|
220
|
+
before_dynamic_fixture_creation name
|
221
|
+
dir= "#{dynamic_fixture_root}/#{name}"
|
222
|
+
Dir.mkdir dir
|
223
|
+
|
224
|
+
if subdir= df[:dir_name]
|
225
|
+
dir+= "/#{subdir}"
|
226
|
+
Dir.mkdir dir
|
227
|
+
end
|
228
|
+
|
229
|
+
start_time_stack= (Thread.current[:gu_dynamic_fixture_start_times] ||= [])
|
230
|
+
start_time_stack<< Time.now
|
231
|
+
begin
|
232
|
+
# Create fixture
|
233
|
+
Dir.chdir(dir){ instance_eval &df[:block] }
|
234
|
+
df.delete :block
|
235
|
+
df[:dir]= dir
|
236
|
+
|
237
|
+
dur= Time.now - start_time_stack.last
|
238
|
+
start_time_stack.map!{|t| t + dur}
|
239
|
+
after_dynamic_fixture_creation name, dur
|
240
|
+
ensure
|
241
|
+
start_time_stack.pop
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
df[:dir].dup
|
249
|
+
end
|
250
|
+
|
251
|
+
#-----------------------------------------------------------------------------------------------------------------
|
252
|
+
|
253
|
+
extend ClassMethods
|
254
|
+
|
255
|
+
# @!visibility private
|
256
|
+
def self.normalise_dynfix_name(name)
|
257
|
+
name.to_sym
|
258
|
+
end
|
259
|
+
|
260
|
+
unless $gu_dynamic_fixtures
|
261
|
+
$gu_dynamic_fixtures= {}
|
262
|
+
$gu_dynamic_fixture_root= nil
|
263
|
+
$gu_dynamic_fixture_chdir_lock= Monitor.new
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|