unobtainium 0.0.1 → 0.0.2
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 +0 -3
- data/Gemfile.lock +0 -23
- data/README.md +2 -0
- data/lib/unobtainium/config.rb +170 -0
- data/lib/unobtainium/driver.rb +79 -27
- data/lib/unobtainium/pathed_hash.rb +121 -0
- data/lib/unobtainium/runtime.rb +126 -0
- data/lib/unobtainium/version.rb +1 -1
- data/unobtainium.gemspec +6 -5
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 194953d1b77be115316598397b69e99037647085
|
4
|
+
data.tar.gz: 9194ccf829bcf5c6e98027251716b8598ea1979b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e43f762d2c90323173cdfeedc2d57e764114558282066bd28a9208ed1fb175deb71917b23cbc135e8f476770e5c396414a572dcb2814ecd5e93449999cb1a8a
|
7
|
+
data.tar.gz: 58fd4916229bf0528304c9cb18541f5b6f2431e958da9f7154b70f1a553ffc04e26be791a02442c0e858ce45e1d42e98bb8c8f9f48e6f9a7fa6866843432baac
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -6,21 +6,7 @@ PATH
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
appium_lib (8.0.2)
|
10
|
-
awesome_print (~> 1.6)
|
11
|
-
json (~> 1.8)
|
12
|
-
nokogiri (~> 1.6.6)
|
13
|
-
selenium-webdriver (~> 2.49)
|
14
|
-
tomlrb (~> 1.1)
|
15
9
|
ast (2.2.0)
|
16
|
-
awesome_print (1.6.1)
|
17
|
-
childprocess (0.5.9)
|
18
|
-
ffi (~> 1.0, >= 1.0.11)
|
19
|
-
ffi (1.9.10)
|
20
|
-
json (1.8.3)
|
21
|
-
mini_portile2 (2.0.0)
|
22
|
-
nokogiri (1.6.7.2)
|
23
|
-
mini_portile2 (~> 2.0.0.rc2)
|
24
10
|
parser (2.3.0.7)
|
25
11
|
ast (~> 2.2)
|
26
12
|
powerpack (0.1.1)
|
@@ -32,23 +18,14 @@ GEM
|
|
32
18
|
ruby-progressbar (~> 1.7)
|
33
19
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
34
20
|
ruby-progressbar (1.7.5)
|
35
|
-
rubyzip (1.2.0)
|
36
|
-
selenium-webdriver (2.53.0)
|
37
|
-
childprocess (~> 0.5)
|
38
|
-
rubyzip (~> 1.0)
|
39
|
-
websocket (~> 1.0)
|
40
|
-
tomlrb (1.2.0)
|
41
21
|
unicode-display_width (1.0.3)
|
42
|
-
websocket (1.2.2)
|
43
22
|
|
44
23
|
PLATFORMS
|
45
24
|
ruby
|
46
25
|
|
47
26
|
DEPENDENCIES
|
48
|
-
appium_lib
|
49
27
|
bundler (~> 1.11)
|
50
28
|
rubocop (~> 0.39)
|
51
|
-
selenium-webdriver
|
52
29
|
unobtainium!
|
53
30
|
|
54
31
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -12,6 +12,8 @@ so that test code can more easily cover:
|
|
12
12
|
Some additional useful functionality for the maintenance of test suites is
|
13
13
|
also added.
|
14
14
|
|
15
|
+
[](https://badge.fury.io/rb/unobtainium)
|
16
|
+
|
15
17
|
# Credits
|
16
18
|
This gem is inspired by [LapisLazuli](https://github.com/spriteCloud/lapis-lazuli),
|
17
19
|
but vastly less complex, and aims to stay so.
|
@@ -0,0 +1,170 @@
|
|
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
|
+
require 'pathname'
|
11
|
+
|
12
|
+
require 'unobtainium/pathed_hash'
|
13
|
+
|
14
|
+
module Unobtainium
|
15
|
+
##
|
16
|
+
# The Config class extends PathedHash by two main pieces of functionality:
|
17
|
+
# a) it loads configuration files and turns them into pathed hashes, and
|
18
|
+
# b) it treats environment variables as overriding anything contained in
|
19
|
+
# the configuration file.
|
20
|
+
#
|
21
|
+
# For configuration file loading, a named configuration file will be laoaded
|
22
|
+
# if present. A file with the same name but '-local' appended before the
|
23
|
+
# extension will be loaded as well, overriding any values in the original
|
24
|
+
# configuration file.
|
25
|
+
#
|
26
|
+
# For environment variable support, any environment variable named like a
|
27
|
+
# path into the configuration hash, but with separators transformed to
|
28
|
+
# underscore and all letters capitalized will override values from the
|
29
|
+
# configuration files under that path, i.e. FOO_BAR will override 'foo.bar'.
|
30
|
+
#
|
31
|
+
# Environment variables can contain JSON *only*; if the value can be parsed
|
32
|
+
# as JSON, it becomes a Hash in the configuration tree. If it cannot be parsed
|
33
|
+
# as JSON, it remains a string.
|
34
|
+
#
|
35
|
+
# Note: if your configuration file's top-level structure is an array, it will
|
36
|
+
# be returned as a hash with a 'config' key that maps to your file's contents.
|
37
|
+
#
|
38
|
+
class Config < PathedHash
|
39
|
+
# Very simple YAML parser
|
40
|
+
class YAMLParser
|
41
|
+
require 'yaml'
|
42
|
+
|
43
|
+
def self.parse(string)
|
44
|
+
YAML.load(string)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Very simple JSON parser
|
49
|
+
class JSONParser
|
50
|
+
require 'json'
|
51
|
+
|
52
|
+
def self.parse(string)
|
53
|
+
JSON.parse(string)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
PathedHash::READ_METHODS.each do |method|
|
58
|
+
# Wrap all read functions into something that checks for environment
|
59
|
+
# variables first.
|
60
|
+
define_method(method) do |*args, &block|
|
61
|
+
# We'll make it rather simple: since the first argument is a key, we
|
62
|
+
# will just transform it to the matching environment variable name,
|
63
|
+
# and see if that environment variable is set.
|
64
|
+
env_name = args[0].upcase.gsub(split_pattern, '_')
|
65
|
+
contents = ENV[env_name]
|
66
|
+
|
67
|
+
# No environment variable set? Fine, just do the usual thing.
|
68
|
+
if contents.nil?
|
69
|
+
return super(*args, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
# With an environment variable, we will try to parse it as JSON first.
|
73
|
+
begin
|
74
|
+
return JSONParser.parse(contents)
|
75
|
+
rescue JSON::ParserError
|
76
|
+
return contents
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
# Mapping of file name extensions to parser types.
|
83
|
+
FILE_TO_PARSER = {
|
84
|
+
'.yml' => YAMLParser,
|
85
|
+
'.yaml' => YAMLParser,
|
86
|
+
'.json' => JSONParser,
|
87
|
+
}.freeze
|
88
|
+
|
89
|
+
# If the config file contains an Array, this is what they key of the
|
90
|
+
# returned Hash will be.
|
91
|
+
ARRAY_KEY = 'config'.freeze
|
92
|
+
|
93
|
+
##
|
94
|
+
# Loads a configuration file with the given file name. The format is
|
95
|
+
# detected based on one of the extensions in FILE_TO_PARSER.
|
96
|
+
def load_config(path)
|
97
|
+
# Load base and local configuration files
|
98
|
+
base, config = load_base_config(path)
|
99
|
+
_, local_config = load_local_config(base)
|
100
|
+
|
101
|
+
# We can't sensibly merge arrays and hashes, so bail if the two classes
|
102
|
+
# don't match.
|
103
|
+
if config.class != local_config.class
|
104
|
+
raise ArgumentError, "Config file and local override file do not have "\
|
105
|
+
"the same top-level structure (hash or array), and therefore "\
|
106
|
+
"cannot be merged!"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Merge
|
110
|
+
if config.is_a? Array
|
111
|
+
config = { ARRAY_KEY => config.push(*local_config) }
|
112
|
+
elsif config.is_a? Hash
|
113
|
+
# rubocop:disable Style/CaseEquality
|
114
|
+
merger = proc do |_, v1, v2|
|
115
|
+
Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
|
116
|
+
end
|
117
|
+
# rubocop:enable Style/CaseEquality
|
118
|
+
config.merge!(local_config, &merger)
|
119
|
+
else
|
120
|
+
raise "Unexpected top-level structure in configuration file: "\
|
121
|
+
"#{config.class}"
|
122
|
+
end
|
123
|
+
|
124
|
+
return Config.new(config)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def load_base_config(path)
|
130
|
+
# Make sure the format is recognized early on.
|
131
|
+
base = Pathname.new(path)
|
132
|
+
formats = FILE_TO_PARSER.keys
|
133
|
+
if not formats.include?(base.extname)
|
134
|
+
raise ArgumentError, "Files with extension '#{base.extname}' are not"\
|
135
|
+
" recognized; please use one of #{formats}!"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Don't check the path whether it exists - loading a nonexistent
|
139
|
+
# file will throw a nice error for the user to catch.
|
140
|
+
file = base.open
|
141
|
+
contents = file.read
|
142
|
+
|
143
|
+
# Parse the contents.
|
144
|
+
config = FILE_TO_PARSER[base.extname].parse(contents)
|
145
|
+
|
146
|
+
return base, config
|
147
|
+
end
|
148
|
+
|
149
|
+
def load_local_config(base)
|
150
|
+
# Now construct a file name for a local override.
|
151
|
+
local = Pathname.new(base.dirname)
|
152
|
+
local = local.join(base.basename(base.extname).to_s + "-local" +
|
153
|
+
base.extname)
|
154
|
+
puts local
|
155
|
+
if not local.exist?
|
156
|
+
return Config.new(config)
|
157
|
+
end
|
158
|
+
|
159
|
+
# We know the local override file exists, but we do want to let any errors
|
160
|
+
# go through that come with reading or parsing it.
|
161
|
+
file = local.open
|
162
|
+
contents = file.read
|
163
|
+
|
164
|
+
local_config = FILE_TO_PARSER[base.extname].parse(contents)
|
165
|
+
|
166
|
+
return local, local_config
|
167
|
+
end
|
168
|
+
end # class << self
|
169
|
+
end # class Config
|
170
|
+
end # module Unobtainium
|
data/lib/unobtainium/driver.rb
CHANGED
@@ -23,23 +23,77 @@ module Unobtainium
|
|
23
23
|
##
|
24
24
|
# Create a driver instance with the given arguments
|
25
25
|
def create(*args)
|
26
|
-
|
26
|
+
new(*args)
|
27
27
|
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Add a new driver implementation. The first parameter is the class
|
31
|
+
# itself, the second should be a file path pointing to the file where
|
32
|
+
# the class is defined. You would typically pass __FILE__ for the second
|
33
|
+
# parameter.
|
34
|
+
#
|
35
|
+
# Using file names lets us figure out whether the class is a duplicate,
|
36
|
+
# or merely a second registration of the same class.
|
37
|
+
def register_implementation(klass, path)
|
38
|
+
# We need to deal with absolute paths only
|
39
|
+
fpath = File.absolute_path(path)
|
40
|
+
|
41
|
+
# Figure out if the class implements all the methods we need; we're not
|
42
|
+
# checking for anything else.
|
43
|
+
klass_methods = klass.methods - klass.instance_methods - Object.methods
|
44
|
+
|
45
|
+
if DRIVER_METHODS - klass_methods != []
|
46
|
+
raise LoadError, "Driver #{klass.name} is not implementing all of "\
|
47
|
+
"the class methods #{DRIVER_METHODS}, aborting!"
|
48
|
+
end
|
49
|
+
|
50
|
+
# The second question is whether the same class is already known, or
|
51
|
+
# whether a class with the same name but under a different location is
|
52
|
+
# known.
|
53
|
+
if @@drivers.include?(klass) and @@drivers[klass] != fpath
|
54
|
+
raise LoadError, "Driver #{klass.name} is duplicated in file "\
|
55
|
+
"'#{fpath}'; previous definition is here: "\
|
56
|
+
"'#{@@drivers[klass]}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
# If all of that was ok, we can register the implementation.
|
60
|
+
@@drivers[klass] = fpath
|
61
|
+
end
|
62
|
+
|
63
|
+
private :new
|
28
64
|
end # class << self
|
29
65
|
|
30
66
|
############################################################################
|
31
67
|
# Public methods
|
32
68
|
attr_reader :label, :options, :impl
|
33
69
|
|
70
|
+
##
|
71
|
+
# Map any missing method to the driver implementation
|
72
|
+
def respond_to?(meth)
|
73
|
+
if not @impl.nil? and @impl.respond_to?(meth)
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
return super
|
77
|
+
end
|
78
|
+
|
79
|
+
def method_missing(meth, *args, &block)
|
80
|
+
if not @impl.nil? and @impl.respond_to?(meth)
|
81
|
+
return @impl.send(meth.to_s, *args, &block)
|
82
|
+
end
|
83
|
+
return super
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
34
88
|
##
|
35
89
|
# Initializer
|
36
90
|
def initialize(*args)
|
37
|
-
# Sanitize options
|
38
|
-
@label, @options = sanitize_options(*args)
|
39
|
-
|
40
91
|
# Load drivers
|
41
92
|
load_drivers
|
42
93
|
|
94
|
+
# Sanitize options
|
95
|
+
@label, @options = sanitize_options(*args)
|
96
|
+
|
43
97
|
# Determine the driver class, if any
|
44
98
|
driver_klass = get_driver(@label)
|
45
99
|
if not driver_klass
|
@@ -54,8 +108,6 @@ module Unobtainium
|
|
54
108
|
@impl = driver_klass.create(@label, @options)
|
55
109
|
end
|
56
110
|
|
57
|
-
private
|
58
|
-
|
59
111
|
# Class variables have their place, rubocop... still, err on the strict
|
60
112
|
# side and just skip this check here.
|
61
113
|
# rubocop:disable Style/ClassVars
|
@@ -70,18 +122,31 @@ module Unobtainium
|
|
70
122
|
].freeze
|
71
123
|
|
72
124
|
##
|
73
|
-
#
|
125
|
+
# Ensures arguments are according to expectations.
|
74
126
|
def sanitize_options(*args)
|
75
|
-
|
76
|
-
|
77
|
-
|
127
|
+
if args.empty?
|
128
|
+
raise ArgumentError, "Need at least one argument specifying the driver!"
|
129
|
+
end
|
130
|
+
|
131
|
+
label = args[0].to_sym
|
132
|
+
|
133
|
+
options = nil
|
134
|
+
if args.length > 1
|
135
|
+
if not args[1].is_a? Hash
|
136
|
+
raise ArgumentError, "The second argument is expected to be an options "\
|
137
|
+
"hash!"
|
138
|
+
end
|
139
|
+
options = args[1]
|
140
|
+
end
|
141
|
+
|
142
|
+
return label, options
|
78
143
|
end
|
79
144
|
|
80
145
|
##
|
81
|
-
# Load drivers.
|
146
|
+
# Load drivers; this loads all driver implementations included in this gem.
|
147
|
+
# You can register external implementations with the :register_implementation
|
148
|
+
# method.
|
82
149
|
def load_drivers
|
83
|
-
# TODO: add load path for external drivers, or let them be specified via
|
84
|
-
# the driver environment/config variables.
|
85
150
|
pattern = File.join(File.dirname(__FILE__), 'drivers', '*.rb')
|
86
151
|
Dir.glob(pattern).each do |fpath|
|
87
152
|
# Determine class name from file name
|
@@ -92,20 +157,7 @@ module Unobtainium
|
|
92
157
|
require fpath
|
93
158
|
klassname = 'Unobtainium::Drivers::' + fname
|
94
159
|
klass = Object.const_get(klassname)
|
95
|
-
|
96
|
-
|
97
|
-
if DRIVER_METHODS - klass_methods != []
|
98
|
-
raise LoadError, "Driver #{klassname} is not implementing all of "\
|
99
|
-
"#{DRIVER_METHODS}, aborting!"
|
100
|
-
end
|
101
|
-
|
102
|
-
if @@drivers.include?(klass) and @@drivers[klass] != fpath
|
103
|
-
raise LoadError, "Driver #{klassname} is duplicated in file "\
|
104
|
-
"'#{fpath}'; previous definition is here: "\
|
105
|
-
"'#{@@drivers[klass]}'"
|
106
|
-
end
|
107
|
-
@@drivers[klass] = fpath
|
108
|
-
|
160
|
+
Driver.register_implementation(klass, fpath)
|
109
161
|
rescue LoadError => err
|
110
162
|
raise LoadError, "#{err.message}: unknown problem loading driver, "\
|
111
163
|
"aborting!"
|
@@ -0,0 +1,121 @@
|
|
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
|
+
##
|
13
|
+
# The PathedHash class wraps Hash by offering pathed access on top of
|
14
|
+
# regular access, i.e. instead of h["first"]["second"] you can write
|
15
|
+
# h["first.second"]
|
16
|
+
class PathedHash
|
17
|
+
##
|
18
|
+
# Initializer
|
19
|
+
def initialize(init = {})
|
20
|
+
@data = init
|
21
|
+
@separator = '.'
|
22
|
+
end
|
23
|
+
|
24
|
+
# The separator is the character or pattern splitting paths
|
25
|
+
attr_accessor :separator
|
26
|
+
|
27
|
+
READ_METHODS = [
|
28
|
+
:[], :default, :delete, :fetch, :has_key?, :include?, :key?, :member?,
|
29
|
+
].freeze
|
30
|
+
WRITE_METHODS = [
|
31
|
+
:[]=, :store,
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
##
|
35
|
+
# Returns the pattern to split paths at
|
36
|
+
def split_pattern
|
37
|
+
/(?<!\\)#{Regexp.escape(@separator)}/
|
38
|
+
end
|
39
|
+
|
40
|
+
(READ_METHODS + WRITE_METHODS).each do |method|
|
41
|
+
# Wrap all accessor functions to deal with paths
|
42
|
+
define_method(method) do |*args, &block|
|
43
|
+
# With any of the dispatch methods, we know that the first argument has
|
44
|
+
# to be a key. We'll try to split it by the path separator.
|
45
|
+
components = args[0].to_s.split(split_pattern)
|
46
|
+
|
47
|
+
# This PathedHash is already the leaf-most Hash
|
48
|
+
if components.length == 1
|
49
|
+
return @data.send(method, *args, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Deal with other paths. The frustrating part here is that for nested
|
53
|
+
# hashes, only this outermost one is guaranteed to know anything about
|
54
|
+
# path splitting, so we'll have to recurse down to the leaf here.
|
55
|
+
#
|
56
|
+
# For write methods, we need to create intermediary hashes.
|
57
|
+
leaf = recursive_fetch(components, @data,
|
58
|
+
create: WRITE_METHODS.include?(method))
|
59
|
+
|
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
|
+
# If we have a leaf, we want to send the requested method to that
|
68
|
+
# leaf.
|
69
|
+
copy = args.dup
|
70
|
+
copy[0] = components.last
|
71
|
+
return leaf.send(method, *copy, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_s
|
76
|
+
@data.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Map any missing method to the driver implementation
|
81
|
+
def respond_to?(meth)
|
82
|
+
if not @data.nil? and @data.respond_to?(meth)
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
return super
|
86
|
+
end
|
87
|
+
|
88
|
+
def method_missing(meth, *args, &block)
|
89
|
+
if not @data.nil? and @data.respond_to?(meth)
|
90
|
+
return @data.send(meth.to_s, *args, &block)
|
91
|
+
end
|
92
|
+
return super
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
##
|
98
|
+
# Given the path components, recursively fetch any but the last key.
|
99
|
+
def recursive_fetch(path, data, options = {})
|
100
|
+
# For the leaf element, we do nothing because that's where we want to
|
101
|
+
# dispatch to.
|
102
|
+
if path.length == 1
|
103
|
+
return data
|
104
|
+
end
|
105
|
+
|
106
|
+
# Split path into head and tail; for the next iteration, we'll look use only
|
107
|
+
# head, and pass tail on recursively.
|
108
|
+
head = path[0]
|
109
|
+
tail = path.slice(1, path.length)
|
110
|
+
|
111
|
+
# If we're a write function, then we need to create intermediary objects,
|
112
|
+
# i.e. what's at head if nothing is there.
|
113
|
+
if options[:create] and data.fetch(head, nil).nil?
|
114
|
+
data[head] = {}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Ok, recurse.
|
118
|
+
return recursive_fetch(tail, data[head], options)
|
119
|
+
end
|
120
|
+
end # class PathedHash
|
121
|
+
end # module Unobtainium
|
@@ -0,0 +1,126 @@
|
|
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
|
+
require 'singleton'
|
10
|
+
|
11
|
+
module Unobtainium
|
12
|
+
|
13
|
+
##
|
14
|
+
# The Runtime class is a singleton scoped to destroy itself when script
|
15
|
+
# execution stops. It's also an object map, which will destroy all object
|
16
|
+
# it contains when it destroys itself.
|
17
|
+
#
|
18
|
+
# Therefore, it can be used as a way to register object instances for
|
19
|
+
# destruction at script end.
|
20
|
+
class Runtime
|
21
|
+
include Singleton
|
22
|
+
|
23
|
+
##
|
24
|
+
# Initializer
|
25
|
+
def initialize
|
26
|
+
@objects = {}
|
27
|
+
|
28
|
+
# Create our own finalizer
|
29
|
+
ObjectSpace.define_finalizer(self) do
|
30
|
+
@objects.keys.each do |key|
|
31
|
+
unset(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Number of objects stored in the object map
|
38
|
+
def length
|
39
|
+
return @objects.length
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Does an object with the given name exist?
|
44
|
+
def has?(name)
|
45
|
+
return @objects.key?(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Store the given object under the given name. This overwrites any objects
|
50
|
+
# already stored under that name, which are destroyed before the new object
|
51
|
+
# is stored.
|
52
|
+
#
|
53
|
+
# If a destructor is passed, it is used to destroy the *new* object only.
|
54
|
+
# If no destructor is passed and the object responds to a :destroy method, that
|
55
|
+
# method is called.
|
56
|
+
def set(name, object, destructor = nil)
|
57
|
+
unset(name)
|
58
|
+
|
59
|
+
@objects[name] = [object, destructor]
|
60
|
+
|
61
|
+
return object
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Store the object returned by the block, if any. If no object is returned
|
66
|
+
# or no block is given, this function does nothing.
|
67
|
+
#
|
68
|
+
# Otherwise it works much like :set above.
|
69
|
+
def set_with(name, destructor = nil, &block)
|
70
|
+
object = nil
|
71
|
+
if not block.nil?
|
72
|
+
object = yield
|
73
|
+
end
|
74
|
+
|
75
|
+
if object.nil?
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
return set(name, object, destructor)
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Unsets (and destroys) any object found under the given name.
|
84
|
+
def unset(name)
|
85
|
+
if not @objects.key?(name)
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
obj, dtor = @objects[name]
|
90
|
+
@objects.delete(name)
|
91
|
+
destroy(obj, dtor)
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Returns the object with the given name, or the default value if no such
|
96
|
+
# object exists.
|
97
|
+
def fetch(name, default = nil)
|
98
|
+
return @objects.fetch(name, default)
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Similar to :fetch, but always returns nil for an object that could not
|
103
|
+
# be found.
|
104
|
+
def [](name)
|
105
|
+
return @objects[name]
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
##
|
111
|
+
# Destroy the given object with the destructor provided.
|
112
|
+
def destroy(object, destructor)
|
113
|
+
if not destructor.nil?
|
114
|
+
destructor.call
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
if not object.respond_to?(:destroy)
|
119
|
+
return
|
120
|
+
end
|
121
|
+
|
122
|
+
object.send(:destroy)
|
123
|
+
end
|
124
|
+
end # class Runtime
|
125
|
+
|
126
|
+
end # module Unobtainium
|
data/lib/unobtainium/version.rb
CHANGED
data/unobtainium.gemspec
CHANGED
@@ -20,11 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.email = ["jens@finkhaeuser.de"]
|
21
21
|
spec.description = %q(
|
22
22
|
Unobtainium wraps Selenium and Appium in a simple driver abstraction so that
|
23
|
-
test code can more easily cover
|
24
|
-
|
25
|
-
- Desktop browsers
|
26
|
-
- Mobile browsers
|
27
|
-
- Mobile apps
|
23
|
+
test code can more easily cover desktop browsers, mobile browsers and mobile
|
24
|
+
apps.
|
28
25
|
|
29
26
|
Some additional useful functionality for the maintenance of test suites is
|
30
27
|
also added.
|
@@ -40,6 +37,10 @@ Gem::Specification.new do |spec|
|
|
40
37
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
41
38
|
spec.require_paths = ["lib"]
|
42
39
|
|
40
|
+
spec.required_ruby_version = '>= 1.9'
|
41
|
+
|
42
|
+
spec.requirements = "Either or all of 'selenium-webdriver', 'appium_lib'"
|
43
|
+
|
43
44
|
spec.add_development_dependency "bundler", "~> 1.11"
|
44
45
|
spec.add_development_dependency "rubocop", "~> 0.39"
|
45
46
|
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.0.
|
4
|
+
version: 0.0.2
|
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-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -39,9 +39,9 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.39'
|
41
41
|
description: "\n Unobtainium wraps Selenium and Appium in a simple driver abstraction
|
42
|
-
so that\n test code can more easily cover
|
43
|
-
|
44
|
-
|
42
|
+
so that\n test code can more easily cover desktop browsers, mobile browsers and
|
43
|
+
mobile\n apps.\n\n Some additional useful functionality for the maintenance
|
44
|
+
of test suites is\n also added.\n "
|
45
45
|
email:
|
46
46
|
- jens@finkhaeuser.de
|
47
47
|
executables: []
|
@@ -55,9 +55,12 @@ files:
|
|
55
55
|
- LICENSE
|
56
56
|
- README.md
|
57
57
|
- lib/unobtainium.rb
|
58
|
+
- lib/unobtainium/config.rb
|
58
59
|
- lib/unobtainium/driver.rb
|
59
60
|
- lib/unobtainium/drivers/appium.rb
|
60
61
|
- lib/unobtainium/drivers/selenium.rb
|
62
|
+
- lib/unobtainium/pathed_hash.rb
|
63
|
+
- lib/unobtainium/runtime.rb
|
61
64
|
- lib/unobtainium/version.rb
|
62
65
|
- unobtainium.gemspec
|
63
66
|
homepage: https://github.com/jfinkhaeuser/unobtainium
|
@@ -72,13 +75,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
75
|
requirements:
|
73
76
|
- - ">="
|
74
77
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
78
|
+
version: '1.9'
|
76
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
80
|
requirements:
|
78
81
|
- - ">="
|
79
82
|
- !ruby/object:Gem::Version
|
80
83
|
version: '0'
|
81
|
-
requirements:
|
84
|
+
requirements:
|
85
|
+
- Either or all of 'selenium-webdriver', 'appium_lib'
|
82
86
|
rubyforge_project:
|
83
87
|
rubygems_version: 2.4.5.1
|
84
88
|
signing_key:
|