golly-utils 0.0.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|