unobtainium 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 194953d1b77be115316598397b69e99037647085
4
- data.tar.gz: 9194ccf829bcf5c6e98027251716b8598ea1979b
3
+ metadata.gz: 9464c7e860902fed8b6f41813261a19368aea2aa
4
+ data.tar.gz: f9498b642d79da6d09cbebd854bda897c6b737d1
5
5
  SHA512:
6
- metadata.gz: 2e43f762d2c90323173cdfeedc2d57e764114558282066bd28a9208ed1fb175deb71917b23cbc135e8f476770e5c396414a572dcb2814ecd5e93449999cb1a8a
7
- data.tar.gz: 58fd4916229bf0528304c9cb18541f5b6f2431e958da9f7154b70f1a553ffc04e26be791a02442c0e858ce45e1d42e98bb8c8f9f48e6f9a7fa6866843432baac
6
+ metadata.gz: 07d45f57984c9ffe4d7c12d8fa7a189a6e22e0638a4dab0bed898f423ba73a9ae0f484247d0c23824513bf93f3eedfc32b7e4212e6515d751611c2b1c34eaee7
7
+ data.tar.gz: c1378459dc8affa7f5d2a2ce96cfcf5d240b2bf45c83158be0a1d3bf81795ae5024821c99eb3cd3e00617e606cea21c6b43f7ea3eda29b70213a43c5a78f3d30
data/.codeclimate.yml ADDED
@@ -0,0 +1,33 @@
1
+ ---
2
+ engines:
3
+ bundler-audit:
4
+ enabled: true
5
+ duplication:
6
+ enabled: true
7
+ exclude_fingerprints:
8
+ - d85d6f12c93d79ccd43868b8315d8816
9
+ - a8e2b4ccb258f16eda697c5f98e21823
10
+ - 442a316695836b4f4693fe3786cd3396
11
+ - 1c24bb5da72323796b645814cc006684
12
+ config:
13
+ languages:
14
+ - ruby
15
+ - javascript
16
+ - python
17
+ - php
18
+ fixme:
19
+ enabled: true
20
+ rubocop:
21
+ enabled: true
22
+ ratings:
23
+ paths:
24
+ - Gemfile.lock
25
+ - "**.inc"
26
+ - "**.js"
27
+ - "**.jsx"
28
+ - "**.module"
29
+ - "**.php"
30
+ - "**.py"
31
+ - "**.rb"
32
+ exclude_paths:
33
+ - spec/
data/.rubocop.yml CHANGED
@@ -52,3 +52,6 @@ Style/IfUnlessModifier:
52
52
 
53
53
  Style/TrailingCommaInLiteral:
54
54
  Enabled: false
55
+
56
+ Style/FirstParameterIndentation:
57
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
6
+ addons:
7
+ code_climate:
8
+ repo_token: 5dcf014e3a26ded8ffe8ff8bc70f98d49523e4851613123d8359035d44937953
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in unobtainium.gemspec
4
4
  gemspec
5
+
6
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/Gemfile.lock CHANGED
@@ -1,16 +1,35 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- unobtainium (0.0.1)
4
+ unobtainium (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.2.0)
10
+ codeclimate-test-reporter (0.5.0)
11
+ simplecov (>= 0.7.1, < 1.0.0)
12
+ diff-lcs (1.2.5)
13
+ docile (1.1.5)
14
+ json (1.8.3)
10
15
  parser (2.3.0.7)
11
16
  ast (~> 2.2)
12
17
  powerpack (0.1.1)
13
18
  rainbow (2.1.0)
19
+ rake (11.1.2)
20
+ rspec (3.4.0)
21
+ rspec-core (~> 3.4.0)
22
+ rspec-expectations (~> 3.4.0)
23
+ rspec-mocks (~> 3.4.0)
24
+ rspec-core (3.4.4)
25
+ rspec-support (~> 3.4.0)
26
+ rspec-expectations (3.4.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.4.0)
29
+ rspec-mocks (3.4.1)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.4.0)
32
+ rspec-support (3.4.1)
14
33
  rubocop (0.39.0)
15
34
  parser (>= 2.3.0.7, < 3.0)
16
35
  powerpack (~> 0.1)
@@ -18,6 +37,11 @@ GEM
18
37
  ruby-progressbar (~> 1.7)
19
38
  unicode-display_width (~> 1.0, >= 1.0.1)
20
39
  ruby-progressbar (1.7.5)
40
+ simplecov (0.11.2)
41
+ docile (~> 1.1.0)
42
+ json (~> 1.8)
43
+ simplecov-html (~> 0.10.0)
44
+ simplecov-html (0.10.0)
21
45
  unicode-display_width (1.0.3)
22
46
 
23
47
  PLATFORMS
@@ -25,7 +49,11 @@ PLATFORMS
25
49
 
26
50
  DEPENDENCIES
27
51
  bundler (~> 1.11)
52
+ codeclimate-test-reporter
53
+ rake (~> 11.1)
54
+ rspec (~> 3.4)
28
55
  rubocop (~> 0.39)
56
+ simplecov (~> 0.11)
29
57
  unobtainium!
30
58
 
31
59
  BUNDLED WITH
data/README.md CHANGED
@@ -13,6 +13,63 @@ Some additional useful functionality for the maintenance of test suites is
13
13
  also added.
14
14
 
15
15
  [![Gem Version](https://badge.fury.io/rb/unobtainium.svg)](https://badge.fury.io/rb/unobtainium)
16
+ [![Build status](https://travis-ci.org/jfinkhaeuser/unobtainium.svg?branch=master)](https://travis-ci.org/jfinkhaeuser/unobtainium)
17
+ [![Code Climate](https://codeclimate.com/github/jfinkhaeuser/unobtainium/badges/gpa.svg)](https://codeclimate.com/github/jfinkhaeuser/unobtainium)
18
+ [![Test Coverage](https://codeclimate.com/github/jfinkhaeuser/unobtainium/badges/coverage.svg)](https://codeclimate.com/github/jfinkhaeuser/unobtainium/coverage)
19
+
20
+ # Usage
21
+
22
+ You can use unobtainium on its own, or use it as part of a
23
+ [cucumber](https://cucumber.io/) test suite.
24
+
25
+ Unobtainium's functionality is in standalone classes, but it's all combined in
26
+ the `Unobtainium::World` module.
27
+
28
+ - The `PathedHash` class extends `Hash` by allowing paths to nested values, e.g.:
29
+ ```ruby
30
+ h = PathedHash.new { "foo" => { "bar" => 42 }}
31
+ h["foo.bar"] == 42 # true
32
+ ```
33
+ - The `Config` class is a `PathedHash`, but also reads JSON or YAML files to
34
+ initialize itself with values, and allows config paths to be overridden by
35
+ environment variables: `FOO_BAR` overrides the "foo.bar" path.
36
+
37
+ You can use JSON as environment variable values.
38
+ - The `Runtime` class is a singleton and a `Hash`-like container (but simpler),
39
+ that destroys all of its contents at the end of a script, calling custom
40
+ destructors if required. That allows for clean teardown and avoids everything
41
+ to have to implement the Singleton pattern itself.
42
+ - The `Driver` class, of course, wraps either of Appium or Selenium drivers:
43
+ ```ruby
44
+ drv = Driver.create(:firefox) # uses Selenium
45
+ drv = Driver.create(:android) # uses Appium
46
+
47
+ drv.navigate.to "..." # delegates to Selenium or Appium
48
+ ```
49
+
50
+ ## World
51
+
52
+ The World module combines all of the above by providing a simple entry point
53
+ for everything:
54
+
55
+ - `World.config_file` can be set to the path of a config file to be loaded,
56
+ defaulting to `config/config.yml`.
57
+ - `World#config` is a `Config` instance containing the above file's contents.
58
+ - `World#driver` returns a Driver, initialized to the settings contained in
59
+ the configuration file.
60
+
61
+ For a simple usage example of the World module, see the [cuke](./cuke)
62
+ subdirectory (used with cucumber).
63
+
64
+ ## Configuration File
65
+
66
+ The configuration file knows two configuration variables:
67
+
68
+ - `driver` is expected to be a string, specifying the driver to use as if it
69
+ was passed to `Driver.create` (see above), e.g. "android", "chrome", etc.
70
+ - `drivers` (note the trailing s) is a Hash. Under each key you can nest an
71
+ options hash you might otherwise pass to `Driver.create` as the second
72
+ parameter.
16
73
 
17
74
  # Credits
18
75
  This gem is inspired by [LapisLazuli](https://github.com/spriteCloud/lapis-lazuli),
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ # Rubocop
2
+ require 'rubocop/rake_task'
3
+ RuboCop::RakeTask.new
4
+
5
+ # Rspec
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ # Combined test task
10
+ desc "Test the code"
11
+ task :test do
12
+ Rake::Task[:rubocop].invoke
13
+ Rake::Task[:spec].invoke
14
+ end
15
+
16
+ # Default is the test task
17
+ task default: :test
data/cuke/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ C:\\nppdf32Log\\debuglog.txt
2
+ config/*-local.yml
data/cuke/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Cucumber
4
+ gem 'cucumber'
5
+
6
+ # For drivers
7
+ gem 'appium_lib'
8
+ gem 'selenium-webdriver'
9
+
10
+ # unobtainium itself; test code should point to the current repo and branch
11
+ repo = `git config --get remote.origin.url`.strip
12
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
13
+ gem 'unobtainium', git: repo, branch: branch
data/cuke/README.md ADDED
@@ -0,0 +1,2 @@
1
+ This directory contains tests for the integration with cucumber. You can also
2
+ run limited tests with the Selenium and Appium drivers from here.
@@ -0,0 +1,6 @@
1
+ ---
2
+ drivers:
3
+ firefox:
4
+ android:
5
+ browser: chrome
6
+ driver: firefox
@@ -0,0 +1,12 @@
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
+ Given(/^I navigate to the best website in the world$/) do
11
+ driver.navigate.to "http://finkhaeuser.de"
12
+ end
@@ -0,0 +1,11 @@
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 'unobtainium'
10
+
11
+ World(Unobtainium::World)
@@ -0,0 +1,4 @@
1
+ Feature: World
2
+
3
+ Scenario: Driver and configuration loading
4
+ Given I navigate to the best website in the world
data/lib/unobtainium.rb CHANGED
@@ -10,3 +10,6 @@
10
10
  require 'unobtainium/version'
11
11
 
12
12
  require 'unobtainium/driver'
13
+ require 'unobtainium/config'
14
+ require 'unobtainium/runtime'
15
+ require 'unobtainium/world'
@@ -14,9 +14,9 @@ require 'unobtainium/pathed_hash'
14
14
  module Unobtainium
15
15
  ##
16
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.
17
+ # - it loads configuration files and turns them into pathed hashes, and
18
+ # - it treats environment variables as overriding anything contained in
19
+ # the configuration file.
20
20
  #
21
21
  # For configuration file loading, a named configuration file will be laoaded
22
22
  # if present. A file with the same name but '-local' appended before the
@@ -34,7 +34,8 @@ module Unobtainium
34
34
  #
35
35
  # Note: if your configuration file's top-level structure is an array, it will
36
36
  # be returned as a hash with a 'config' key that maps to your file's contents.
37
- #
37
+ # That means that if you are trying to merge a hash with an array config, the
38
+ # result may be unexpected.
38
39
  class Config < PathedHash
39
40
  # Very simple YAML parser
40
41
  class YAMLParser
@@ -97,29 +98,22 @@ module Unobtainium
97
98
  # Load base and local configuration files
98
99
  base, config = load_base_config(path)
99
100
  _, 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!"
101
+ if local_config.nil?
102
+ return Config.new(config)
107
103
  end
108
104
 
109
105
  # 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
106
+ merger = proc do |_, v1, v2|
107
+ # rubocop:disable Style/GuardClause
108
+ if v1.is_a? Hash and v2.is_a? Hash
109
+ next v1.merge(v2, &merger)
110
+ elsif v1.is_a? Array and v2.is_a? Array
111
+ next v1 + v2
116
112
  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}"
113
+ next v2
114
+ # rubocop:enable Style/GuardClause
122
115
  end
116
+ config.merge!(local_config, &merger)
123
117
 
124
118
  return Config.new(config)
125
119
  end
@@ -143,7 +137,7 @@ module Unobtainium
143
137
  # Parse the contents.
144
138
  config = FILE_TO_PARSER[base.extname].parse(contents)
145
139
 
146
- return base, config
140
+ return base, hashify(config)
147
141
  end
148
142
 
149
143
  def load_local_config(base)
@@ -151,9 +145,8 @@ module Unobtainium
151
145
  local = Pathname.new(base.dirname)
152
146
  local = local.join(base.basename(base.extname).to_s + "-local" +
153
147
  base.extname)
154
- puts local
155
148
  if not local.exist?
156
- return Config.new(config)
149
+ return local, nil
157
150
  end
158
151
 
159
152
  # We know the local override file exists, but we do want to let any errors
@@ -163,7 +156,17 @@ module Unobtainium
163
156
 
164
157
  local_config = FILE_TO_PARSER[base.extname].parse(contents)
165
158
 
166
- return local, local_config
159
+ return local, hashify(local_config)
160
+ end
161
+
162
+ def hashify(data)
163
+ if data.nil?
164
+ return {}
165
+ end
166
+ if data.is_a? Array
167
+ data = { ARRAY_KEY => data }
168
+ end
169
+ return data
167
170
  end
168
171
  end # class << self
169
172
  end # class Config
@@ -132,7 +132,7 @@ module Unobtainium
132
132
 
133
133
  options = nil
134
134
  if args.length > 1
135
- if not args[1].is_a? Hash
135
+ if not args[1].nil? and not args[1].is_a? Hash
136
136
  raise ArgumentError, "The second argument is expected to be an options "\
137
137
  "hash!"
138
138
  end
@@ -28,7 +28,7 @@ module Unobtainium
28
28
  # Create our own finalizer
29
29
  ObjectSpace.define_finalizer(self) do
30
30
  @objects.keys.each do |key|
31
- unset(key)
31
+ delete(key)
32
32
  end
33
33
  end
34
34
  end
@@ -53,8 +53,8 @@ module Unobtainium
53
53
  # If a destructor is passed, it is used to destroy the *new* object only.
54
54
  # If no destructor is passed and the object responds to a :destroy method, that
55
55
  # method is called.
56
- def set(name, object, destructor = nil)
57
- unset(name)
56
+ def store(name, object, destructor = nil)
57
+ delete(name)
58
58
 
59
59
  @objects[name] = [object, destructor]
60
60
 
@@ -65,8 +65,8 @@ module Unobtainium
65
65
  # Store the object returned by the block, if any. If no object is returned
66
66
  # or no block is given, this function does nothing.
67
67
  #
68
- # Otherwise it works much like :set above.
69
- def set_with(name, destructor = nil, &block)
68
+ # Otherwise it works much like :store above.
69
+ def store_with(name, destructor = nil, &block)
70
70
  object = nil
71
71
  if not block.nil?
72
72
  object = yield
@@ -76,12 +76,30 @@ module Unobtainium
76
76
  return
77
77
  end
78
78
 
79
- return set(name, object, destructor)
79
+ return store(name, object, destructor)
80
80
  end
81
81
 
82
82
  ##
83
- # Unsets (and destroys) any object found under the given name.
84
- def unset(name)
83
+ # Like :store, but only stores the object if none exists for that key yet.
84
+ def store_if(name, object, destructor = nil)
85
+ if has?(name)
86
+ return self[name]
87
+ end
88
+ return store(name, object, destructor)
89
+ end
90
+
91
+ ##
92
+ # Like :store_if, but as a block version similar to :store_with.
93
+ def store_with_if(name, destructor = nil, &block)
94
+ if has?(name)
95
+ return self[name]
96
+ end
97
+ return store_with(name, destructor, &block)
98
+ end
99
+
100
+ ##
101
+ # Deletes (and destroys) any object found under the given name.
102
+ def delete(name)
85
103
  if not @objects.key?(name)
86
104
  return
87
105
  end
@@ -95,14 +113,23 @@ module Unobtainium
95
113
  # Returns the object with the given name, or the default value if no such
96
114
  # object exists.
97
115
  def fetch(name, default = nil)
98
- return @objects.fetch(name, default)
116
+ return @objects.fetch(name)[0]
117
+ rescue KeyError
118
+ if default.nil?
119
+ raise
120
+ end
121
+ return default
99
122
  end
100
123
 
101
124
  ##
102
125
  # Similar to :fetch, but always returns nil for an object that could not
103
126
  # be found.
104
127
  def [](name)
105
- return @objects[name]
128
+ val = @objects[name]
129
+ if val.nil?
130
+ return nil
131
+ end
132
+ return val[0]
106
133
  end
107
134
 
108
135
  private
@@ -111,8 +138,7 @@ module Unobtainium
111
138
  # Destroy the given object with the destructor provided.
112
139
  def destroy(object, destructor)
113
140
  if not destructor.nil?
114
- destructor.call
115
- return
141
+ return destructor.call(object)
116
142
  end
117
143
 
118
144
  if not object.respond_to?(:destroy)
@@ -7,5 +7,5 @@
7
7
  # All rights reserved.
8
8
  #
9
9
  module Unobtainium
10
- VERSION = "0.0.2".freeze
10
+ VERSION = "0.1.0".freeze
11
11
  end
@@ -0,0 +1,79 @@
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 'unobtainium'
10
+
11
+ require 'unobtainium/driver'
12
+ require 'unobtainium/config'
13
+ require 'unobtainium/runtime'
14
+
15
+ module Unobtainium
16
+ ##
17
+ # The World module combines other modules, defining simpler entry points
18
+ # into the gem's functionality.
19
+ module World
20
+ ##
21
+ # Modules can have class methods, too.
22
+ module ClassMethods
23
+ # Configuration related
24
+ def config_file=(name)
25
+ @config_file = name
26
+ end
27
+
28
+ def config_file
29
+ return @config_file || "config/config.yml"
30
+ end
31
+ end # module ClassMethods
32
+ extend ClassMethods
33
+
34
+ ##
35
+ # Return the global configuration, loaded from :config_file
36
+ def config
37
+ return ::Unobtainium::Runtime.instance.store_with_if(:config) do
38
+ ::Unobtainium::Config.load_config(::Unobtainium::World.config_file)
39
+ end
40
+ end
41
+
42
+ ##
43
+ # Returns a driver instance with the given options. If no options are
44
+ # provided, options from the global configuration are used.
45
+ def driver(label = nil, options = nil)
46
+ # Make sure we have a label for the driver
47
+ if label.nil?
48
+ label = config["driver"]
49
+ end
50
+
51
+ # Make sure we have options matching the driver
52
+ if options.nil?
53
+ options = config["drivers.#{label}"]
54
+ end
55
+
56
+ # Create a key for the label and options. This should always
57
+ # return the same key for the same label and options.
58
+ key = { label: label, options: options }
59
+ require 'digest/sha1'
60
+ key = Digest::SHA1.hexdigest(key.to_s)
61
+ key = "driver-#{key}"
62
+
63
+ # Only create a driver with this exact configuration once
64
+ dtor = ::Unobtainium::World.method(:driver_destructor)
65
+ return ::Unobtainium::Runtime.instance.store_with_if(key, dtor) do
66
+ ::Unobtainium::Driver.create(label, options)
67
+ end
68
+ end
69
+
70
+ class << self
71
+ def driver_destructor(the_driver = nil)
72
+ if the_driver.nil?
73
+ return
74
+ end
75
+ the_driver.close
76
+ end
77
+ end
78
+ end # module World
79
+ end # module Unobtainium
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/unobtainium/config'
3
+
4
+ describe ::Unobtainium::Config do
5
+ before :each do
6
+ @data_path = File.join(File.dirname(__FILE__), 'data')
7
+ end
8
+
9
+ it "fails to load a nonexistent file" do
10
+ expect { ::Unobtainium::Config.load_config("_nope_.yaml") }.to \
11
+ raise_error Errno::ENOENT
12
+ end
13
+
14
+ it "is asked to load an unrecognized extension" do
15
+ expect { ::Unobtainium::Config.load_config("_nope_.cfg") }.to \
16
+ raise_error ArgumentError
17
+ end
18
+
19
+ it "loads a yaml config with a top-level hash correctly" do
20
+ config = File.join(@data_path, 'hash.yml')
21
+ cfg = ::Unobtainium::Config.load_config(config)
22
+
23
+ expect(cfg["foo"]).to eql "bar"
24
+ expect(cfg["baz"]).to eql "quux"
25
+ end
26
+
27
+ it "loads a yaml config with a top-level array correctly" do
28
+ config = File.join(@data_path, 'array.yaml')
29
+ cfg = ::Unobtainium::Config.load_config(config)
30
+
31
+ expect(cfg["config"]).to eql %w(foo bar)
32
+ end
33
+
34
+ it "loads a JSON config correctly" do
35
+ config = File.join(@data_path, 'test.json')
36
+ cfg = ::Unobtainium::Config.load_config(config)
37
+
38
+ expect(cfg["foo"]).to eql "bar"
39
+ expect(cfg["baz"]).to eql 42
40
+ end
41
+
42
+ it "merges a hashed config correctly" do
43
+ config = File.join(@data_path, 'hashmerge.yml')
44
+ cfg = ::Unobtainium::Config.load_config(config)
45
+
46
+ expect(cfg["asdf"]).to eql 1
47
+ expect(cfg["foo.bar"]).to eql "baz"
48
+ expect(cfg["foo.quux"]).to eql [1, 42]
49
+ expect(cfg["foo.baz"]).to eql 3.14
50
+ expect(cfg["blargh"]).to eql false
51
+ end
52
+
53
+ it "merges an array config correctly" do
54
+ config = File.join(@data_path, 'arraymerge.yaml')
55
+ cfg = ::Unobtainium::Config.load_config(config)
56
+
57
+ expect(cfg["config"]).to eql %w(foo bar baz)
58
+ end
59
+
60
+ it "merges an array and hash config" do
61
+ config = File.join(@data_path, 'mergefail.yaml')
62
+ cfg = ::Unobtainium::Config.load_config(config)
63
+
64
+ expect(cfg["config"]).to eql %w(array in main config)
65
+ expect(cfg["local"]).to eql "override is a hash"
66
+ end
67
+
68
+ it "overrides configuration variables from the environment" do
69
+ config = File.join(@data_path, 'hash.yml')
70
+ cfg = ::Unobtainium::Config.load_config(config)
71
+
72
+ ENV["BAZ"] = "override"
73
+ expect(cfg["foo"]).to eql "bar"
74
+ expect(cfg["baz"]).to eql "override"
75
+ end
76
+
77
+ it "treats an empty YAML file as an empty hash" do
78
+ config = File.join(@data_path, 'empty.yml')
79
+ cfg = ::Unobtainium::Config.load_config(config)
80
+ expect(cfg).to be_empty
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ - foo
3
+ - bar
@@ -0,0 +1,2 @@
1
+ ---
2
+ - baz
@@ -0,0 +1,3 @@
1
+ ---
2
+ - foo
3
+ - bar
@@ -0,0 +1 @@
1
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ foo: bar
3
+ baz: quux
@@ -0,0 +1,4 @@
1
+ ---
2
+ blargh: false
3
+ foo:
4
+ baz: 3.14
@@ -0,0 +1,7 @@
1
+ ---
2
+ asdf: 1
3
+ foo:
4
+ bar: baz
5
+ quux:
6
+ - 1
7
+ - 42
@@ -0,0 +1,2 @@
1
+ ---
2
+ local: override is a hash
@@ -0,0 +1,5 @@
1
+ ---
2
+ - array
3
+ - in
4
+ - main
5
+ - config
@@ -0,0 +1,4 @@
1
+ {
2
+ "foo": "bar",
3
+ "baz": 42
4
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ drivers:
3
+ mock:
4
+ option: value
5
+ driver: mock
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/unobtainium/driver'
3
+ require_relative './mock_driver.rb'
4
+
5
+ class FakeDriver
6
+ end # class FakeDriver
7
+
8
+ describe ::Unobtainium::Driver do
9
+ before :each do
10
+ ::Unobtainium::Driver.register_implementation(MockDriver, "mock_driver.rb")
11
+ end
12
+
13
+ it "refuses to register a driver with missing methods" do
14
+ expect do
15
+ ::Unobtainium::Driver.register_implementation(FakeDriver, __FILE__)
16
+ end.to raise_error(LoadError)
17
+ end
18
+
19
+ it "refuses to register the same driver twice from different locations" do
20
+ expect do
21
+ ::Unobtainium::Driver.register_implementation(MockDriver, __FILE__)
22
+ end.to raise_error(LoadError)
23
+ end
24
+
25
+ it "verifies arguments" do
26
+ expect { ::Unobtainium::Driver.create }.to raise_error(ArgumentError)
27
+
28
+ expect do
29
+ ::Unobtainium::Driver.create(:mock, 1)
30
+ end.to raise_error(ArgumentError)
31
+
32
+ expect do
33
+ ::Unobtainium::Driver.create(:mock, [])
34
+ end.to raise_error(ArgumentError)
35
+
36
+ expect do
37
+ ::Unobtainium::Driver.create(:mock, "foo")
38
+ end.to raise_error(ArgumentError)
39
+ end
40
+
41
+ it "creates no driver with an unknown label" do
42
+ expect { ::Unobtainium::Driver.create(:nope) }.to raise_error(LoadError)
43
+ end
44
+
45
+ it "fails preconditions correctly" do
46
+ expect do
47
+ ::Unobtainium::Driver.create(:raise_mock)
48
+ end.to raise_error(RuntimeError)
49
+ end
50
+
51
+ it "creates a driver correctly" do
52
+ ::Unobtainium::Driver.create(:mock)
53
+ end
54
+
55
+ it "delegates to created driver class" do
56
+ drv = ::Unobtainium::Driver.create(:mock, foo: 42)
57
+ expect(drv.respond_to?(:passed_options)).to be_truthy
58
+ _ = drv.passed_options
59
+ end
60
+
61
+ it "passes options through correctly" do
62
+ drv = ::Unobtainium::Driver.create(:mock, foo: 42)
63
+ expect(drv.passed_options).to eql foo: 42
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ class Mock
2
+ attr_accessor :passed_options
3
+
4
+ def initialize(opts)
5
+ @passed_options = opts
6
+ end
7
+ end
8
+
9
+ # rubocop:disable Style/GuardClause
10
+ class MockDriver
11
+ class << self
12
+ def matches?(label)
13
+ label == :mock || label == :raise_mock
14
+ end
15
+
16
+ def ensure_preconditions(label, _)
17
+ if label == :raise_mock
18
+ raise "something"
19
+ end
20
+ end
21
+
22
+ def create(_, options)
23
+ Mock.new(options)
24
+ end
25
+ end
26
+ end # class MockDriver
27
+ # rubocop:enable Style/GuardClause
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/unobtainium/pathed_hash'
3
+
4
+ describe ::Unobtainium::PathedHash do
5
+ describe "#initialize" do
6
+ it "can be constructed without values" do
7
+ ph = ::Unobtainium::PathedHash.new
8
+ expect(ph.empty?).to eql true
9
+ end
10
+
11
+ it "can be constructed with values" do
12
+ ph = ::Unobtainium::PathedHash.new(foo: 42)
13
+ expect(ph.empty?).to eql false
14
+ expect(ph[:foo]).to eql 42
15
+ end
16
+ end
17
+
18
+ describe "Hash-like" do
19
+ it "responds to Hash functions" do
20
+ ph = ::Unobtainium::PathedHash.new
21
+ [:invert, :delete, :fetch].each do |meth|
22
+ expect(ph.respond_to?(meth)).to eql true
23
+ end
24
+ end
25
+
26
+ it "can be used like a hash" do
27
+ ph = ::Unobtainium::PathedHash.new(foo: 42)
28
+ inverted = ph.invert
29
+ expect(inverted.empty?).to eql false
30
+ expect(inverted[42]).to eql :foo
31
+ end
32
+ end
33
+
34
+ it "can recursively read entries via a path" do
35
+ sample = {
36
+ "foo" => 42,
37
+ "bar" => {
38
+ "baz" => "quux",
39
+ "blah" => [1, 2],
40
+ }
41
+ }
42
+ ph = ::Unobtainium::PathedHash.new(sample)
43
+
44
+ expect(ph["foo"]).to eql 42
45
+ expect(ph["bar.baz"]).to eql "quux"
46
+ expect(ph["bar.blah"]).to eql [1, 2]
47
+
48
+ expect(ph["nope"]).to eql nil
49
+ expect(ph["bar.nope"]).to eql nil
50
+ end
51
+
52
+ it "can recursively write entries via a path" do
53
+ ph = ::Unobtainium::PathedHash.new
54
+ ph["foo.bar"] = 42
55
+ expect(ph["foo.bar"]).to eql 42
56
+ end
57
+
58
+ it "has the same string representation as the hash it's initialized from" do
59
+ h = { foo: 42 }
60
+ ph = ::Unobtainium::PathedHash.new(h)
61
+ expect(ph.to_s).to eql h.to_s
62
+ end
63
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/unobtainium/runtime'
3
+
4
+ # rubocop:disable Style/ClassVars
5
+ class Foo
6
+ @@instances = 0
7
+
8
+ class << self
9
+ def instances
10
+ @@instances ||= 0
11
+ end
12
+
13
+ def instances=(value)
14
+ @@instances = value
15
+ end
16
+ end
17
+
18
+ def initialize
19
+ Foo.instances += 1
20
+ end
21
+
22
+ def destroy
23
+ Foo.instances -= 1
24
+ end
25
+ end # class Foo
26
+ # rubocop:enable Style/ClassVars
27
+
28
+ describe ::Unobtainium::Runtime do
29
+ it "is a singleton" do
30
+ expect { ::Unobtainium::Runtime.new }.to raise_error(NoMethodError)
31
+ first = ::Unobtainium::Runtime.instance
32
+ second = ::Unobtainium::Runtime.instance
33
+ expect(first).to eql second
34
+ end
35
+
36
+ it "can store objects" do
37
+ ::Unobtainium::Runtime.instance.store("foo", 42)
38
+
39
+ expect(::Unobtainium::Runtime.instance.has?("foo")).to be_truthy
40
+ expect(::Unobtainium::Runtime.instance.length).to eql 1
41
+ expect(::Unobtainium::Runtime.instance.fetch("foo")).to eql 42
42
+ expect(::Unobtainium::Runtime.instance["foo"]).to eql 42
43
+ end
44
+
45
+ it "deals well with default values" do
46
+ expect(::Unobtainium::Runtime.instance["bar"]).to be_nil
47
+ expect { ::Unobtainium::Runtime.instance.fetch("bar") }.to raise_error(
48
+ KeyError)
49
+ expect(::Unobtainium::Runtime.instance.fetch("bar", 123)).to eql 123
50
+ end
51
+
52
+ it "destroys deleted objects" do
53
+ expect(Foo.instances).to eql 0
54
+
55
+ ::Unobtainium::Runtime.instance.store("foo", Foo.new)
56
+ expect(Foo.instances).to eql 1
57
+
58
+ ::Unobtainium::Runtime.instance.delete("foo")
59
+ expect(Foo.instances).to eql 0
60
+ end
61
+
62
+ it "can use custom destructors" do
63
+ called = false
64
+ ::Unobtainium::Runtime.instance.store("foo", 666, proc { called = true })
65
+ expect(called).to be_falsy
66
+
67
+ ::Unobtainium::Runtime.instance.delete("foo")
68
+ expect(called).to be_truthy
69
+ end
70
+
71
+ it "can store objects created from a block" do
72
+ ::Unobtainium::Runtime.instance.store_with("foo") { 123 }
73
+ expect(::Unobtainium::Runtime.instance.fetch("foo")).to eql 123
74
+ end
75
+
76
+ it "ignores nil objects created from a block" do
77
+ ::Unobtainium::Runtime.instance.store_with("_nope_") { nil }
78
+ expect { ::Unobtainium::Runtime.instance.fetch("_nope_") }.to raise_error(
79
+ KeyError)
80
+ end
81
+
82
+ it "stores objects with :store_if" do
83
+ ::Unobtainium::Runtime.instance.store_if("store_if", 42)
84
+ expect(::Unobtainium::Runtime.instance.fetch("store_if")).to eql 42
85
+ end
86
+
87
+ it "does not overwrite objects with :store_if" do
88
+ ::Unobtainium::Runtime.instance.store("foo", 42)
89
+ ::Unobtainium::Runtime.instance.store_if("foo", 123)
90
+ expect(::Unobtainium::Runtime.instance.fetch("foo")).to eql 42
91
+ end
92
+
93
+ it "stores objects with :store_with_if" do
94
+ ::Unobtainium::Runtime.instance.store_with_if("store_with_if") do
95
+ 42
96
+ end
97
+ expect(::Unobtainium::Runtime.instance.fetch("store_with_if")).to eql 42
98
+ end
99
+
100
+ it "does not overwrite objects with :store_with_if" do
101
+ ::Unobtainium::Runtime.instance.store("foo", 42)
102
+ called = false
103
+ ::Unobtainium::Runtime.instance.store_with_if("foo") do
104
+ called = true
105
+ 123
106
+ end
107
+ expect(::Unobtainium::Runtime.instance.fetch("foo")).to eql 42
108
+ expect(called).to be_falsy
109
+ end
110
+ end
@@ -0,0 +1,7 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter 'unobtainium/drivers'
4
+ end
5
+
6
+ require "codeclimate-test-reporter"
7
+ CodeClimate::TestReporter.start
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/unobtainium/world'
3
+ require_relative './mock_driver.rb'
4
+
5
+ class Tester
6
+ include ::Unobtainium::World
7
+ end # class Tester
8
+
9
+ describe ::Unobtainium::World do
10
+ before :each do
11
+ # Set configuration
12
+ path = File.join(File.dirname(__FILE__), 'data', 'world.yml')
13
+ ::Unobtainium::World.config_file = path
14
+
15
+ # Load MockDriver
16
+ ::Unobtainium::Driver.register_implementation(MockDriver, "mock_driver.rb")
17
+
18
+ # Create tester object
19
+ @tester = Tester.new
20
+ end
21
+
22
+ it "loads the global config" do
23
+ expect(@tester.config["drivers.mock.option"]).to eql "value"
24
+ end
25
+
26
+ it "creates a mock driver parameters" do
27
+ expect(@tester.driver.respond_to?(:passed_options)).to be_truthy
28
+ end
29
+
30
+ it "passed the config file options to the driver" do
31
+ expect(@tester.driver.passed_options["option"]).to eql "value"
32
+ end
33
+ end
data/unobtainium.gemspec CHANGED
@@ -37,12 +37,15 @@ Gem::Specification.new do |spec|
37
37
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
38
38
  spec.require_paths = ["lib"]
39
39
 
40
- spec.required_ruby_version = '>= 1.9'
40
+ spec.required_ruby_version = '>= 2.0'
41
41
 
42
42
  spec.requirements = "Either or all of 'selenium-webdriver', 'appium_lib'"
43
43
 
44
44
  spec.add_development_dependency "bundler", "~> 1.11"
45
45
  spec.add_development_dependency "rubocop", "~> 0.39"
46
+ spec.add_development_dependency "rake", "~> 11.1"
47
+ spec.add_development_dependency "rspec", "~> 3.4"
48
+ spec.add_development_dependency "simplecov", "~> 0.11"
46
49
  end
47
50
  # rubocop:enable Style/SpaceAroundOperators
48
51
  # rubocop:enable Style/UnneededPercentQ, Style/ExtraSpacing
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.2
4
+ version: 0.1.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-08 00:00:00.000000000 Z
11
+ date: 2016-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,48 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.39'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '11.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '11.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.11'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.11'
41
83
  description: "\n Unobtainium wraps Selenium and Appium in a simple driver abstraction
42
84
  so that\n test code can more easily cover desktop browsers, mobile browsers and
43
85
  mobile\n apps.\n\n Some additional useful functionality for the maintenance
@@ -48,12 +90,22 @@ executables: []
48
90
  extensions: []
49
91
  extra_rdoc_files: []
50
92
  files:
93
+ - ".codeclimate.yml"
51
94
  - ".gitignore"
52
95
  - ".rubocop.yml"
96
+ - ".travis.yml"
53
97
  - Gemfile
54
98
  - Gemfile.lock
55
99
  - LICENSE
56
100
  - README.md
101
+ - Rakefile
102
+ - cuke/.gitignore
103
+ - cuke/Gemfile
104
+ - cuke/README.md
105
+ - cuke/config/config.yml
106
+ - cuke/features/step_definitions/steps.rb
107
+ - cuke/features/support/env.rb
108
+ - cuke/features/world.feature
57
109
  - lib/unobtainium.rb
58
110
  - lib/unobtainium/config.rb
59
111
  - lib/unobtainium/driver.rb
@@ -62,6 +114,25 @@ files:
62
114
  - lib/unobtainium/pathed_hash.rb
63
115
  - lib/unobtainium/runtime.rb
64
116
  - lib/unobtainium/version.rb
117
+ - lib/unobtainium/world.rb
118
+ - spec/config_spec.rb
119
+ - spec/data/array.yaml
120
+ - spec/data/arraymerge-local.yaml
121
+ - spec/data/arraymerge.yaml
122
+ - spec/data/empty.yml
123
+ - spec/data/hash.yml
124
+ - spec/data/hashmerge-local.yml
125
+ - spec/data/hashmerge.yml
126
+ - spec/data/mergefail-local.yaml
127
+ - spec/data/mergefail.yaml
128
+ - spec/data/test.json
129
+ - spec/data/world.yml
130
+ - spec/driver_spec.rb
131
+ - spec/mock_driver.rb
132
+ - spec/pathed_hash_spec.rb
133
+ - spec/runtime_spec.rb
134
+ - spec/spec_helper.rb
135
+ - spec/world_spec.rb
65
136
  - unobtainium.gemspec
66
137
  homepage: https://github.com/jfinkhaeuser/unobtainium
67
138
  licenses:
@@ -75,7 +146,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
75
146
  requirements:
76
147
  - - ">="
77
148
  - !ruby/object:Gem::Version
78
- version: '1.9'
149
+ version: '2.0'
79
150
  required_rubygems_version: !ruby/object:Gem::Requirement
80
151
  requirements:
81
152
  - - ">="
@@ -88,5 +159,23 @@ rubygems_version: 2.4.5.1
88
159
  signing_key:
89
160
  specification_version: 4
90
161
  summary: 'Obtain the unobtainable: test code covering multiple platforms'
91
- test_files: []
162
+ test_files:
163
+ - spec/config_spec.rb
164
+ - spec/data/array.yaml
165
+ - spec/data/arraymerge-local.yaml
166
+ - spec/data/arraymerge.yaml
167
+ - spec/data/empty.yml
168
+ - spec/data/hash.yml
169
+ - spec/data/hashmerge-local.yml
170
+ - spec/data/hashmerge.yml
171
+ - spec/data/mergefail-local.yaml
172
+ - spec/data/mergefail.yaml
173
+ - spec/data/test.json
174
+ - spec/data/world.yml
175
+ - spec/driver_spec.rb
176
+ - spec/mock_driver.rb
177
+ - spec/pathed_hash_spec.rb
178
+ - spec/runtime_spec.rb
179
+ - spec/spec_helper.rb
180
+ - spec/world_spec.rb
92
181
  has_rdoc: