unobtainium 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/cuke/Gemfile.lock +59 -0
- data/cuke/config/config.yml +1 -0
- data/lib/unobtainium/config.rb +131 -21
- data/lib/unobtainium/drivers/selenium.rb +19 -2
- data/lib/unobtainium/pathed_hash.rb +46 -9
- data/lib/unobtainium/recursive_merge.rb +40 -0
- data/lib/unobtainium/version.rb +1 -1
- data/lib/unobtainium/world.rb +18 -10
- data/spec/config_spec.rb +26 -0
- data/spec/data/driverconfig.yml +23 -0
- data/spec/pathed_hash_spec.rb +73 -0
- data/spec/world_spec.rb +7 -3
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62f55e229f2458af4e265380afd8e407135e2f6e
|
4
|
+
data.tar.gz: 84c5321fff693ec7eedddc59f74bbe829303f383
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 070ff928ed2373b5f26bcb9d4c596d6d7ced6443d0028feeaba897a1cb66accec60b8612204245ef9c76dc00213bab8c0c9ea0562483d286d7d3524926536781
|
7
|
+
data.tar.gz: 2c8b4a547d1cc410d60f0d0d86c214720097f1da2ea7a4d1717d7b1645615be12add8ccfac7eac53f1e091e4c8ef32ef28b8672f1366d7db87ebffad32acf4a5
|
data/Gemfile.lock
CHANGED
data/cuke/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git@github.com:jfinkhaeuser/unobtainium.git
|
3
|
+
revision: 16b26ed3c3f60bd114ab774a717d3050feb94e03
|
4
|
+
branch: master
|
5
|
+
specs:
|
6
|
+
unobtainium (0.2.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
appium_lib (8.0.2)
|
12
|
+
awesome_print (~> 1.6)
|
13
|
+
json (~> 1.8)
|
14
|
+
nokogiri (~> 1.6.6)
|
15
|
+
selenium-webdriver (~> 2.49)
|
16
|
+
tomlrb (~> 1.1)
|
17
|
+
awesome_print (1.6.1)
|
18
|
+
builder (3.2.2)
|
19
|
+
childprocess (0.5.9)
|
20
|
+
ffi (~> 1.0, >= 1.0.11)
|
21
|
+
cucumber (2.3.3)
|
22
|
+
builder (>= 2.1.2)
|
23
|
+
cucumber-core (~> 1.4.0)
|
24
|
+
cucumber-wire (~> 0.0.1)
|
25
|
+
diff-lcs (>= 1.1.3)
|
26
|
+
gherkin (~> 3.2.0)
|
27
|
+
multi_json (>= 1.7.5, < 2.0)
|
28
|
+
multi_test (>= 0.1.2)
|
29
|
+
cucumber-core (1.4.0)
|
30
|
+
gherkin (~> 3.2.0)
|
31
|
+
cucumber-wire (0.0.1)
|
32
|
+
diff-lcs (1.2.5)
|
33
|
+
ffi (1.9.10)
|
34
|
+
gherkin (3.2.0)
|
35
|
+
json (1.8.3)
|
36
|
+
mini_portile2 (2.0.0)
|
37
|
+
multi_json (1.11.2)
|
38
|
+
multi_test (0.1.2)
|
39
|
+
nokogiri (1.6.7.2)
|
40
|
+
mini_portile2 (~> 2.0.0.rc2)
|
41
|
+
rubyzip (1.2.0)
|
42
|
+
selenium-webdriver (2.53.0)
|
43
|
+
childprocess (~> 0.5)
|
44
|
+
rubyzip (~> 1.0)
|
45
|
+
websocket (~> 1.0)
|
46
|
+
tomlrb (1.2.1)
|
47
|
+
websocket (1.2.3)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
appium_lib
|
54
|
+
cucumber
|
55
|
+
selenium-webdriver
|
56
|
+
unobtainium!
|
57
|
+
|
58
|
+
BUNDLED WITH
|
59
|
+
1.11.2
|
data/cuke/config/config.yml
CHANGED
data/lib/unobtainium/config.rb
CHANGED
@@ -59,14 +59,23 @@ module Unobtainium
|
|
59
59
|
# Wrap all read functions into something that checks for environment
|
60
60
|
# variables first.
|
61
61
|
define_method(method) do |*args, &block|
|
62
|
+
# If there are no arguments, there's nothing to do with paths. Just
|
63
|
+
# delegate to the hash.
|
64
|
+
if args.empty?
|
65
|
+
return super(*args, &block)
|
66
|
+
end
|
67
|
+
|
62
68
|
# We'll make it rather simple: since the first argument is a key, we
|
63
69
|
# will just transform it to the matching environment variable name,
|
64
70
|
# and see if that environment variable is set.
|
65
|
-
env_name = args[0].upcase.gsub(split_pattern, '_')
|
66
|
-
contents =
|
71
|
+
env_name = args[0].to_s.upcase.gsub(split_pattern, '_')
|
72
|
+
contents = nil
|
73
|
+
if env_name != '_'
|
74
|
+
contents = ENV[env_name]
|
75
|
+
end
|
67
76
|
|
68
77
|
# No environment variable set? Fine, just do the usual thing.
|
69
|
-
if contents.nil?
|
78
|
+
if contents.nil? or contents.empty?
|
70
79
|
return super(*args, &block)
|
71
80
|
end
|
72
81
|
|
@@ -94,28 +103,23 @@ module Unobtainium
|
|
94
103
|
##
|
95
104
|
# Loads a configuration file with the given file name. The format is
|
96
105
|
# detected based on one of the extensions in FILE_TO_PARSER.
|
97
|
-
def load_config(path)
|
106
|
+
def load_config(path, resolve_extensions = true)
|
98
107
|
# Load base and local configuration files
|
99
108
|
base, config = load_base_config(path)
|
100
109
|
_, local_config = load_local_config(base)
|
101
|
-
if local_config.nil?
|
102
|
-
return Config.new(config)
|
103
|
-
end
|
104
110
|
|
105
|
-
# Merge
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
# rubocop:enable Style/GuardClause
|
111
|
+
# Merge local configuration
|
112
|
+
config.recursive_merge!(local_config)
|
113
|
+
|
114
|
+
# Create config from the result
|
115
|
+
cfg = Config.new(config)
|
116
|
+
|
117
|
+
# Now resolve config hashes that extend other hashes.
|
118
|
+
if resolve_extensions
|
119
|
+
cfg.resolve_extensions!
|
115
120
|
end
|
116
|
-
config.merge!(local_config, &merger)
|
117
121
|
|
118
|
-
return
|
122
|
+
return cfg
|
119
123
|
end
|
120
124
|
|
121
125
|
private
|
@@ -137,7 +141,7 @@ module Unobtainium
|
|
137
141
|
# Parse the contents.
|
138
142
|
config = FILE_TO_PARSER[base.extname].parse(contents)
|
139
143
|
|
140
|
-
return base, hashify(config)
|
144
|
+
return base, PathedHash.new(hashify(config))
|
141
145
|
end
|
142
146
|
|
143
147
|
def load_local_config(base)
|
@@ -156,7 +160,7 @@ module Unobtainium
|
|
156
160
|
|
157
161
|
local_config = FILE_TO_PARSER[base.extname].parse(contents)
|
158
162
|
|
159
|
-
return local, hashify(local_config)
|
163
|
+
return local, PathedHash.new(hashify(local_config))
|
160
164
|
end
|
161
165
|
|
162
166
|
def hashify(data)
|
@@ -169,5 +173,111 @@ module Unobtainium
|
|
169
173
|
return data
|
170
174
|
end
|
171
175
|
end # class << self
|
176
|
+
|
177
|
+
##
|
178
|
+
# Resolve extensions in configuration hashes. If your hash contains e.g.:
|
179
|
+
#
|
180
|
+
# foo:
|
181
|
+
# bar:
|
182
|
+
# some: value
|
183
|
+
# baz:
|
184
|
+
# extends: bar
|
185
|
+
#
|
186
|
+
# Then 'foo.baz.some' will equal 'value' after resolving extensions. Note
|
187
|
+
# that :load_config calls this function, so normally you don't need to call
|
188
|
+
# it yourself. You can switch this behaviour off in :load_config.
|
189
|
+
#
|
190
|
+
# Note that this process has some intended side-effects:
|
191
|
+
# 1) If a hash can't be extended because the base cannot be found, an error
|
192
|
+
# is raised.
|
193
|
+
# 2) If a hash got successfully extended, the :extends keyword itself is
|
194
|
+
# removed from the hash.
|
195
|
+
# 3) In a successfully extended hash, an :base keyword, which contains
|
196
|
+
# the name of the base. In case of multiple recursive extensions, the
|
197
|
+
# final base is stored here.
|
198
|
+
#
|
199
|
+
# Also note that all of this means that :extends and :base are reserved
|
200
|
+
# keywords that cannot be used in configuration files other than for this
|
201
|
+
# purpose!
|
202
|
+
def resolve_extensions!
|
203
|
+
recursive_merge("", "")
|
204
|
+
end
|
205
|
+
|
206
|
+
def resolve_extensions
|
207
|
+
dup.resolve_extensions!
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
def recursive_merge(parent, key)
|
213
|
+
loop do
|
214
|
+
full_key = "#{parent}#{separator}#{key}"
|
215
|
+
|
216
|
+
# Recurse down to the remaining root of the hierarchy
|
217
|
+
base = full_key
|
218
|
+
derived = nil
|
219
|
+
loop do
|
220
|
+
new_base, new_derived = resolve_extension(parent, base)
|
221
|
+
|
222
|
+
if new_derived.nil?
|
223
|
+
break
|
224
|
+
end
|
225
|
+
|
226
|
+
base = new_base
|
227
|
+
derived = new_derived
|
228
|
+
end
|
229
|
+
|
230
|
+
# If recursion found nothing to merge, we're done!
|
231
|
+
if derived.nil?
|
232
|
+
break
|
233
|
+
end
|
234
|
+
|
235
|
+
# Otherwise, merge what needs merging and continue
|
236
|
+
merge_extension(base, derived)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def resolve_extension(grandparent, parent)
|
241
|
+
fetch(parent, {}).each do |key, value|
|
242
|
+
# Recurse into hash values
|
243
|
+
if value.is_a? Hash
|
244
|
+
recursive_merge(parent, key)
|
245
|
+
end
|
246
|
+
|
247
|
+
# No hash, ignore any keys other than the special "extends" key
|
248
|
+
if key != "extends"
|
249
|
+
next
|
250
|
+
end
|
251
|
+
|
252
|
+
# If the key is "extends", return a normalized version of its value.
|
253
|
+
full_value = value.dup
|
254
|
+
if not full_value.start_with?(separator)
|
255
|
+
full_value = "#{grandparent}#{separator}#{value}"
|
256
|
+
end
|
257
|
+
|
258
|
+
if full_value == parent
|
259
|
+
next
|
260
|
+
end
|
261
|
+
return full_value, parent
|
262
|
+
end
|
263
|
+
|
264
|
+
return nil, nil
|
265
|
+
end
|
266
|
+
|
267
|
+
def merge_extension(base, derived)
|
268
|
+
# Remove old 'extends' key, but remember the value
|
269
|
+
extends = self[derived]["extends"]
|
270
|
+
self[derived].delete("extends")
|
271
|
+
|
272
|
+
# Recursively merge base into derived without overwriting
|
273
|
+
self[derived].extend(::Unobtainium::RecursiveMerge)
|
274
|
+
self[derived].recursive_merge!(self[base], false)
|
275
|
+
|
276
|
+
# Then set the "base" keyword, but only if it's not yet set.
|
277
|
+
if not self[derived]["base"].nil?
|
278
|
+
return
|
279
|
+
end
|
280
|
+
self[derived]["base"] = extends
|
281
|
+
end
|
172
282
|
end # class Config
|
173
283
|
end # module Unobtainium
|
@@ -19,6 +19,7 @@ module Unobtainium
|
|
19
19
|
safari: [],
|
20
20
|
chrome: [],
|
21
21
|
chromium: [],
|
22
|
+
remote: [],
|
22
23
|
}.freeze
|
23
24
|
|
24
25
|
class << self
|
@@ -39,10 +40,26 @@ module Unobtainium
|
|
39
40
|
err.backtrace
|
40
41
|
end
|
41
42
|
|
43
|
+
##
|
44
|
+
# Selenium really wants symbol keys for the options
|
45
|
+
def sanitize_options(label, options)
|
46
|
+
new_opts = {}
|
47
|
+
|
48
|
+
if not options.nil?
|
49
|
+
options.each do |key, value|
|
50
|
+
new_opts[key.to_sym] = value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
options = new_opts
|
55
|
+
|
56
|
+
return label, options
|
57
|
+
end
|
58
|
+
|
42
59
|
##
|
43
60
|
# Create and return a driver instance
|
44
|
-
def create(label,
|
45
|
-
driver = ::Selenium::WebDriver.for(normalize_label(label))
|
61
|
+
def create(label, options)
|
62
|
+
driver = ::Selenium::WebDriver.for(normalize_label(label), options)
|
46
63
|
return driver
|
47
64
|
end
|
48
65
|
|
@@ -7,6 +7,8 @@
|
|
7
7
|
# All rights reserved.
|
8
8
|
#
|
9
9
|
|
10
|
+
require 'unobtainium/recursive_merge'
|
11
|
+
|
10
12
|
module Unobtainium
|
11
13
|
|
12
14
|
##
|
@@ -14,10 +16,16 @@ module Unobtainium
|
|
14
16
|
# regular access, i.e. instead of h["first"]["second"] you can write
|
15
17
|
# h["first.second"]
|
16
18
|
class PathedHash
|
19
|
+
include RecursiveMerge
|
20
|
+
|
17
21
|
##
|
18
22
|
# Initializer
|
19
23
|
def initialize(init = {})
|
20
|
-
|
24
|
+
if init.nil?
|
25
|
+
@data = {}
|
26
|
+
else
|
27
|
+
@data = init.dup
|
28
|
+
end
|
21
29
|
@separator = '.'
|
22
30
|
end
|
23
31
|
|
@@ -40,13 +48,39 @@ module Unobtainium
|
|
40
48
|
(READ_METHODS + WRITE_METHODS).each do |method|
|
41
49
|
# Wrap all accessor functions to deal with paths
|
42
50
|
define_method(method) do |*args, &block|
|
51
|
+
# If there are no arguments, there's nothing to do with paths. Just
|
52
|
+
# delegate to the hash.
|
53
|
+
if args.empty?
|
54
|
+
return @data.send(method, *args, &block)
|
55
|
+
end
|
56
|
+
|
43
57
|
# With any of the dispatch methods, we know that the first argument has
|
44
58
|
# to be a key. We'll try to split it by the path separator.
|
45
59
|
components = args[0].to_s.split(split_pattern)
|
60
|
+
loop do
|
61
|
+
if components.empty? or not components[0].empty?
|
62
|
+
break
|
63
|
+
end
|
64
|
+
components.shift
|
65
|
+
end
|
66
|
+
|
67
|
+
# If there are no components, return self/the root
|
68
|
+
if components.empty?
|
69
|
+
return self
|
70
|
+
end
|
46
71
|
|
47
72
|
# This PathedHash is already the leaf-most Hash
|
48
73
|
if components.length == 1
|
49
|
-
|
74
|
+
# Weird edge case: if we didn't have to shift anything, then it's
|
75
|
+
# possible we inadvertently changed a symbol key into a string key,
|
76
|
+
# which could mean looking fails.
|
77
|
+
# We can detect that by comparing copy[0] to a symbolized version of
|
78
|
+
# components[0].
|
79
|
+
copy = args.dup
|
80
|
+
if copy[0] != components[0].to_sym
|
81
|
+
copy[0] = components[0]
|
82
|
+
end
|
83
|
+
return @data.send(method, *copy, &block)
|
50
84
|
end
|
51
85
|
|
52
86
|
# Deal with other paths. The frustrating part here is that for nested
|
@@ -57,13 +91,6 @@ module Unobtainium
|
|
57
91
|
leaf = recursive_fetch(components, @data,
|
58
92
|
create: WRITE_METHODS.include?(method))
|
59
93
|
|
60
|
-
# If the leaf is nil, we can't send it any method without raising
|
61
|
-
# an error. We'll instead send the method to an empty hash, to mimic
|
62
|
-
# the correct behaviour.
|
63
|
-
if leaf.nil?
|
64
|
-
return {}.send(method, *args, &block)
|
65
|
-
end
|
66
|
-
|
67
94
|
# If we have a leaf, we want to send the requested method to that
|
68
95
|
# leaf.
|
69
96
|
copy = args.dup
|
@@ -76,6 +103,16 @@ module Unobtainium
|
|
76
103
|
@data.to_s
|
77
104
|
end
|
78
105
|
|
106
|
+
def dup
|
107
|
+
PathedHash.new(@data.dup)
|
108
|
+
end
|
109
|
+
|
110
|
+
def merge!(*args, &block)
|
111
|
+
# FIXME: we may need other methods like this. This is used by
|
112
|
+
# RecursiveMerge, so we know it's required.
|
113
|
+
PathedHash.new(super)
|
114
|
+
end
|
115
|
+
|
79
116
|
##
|
80
117
|
# Map any missing method to the driver implementation
|
81
118
|
def respond_to?(meth)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
#
|
3
|
+
# unobtainium
|
4
|
+
# https://github.com/jfinkhaeuser/unobtainium
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Unobtainium
|
11
|
+
##
|
12
|
+
# Provides recursive merge functions for hashes. Used in PathedHash.
|
13
|
+
module RecursiveMerge
|
14
|
+
def recursive_merge!(other, overwrite = true)
|
15
|
+
if other.nil?
|
16
|
+
return self
|
17
|
+
end
|
18
|
+
|
19
|
+
merger = proc do |_, v1, v2|
|
20
|
+
# rubocop:disable Style/GuardClause
|
21
|
+
if v1.is_a? Hash and v2.is_a? Hash
|
22
|
+
next v1.merge(v2, &merger)
|
23
|
+
elsif v1.is_a? Array and v2.is_a? Array
|
24
|
+
next v1 + v2
|
25
|
+
end
|
26
|
+
if overwrite
|
27
|
+
next v2
|
28
|
+
else
|
29
|
+
next v1
|
30
|
+
end
|
31
|
+
# rubocop:enable Style/GuardClause
|
32
|
+
end
|
33
|
+
merge!(other, &merger)
|
34
|
+
end
|
35
|
+
|
36
|
+
def recursive_merge(other, overwrite = true)
|
37
|
+
dup.recursive_merge!(other, overwrite)
|
38
|
+
end
|
39
|
+
end # module RecursiveMerge
|
40
|
+
end # module Unobtainium
|
data/lib/unobtainium/version.rb
CHANGED
data/lib/unobtainium/world.rb
CHANGED
@@ -53,6 +53,12 @@ module Unobtainium
|
|
53
53
|
options = config["drivers.#{label}"]
|
54
54
|
end
|
55
55
|
|
56
|
+
# The merged/extended options might define a "base"; that's the label
|
57
|
+
# we need to use.
|
58
|
+
if not options.nil? and not options["base"].nil?
|
59
|
+
label = options["base"]
|
60
|
+
end
|
61
|
+
|
56
62
|
# The driver may modify the options; if so, we should let it do that
|
57
63
|
# here. That way our key (below) is based on the expanded options.
|
58
64
|
label, options = ::Unobtainium::Driver.sanitize_options(label, options)
|
@@ -64,19 +70,21 @@ module Unobtainium
|
|
64
70
|
key = Digest::SHA1.hexdigest(key.to_s)
|
65
71
|
key = "driver-#{key}"
|
66
72
|
|
67
|
-
# Only create a driver with this exact configuration once
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
class << self
|
75
|
-
def driver_destructor(the_driver = nil)
|
73
|
+
# Only create a driver with this exact configuration once. Unfortunately
|
74
|
+
# We'll have to bind the destructor to whatever configuration exists at
|
75
|
+
# this point in time, so we have to create a proc here - whether the Driver
|
76
|
+
# gets created or not.
|
77
|
+
at_end = config.fetch("at_end", "quit")
|
78
|
+
dtor = proc do |the_driver|
|
76
79
|
if the_driver.nil?
|
77
80
|
return
|
78
81
|
end
|
79
|
-
|
82
|
+
|
83
|
+
meth = at_end.to_sym
|
84
|
+
the_driver.send(meth)
|
85
|
+
end
|
86
|
+
return ::Unobtainium::Runtime.instance.store_with_if(key, dtor) do
|
87
|
+
::Unobtainium::Driver.create(label, options)
|
80
88
|
end
|
81
89
|
end
|
82
90
|
end # module World
|
data/spec/config_spec.rb
CHANGED
@@ -79,4 +79,30 @@ describe ::Unobtainium::Config do
|
|
79
79
|
cfg = ::Unobtainium::Config.load_config(config)
|
80
80
|
expect(cfg).to be_empty
|
81
81
|
end
|
82
|
+
|
83
|
+
it "extends configuration hashes" do
|
84
|
+
config = File.join(@data_path, 'driverconfig.yml')
|
85
|
+
cfg = ::Unobtainium::Config.load_config(config)
|
86
|
+
|
87
|
+
# First, test for non-extended values
|
88
|
+
expect(cfg["drivers.mock.mockoption"]).to eql 42
|
89
|
+
expect(cfg["drivers.branch1.branch1option"]).to eql "foo"
|
90
|
+
expect(cfg["drivers.branch2.branch2option"]).to eql "bar"
|
91
|
+
expect(cfg["drivers.leaf.leafoption"]).to eql "baz"
|
92
|
+
|
93
|
+
# Now test extended values
|
94
|
+
expect(cfg["drivers.branch1.mockoption"]).to eql 42
|
95
|
+
expect(cfg["drivers.branch2.mockoption"]).to eql 42
|
96
|
+
expect(cfg["drivers.leaf.mockoption"]).to eql 42
|
97
|
+
|
98
|
+
expect(cfg["drivers.branch2.branch1option"]).to eql "foo"
|
99
|
+
expect(cfg["drivers.leaf.branch1option"]).to eql "override" # not "foo" !
|
100
|
+
|
101
|
+
expect(cfg["drivers.leaf.branch2option"]).to eql "bar"
|
102
|
+
|
103
|
+
# Also test that all levels go back to base == mock
|
104
|
+
expect(cfg["drivers.branch1.base"]).to eql 'mock'
|
105
|
+
expect(cfg["drivers.branch2.base"]).to eql 'mock'
|
106
|
+
expect(cfg["drivers.leaf.base"]).to eql 'mock'
|
107
|
+
end
|
82
108
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# the hierarchy is mock -> branch1 -> branch2 -> leaf
|
2
|
+
#
|
3
|
+
# We test
|
4
|
+
# a) having a base before its derived by ordering mock before branch1
|
5
|
+
# b) having a base after its derived by ordering branch2 after leaf
|
6
|
+
# c) overrides by having leaf override the option from branch1
|
7
|
+
#
|
8
|
+
# so the order in this file has to be mock -> branch2 -> branch1 -> leaf
|
9
|
+
---
|
10
|
+
drivers:
|
11
|
+
mock:
|
12
|
+
mockoption: 42
|
13
|
+
branch2:
|
14
|
+
extends: branch1
|
15
|
+
branch2option: bar
|
16
|
+
branch1:
|
17
|
+
extends: mock
|
18
|
+
branch1option: foo
|
19
|
+
leaf:
|
20
|
+
extends: branch2
|
21
|
+
leafoption: baz
|
22
|
+
branch1option: override
|
23
|
+
driver: leaf
|
data/spec/pathed_hash_spec.rb
CHANGED
@@ -13,6 +13,11 @@ describe ::Unobtainium::PathedHash do
|
|
13
13
|
expect(ph.empty?).to eql false
|
14
14
|
expect(ph[:foo]).to eql 42
|
15
15
|
end
|
16
|
+
|
17
|
+
it "can be constructed with a nil value" do
|
18
|
+
ph = ::Unobtainium::PathedHash.new(nil)
|
19
|
+
expect(ph.empty?).to eql true
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
18
23
|
describe "Hash-like" do
|
@@ -49,6 +54,20 @@ describe ::Unobtainium::PathedHash do
|
|
49
54
|
expect(ph["bar.nope"]).to eql nil
|
50
55
|
end
|
51
56
|
|
57
|
+
it "treats a single separator as the root" do
|
58
|
+
sample = { "foo" => 42 }
|
59
|
+
ph = ::Unobtainium::PathedHash.new(sample)
|
60
|
+
|
61
|
+
expect(ph[ph.separator]["foo"]).to eql 42
|
62
|
+
end
|
63
|
+
|
64
|
+
it "treats an empty path as the root" do
|
65
|
+
sample = { "foo" => 42 }
|
66
|
+
ph = ::Unobtainium::PathedHash.new(sample)
|
67
|
+
|
68
|
+
expect(ph[""]["foo"]).to eql 42
|
69
|
+
end
|
70
|
+
|
52
71
|
it "can recursively write entries via a path" do
|
53
72
|
ph = ::Unobtainium::PathedHash.new
|
54
73
|
ph["foo.bar"] = 42
|
@@ -60,4 +79,58 @@ describe ::Unobtainium::PathedHash do
|
|
60
79
|
ph = ::Unobtainium::PathedHash.new(h)
|
61
80
|
expect(ph.to_s).to eql h.to_s
|
62
81
|
end
|
82
|
+
|
83
|
+
it "understands absolute paths (starting with separator)" do
|
84
|
+
sample = {
|
85
|
+
"foo" => 42,
|
86
|
+
"bar" => {
|
87
|
+
"baz" => "quux",
|
88
|
+
"blah" => [1, 2],
|
89
|
+
}
|
90
|
+
}
|
91
|
+
ph = ::Unobtainium::PathedHash.new(sample)
|
92
|
+
|
93
|
+
expect(ph["bar.baz"]).to eql "quux"
|
94
|
+
expect(ph[".bar.baz"]).to eql "quux"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "recursively merges with overwriting" do
|
98
|
+
sample1 = {
|
99
|
+
"foo" => {
|
100
|
+
"bar" => 42,
|
101
|
+
"baz" => "quux",
|
102
|
+
}
|
103
|
+
}
|
104
|
+
sample2 = {
|
105
|
+
"foo" => {
|
106
|
+
"baz" => "override"
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
ph1 = ::Unobtainium::PathedHash.new(sample1)
|
111
|
+
ph2 = ph1.recursive_merge(sample2)
|
112
|
+
|
113
|
+
expect(ph2["foo.bar"]).to eql 42
|
114
|
+
expect(ph2["foo.baz"]).to eql "override"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "recursively merges without overwriting" do
|
118
|
+
sample1 = {
|
119
|
+
"foo" => {
|
120
|
+
"bar" => 42,
|
121
|
+
"baz" => "quux",
|
122
|
+
}
|
123
|
+
}
|
124
|
+
sample2 = {
|
125
|
+
"foo" => {
|
126
|
+
"baz" => "override"
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
ph1 = ::Unobtainium::PathedHash.new(sample1)
|
131
|
+
ph2 = ph1.recursive_merge(sample2, false)
|
132
|
+
|
133
|
+
expect(ph2["foo.bar"]).to eql 42
|
134
|
+
expect(ph2["foo.baz"]).to eql "quux"
|
135
|
+
end
|
63
136
|
end
|
data/spec/world_spec.rb
CHANGED
@@ -9,7 +9,7 @@ end # class Tester
|
|
9
9
|
describe ::Unobtainium::World do
|
10
10
|
before :each do
|
11
11
|
# Set configuration
|
12
|
-
path = File.join(File.dirname(__FILE__), 'data', '
|
12
|
+
path = File.join(File.dirname(__FILE__), 'data', 'driverconfig.yml')
|
13
13
|
::Unobtainium::World.config_file = path
|
14
14
|
|
15
15
|
# Load MockDriver
|
@@ -20,7 +20,7 @@ describe ::Unobtainium::World do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "loads the global config" do
|
23
|
-
expect(@tester.config["drivers.mock.
|
23
|
+
expect(@tester.config["drivers.mock.mockoption"]).to eql 42
|
24
24
|
end
|
25
25
|
|
26
26
|
it "creates a mock driver parameters" do
|
@@ -28,6 +28,10 @@ describe ::Unobtainium::World do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it "passed the config file options to the driver" do
|
31
|
-
expect(@tester.driver.passed_options["
|
31
|
+
expect(@tester.driver.passed_options["mockoption"]).to eql 42
|
32
|
+
end
|
33
|
+
|
34
|
+
it "extends driver options" do
|
35
|
+
expect(@tester.driver.passed_options["base"]).to eql "mock"
|
32
36
|
end
|
33
37
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unobtainium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Finkhaeuser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -101,6 +101,7 @@ files:
|
|
101
101
|
- Rakefile
|
102
102
|
- cuke/.gitignore
|
103
103
|
- cuke/Gemfile
|
104
|
+
- cuke/Gemfile.lock
|
104
105
|
- cuke/README.md
|
105
106
|
- cuke/config/config.yml
|
106
107
|
- cuke/features/step_definitions/steps.rb
|
@@ -112,6 +113,7 @@ files:
|
|
112
113
|
- lib/unobtainium/drivers/appium.rb
|
113
114
|
- lib/unobtainium/drivers/selenium.rb
|
114
115
|
- lib/unobtainium/pathed_hash.rb
|
116
|
+
- lib/unobtainium/recursive_merge.rb
|
115
117
|
- lib/unobtainium/runtime.rb
|
116
118
|
- lib/unobtainium/version.rb
|
117
119
|
- lib/unobtainium/world.rb
|
@@ -119,6 +121,7 @@ files:
|
|
119
121
|
- spec/data/array.yaml
|
120
122
|
- spec/data/arraymerge-local.yaml
|
121
123
|
- spec/data/arraymerge.yaml
|
124
|
+
- spec/data/driverconfig.yml
|
122
125
|
- spec/data/empty.yml
|
123
126
|
- spec/data/hash.yml
|
124
127
|
- spec/data/hashmerge-local.yml
|
@@ -164,6 +167,7 @@ test_files:
|
|
164
167
|
- spec/data/array.yaml
|
165
168
|
- spec/data/arraymerge-local.yaml
|
166
169
|
- spec/data/arraymerge.yaml
|
170
|
+
- spec/data/driverconfig.yml
|
167
171
|
- spec/data/empty.yml
|
168
172
|
- spec/data/hash.yml
|
169
173
|
- spec/data/hashmerge-local.yml
|