chemistrykit 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -1
- data/.travis.yml +0 -4
- data/CHANGELOG.md +29 -0
- data/Rakefile +66 -53
- data/bin/ckit +3 -1
- data/chemistrykit.gemspec +8 -3
- data/features/brew.feature +18 -17
- data/features/catalyst.feature +4 -4
- data/features/concurrency.feature +61 -0
- data/features/exit_status.feature +5 -5
- data/features/global-config.feature +26 -0
- data/features/load_page_objects.feature +4 -4
- data/features/multi-config.feature +10 -10
- data/features/step_definitions/steps.rb +5 -3
- data/features/support/env.rb +4 -2
- data/lib/chemistrykit.rb +2 -0
- data/lib/chemistrykit/catalyst.rb +5 -4
- data/lib/chemistrykit/cli/beaker.rb +3 -0
- data/lib/chemistrykit/cli/cli.rb +54 -28
- data/lib/chemistrykit/cli/formula.rb +5 -2
- data/lib/chemistrykit/cli/helpers/formula_loader.rb +32 -19
- data/lib/chemistrykit/cli/new.rb +5 -2
- data/lib/chemistrykit/configuration.rb +54 -0
- data/lib/chemistrykit/formula/base.rb +6 -3
- data/lib/chemistrykit/parallel_tests_mods.rb +55 -0
- data/lib/chemistrykit/shared_context.rb +13 -4
- data/lib/templates/chemistrykit/config.yaml.tt +22 -17
- data/lib/templates/chemistrykit/formulas/lib/formula.rb +5 -2
- data/spec/chemistrykit/catalyst_spec.rb +5 -9
- data/spec/chemistrykit/cli/helpers/formula_loader_spec.rb +15 -13
- data/spec/chemistrykit/configuration_spec.rb +56 -0
- data/spec/chemistrykit/formula/base_spec.rb +4 -2
- data/spec/spec_helper.rb +4 -1
- data/spec/support/config.yaml +7 -0
- metadata +65 -6
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Initialize a global configuration
|
2
|
+
In order to configure chemistrykit
|
3
|
+
As a chemistry kit harness developer
|
4
|
+
I want to specify configurations in a central file so they are available in test scripts
|
5
|
+
|
6
|
+
Scenario: Use a configuration option in a beaker
|
7
|
+
Given I run `ckit new global-config-test`
|
8
|
+
And I cd to "global-config-test"
|
9
|
+
And a file named "beakers/test_beaker.rb" with:
|
10
|
+
"""
|
11
|
+
describe "Cheese", :depth => 'shallow' do
|
12
|
+
it "loads an external web page" do
|
13
|
+
@driver.get @config.base_url
|
14
|
+
@driver.title.should include("Google")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
"""
|
18
|
+
And I overwrite config.yaml with:
|
19
|
+
"""
|
20
|
+
base_url: http://www.google.com
|
21
|
+
selenium_connect:
|
22
|
+
log: 'evidence'
|
23
|
+
host: 'localhost'
|
24
|
+
"""
|
25
|
+
When I run `ckit brew`
|
26
|
+
Then the stdout should contain "1 example, 0 failures"
|
@@ -6,9 +6,9 @@ Formulas should be loaded in the correct order with thier dependencies
|
|
6
6
|
And I cd to "big-project"
|
7
7
|
And a file named "config.yaml" with:
|
8
8
|
"""
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
selenium_connect:
|
10
|
+
log: 'evidence'
|
11
|
+
host: 'localhost'
|
12
12
|
"""
|
13
13
|
And a file named "formulas/big.rb" with:
|
14
14
|
"""
|
@@ -34,7 +34,7 @@ Formulas should be loaded in the correct order with thier dependencies
|
|
34
34
|
end
|
35
35
|
"""
|
36
36
|
|
37
|
-
And a file named "
|
37
|
+
And a file named "beakers/big_beaker.rb" with:
|
38
38
|
"""
|
39
39
|
describe "Big", :depth => 'shallow' do
|
40
40
|
let(:book) { Formulas::BigProject.new(@driver) }
|
@@ -6,7 +6,7 @@ Feature: Support for multiple configuration files
|
|
6
6
|
Background:
|
7
7
|
Given I run `ckit new config-test`
|
8
8
|
And I cd to "config-test"
|
9
|
-
And a file named "
|
9
|
+
And a file named "beakers/test_beaker.rb" with:
|
10
10
|
"""
|
11
11
|
describe "Cheese", :depth => 'shallow' do
|
12
12
|
it "loads an external web page" do
|
@@ -19,9 +19,9 @@ Feature: Support for multiple configuration files
|
|
19
19
|
Given a directory named "evidence_config"
|
20
20
|
When I overwrite config.yaml with:
|
21
21
|
"""
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
selenium_connect:
|
23
|
+
log: 'evidence_config'
|
24
|
+
host: 'localhost'
|
25
25
|
"""
|
26
26
|
When I run `ckit brew`
|
27
27
|
Then the stdout should contain "1 example, 0 failures"
|
@@ -32,9 +32,9 @@ Feature: Support for multiple configuration files
|
|
32
32
|
Given a directory named "evidence_alternate"
|
33
33
|
And a file named "alternate.yaml" with:
|
34
34
|
"""
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
selenium_connect:
|
36
|
+
log: 'evidence_alternate'
|
37
|
+
host: 'localhost'
|
38
38
|
"""
|
39
39
|
When I run `ckit brew --config alternate.yaml`
|
40
40
|
Then the stdout should contain "1 example, 0 failures"
|
@@ -45,9 +45,9 @@ Feature: Support for multiple configuration files
|
|
45
45
|
Given a directory named "evidence_alternate"
|
46
46
|
And a file named "alternate.yaml" with:
|
47
47
|
"""
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
selenium_connect:
|
49
|
+
log: 'evidence_alternate'
|
50
|
+
host: 'localhost'
|
51
51
|
"""
|
52
52
|
When I run `ckit brew -c alternate.yaml`
|
53
53
|
Then the stdout should contain "1 example, 0 failures"
|
@@ -1,10 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
When /^I overwrite ([^"]*) with:$/ do | file_name, file_content |
|
4
|
+
# Modified from https://github.com/cucumber/aruba/blob/master/lib/aruba/cucumber.rb
|
3
5
|
require 'erb'
|
4
6
|
data = ERB.new(file_content)
|
5
7
|
overwrite_file(file_name, data.result)
|
6
8
|
end
|
7
9
|
|
8
10
|
Then(/^the exit code should be (\d+)$/) do |exit_status|
|
9
|
-
|
11
|
+
@last_exit_status.should == exit_status.to_i
|
10
12
|
end
|
data/features/support/env.rb
CHANGED
data/lib/chemistrykit.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
1
3
|
require 'csv'
|
2
4
|
|
3
5
|
module ChemistryKit
|
6
|
+
# Serves as a hash wrapper class for injecting data into formulas
|
4
7
|
class Catalyst
|
5
|
-
#this class serves as a standard container for data that can be injected into a formula
|
8
|
+
# this class serves as a standard container for data that can be injected into a formula
|
6
9
|
|
7
10
|
def initialize(data_file)
|
8
11
|
@data = {}
|
@@ -24,9 +27,7 @@ module ChemistryKit
|
|
24
27
|
private
|
25
28
|
|
26
29
|
def validate_key(key)
|
27
|
-
unless @data.has_key?(key.to_sym)
|
28
|
-
raise "Unknown \"#{key}\""
|
29
|
-
end
|
30
|
+
raise "Unknown \"#{key}\"" unless @data.has_key?(key.to_sym)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
data/lib/chemistrykit/cli/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
1
3
|
require 'thor'
|
2
4
|
require 'rspec'
|
3
5
|
require 'ci/reporter/rake/rspec_loader'
|
@@ -7,45 +9,55 @@ require 'chemistrykit/cli/beaker'
|
|
7
9
|
require 'chemistrykit/cli/helpers/formula_loader'
|
8
10
|
require 'chemistrykit/catalyst'
|
9
11
|
require 'chemistrykit/formula/base'
|
12
|
+
require 'selenium-connect'
|
13
|
+
require 'chemistrykit/configuration'
|
10
14
|
|
11
15
|
module ChemistryKit
|
12
16
|
module CLI
|
13
17
|
|
18
|
+
# Registers the formula and beaker commands
|
14
19
|
class Generate < Thor
|
15
20
|
register(ChemistryKit::CLI::FormulaGenerator, 'formula', 'formula [NAME]', 'generates a page object')
|
16
21
|
register(ChemistryKit::CLI::BeakerGenerator, 'beaker', 'beaker [NAME]', 'generates a beaker')
|
17
22
|
end
|
18
23
|
|
24
|
+
# Main Chemistry Kit CLI Class
|
19
25
|
class CKitCLI < Thor
|
20
26
|
|
21
27
|
register(ChemistryKit::CLI::New, 'new', 'new [NAME]', 'Creates a new ChemistryKit project')
|
22
28
|
check_unknown_options!
|
23
29
|
default_task :help
|
24
30
|
|
25
|
-
desc
|
26
|
-
subcommand
|
31
|
+
desc 'generate SUBCOMMAND', 'generate <formula> or <beaker> [NAME]'
|
32
|
+
subcommand 'generate', Generate
|
27
33
|
|
28
|
-
desc
|
29
|
-
method_option :params, :
|
30
|
-
method_option :tag, :
|
31
|
-
method_option :config, :
|
32
|
-
#TODO there should be a facility to simply pass a path to this command
|
33
|
-
method_option :
|
34
|
+
desc 'brew', 'Run ChemistryKit'
|
35
|
+
method_option :params, type: :hash
|
36
|
+
method_option :tag, default: ['depth:shallow'], type: :array
|
37
|
+
method_option :config, default: 'config.yaml', aliases: '-c', desc: 'Supply alternative config file.'
|
38
|
+
# TODO there should be a facility to simply pass a path to this command
|
39
|
+
method_option :beakers, type: :array
|
40
|
+
# This is set if the thread is being run in parallel so as not to trigger recursive concurency
|
41
|
+
method_option :parallel, default: false
|
34
42
|
|
35
43
|
def brew
|
36
|
-
load_config
|
37
|
-
|
44
|
+
config = load_config options['config']
|
45
|
+
# TODO perhaps the params should be rolled into the available
|
46
|
+
# config object injected into the system?
|
38
47
|
pass_params if options['params']
|
39
48
|
turn_stdout_stderr_on_off
|
40
49
|
set_logs_dir
|
41
50
|
load_page_objects
|
42
51
|
setup_tags
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
52
|
+
# configure rspec
|
53
|
+
rspec_config(config)
|
54
|
+
# get those beakers that should be executed
|
55
|
+
beakers = options['beakers'] ? options['beakers'] : Dir.glob(File.join(Dir.getwd, 'beakers/*'))
|
56
|
+
# based on concurrency parameter run tests
|
57
|
+
if config.concurrency > 1 && ! options['parallel']
|
58
|
+
run_in_parallel beakers, config.concurrency
|
47
59
|
else
|
48
|
-
run_rspec
|
60
|
+
run_rspec beakers
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
@@ -59,7 +71,7 @@ module ChemistryKit
|
|
59
71
|
|
60
72
|
def load_page_objects
|
61
73
|
loader = ChemistryKit::CLI::Helpers::FormulaLoader.new
|
62
|
-
loader.get_formulas(File.join(Dir.getwd, 'formulas')).each {|file| require file }
|
74
|
+
loader.get_formulas(File.join(Dir.getwd, 'formulas')).each { |file| require file }
|
63
75
|
end
|
64
76
|
|
65
77
|
def set_logs_dir
|
@@ -70,10 +82,9 @@ module ChemistryKit
|
|
70
82
|
ENV['CI_CAPTURE'] = 'on'
|
71
83
|
end
|
72
84
|
|
73
|
-
def load_config
|
74
|
-
|
75
|
-
|
76
|
-
ENV['CONFIG_FILE'] = options['config']
|
85
|
+
def load_config(file_name)
|
86
|
+
config_file = File.join(Dir.getwd, file_name)
|
87
|
+
ChemistryKit::Configuration.initialize_with_yaml config_file
|
77
88
|
end
|
78
89
|
|
79
90
|
def setup_tags
|
@@ -91,25 +102,40 @@ module ChemistryKit
|
|
91
102
|
end
|
92
103
|
end
|
93
104
|
|
94
|
-
def rspec_config #Some of these bits work and others don't
|
105
|
+
def rspec_config(config) # Some of these bits work and others don't
|
106
|
+
SeleniumConnect.configure do |c|
|
107
|
+
c.populate_with_hash config.selenium_connect
|
108
|
+
end
|
95
109
|
RSpec.configure do |c|
|
96
110
|
c.treat_symbols_as_metadata_keys_with_true_values = true
|
97
111
|
c.filter_run @tags[:filter] unless @tags[:filter].nil?
|
98
112
|
c.filter_run_excluding @tags[:exclusion_filter] unless @tags[:exclusion_filter].nil?
|
99
|
-
c.
|
113
|
+
c.before(:each) do
|
114
|
+
@driver = SeleniumConnect.start
|
115
|
+
@config = config
|
116
|
+
end
|
117
|
+
c.after(:each) do
|
118
|
+
@driver.quit
|
119
|
+
end
|
120
|
+
c.after(:all) do
|
121
|
+
SeleniumConnect.finish
|
122
|
+
end
|
100
123
|
c.order = 'random'
|
101
124
|
c.default_path = 'beakers'
|
102
125
|
c.pattern = '**/*_beaker.rb'
|
103
126
|
end
|
104
127
|
end
|
105
128
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
129
|
+
def run_in_parallel(beakers, concurrency)
|
130
|
+
require 'parallel_tests'
|
131
|
+
require 'chemistrykit/parallel_tests_mods'
|
132
|
+
ParallelTests::CLI.new.run(%w(--type rspec) + ['-n', concurrency.to_s] + %w(-o --beakers=) + beakers)
|
133
|
+
end
|
109
134
|
|
135
|
+
def run_rspec(beakers)
|
110
136
|
RSpec::Core::Runner.run(beakers)
|
111
137
|
end
|
112
138
|
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
139
|
+
end # CkitCLI
|
140
|
+
end # CLI
|
141
|
+
end # ChemistryKit
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
1
3
|
require 'thor/group'
|
2
4
|
|
3
5
|
module ChemistryKit
|
4
6
|
module CLI
|
7
|
+
# Creates a starting formula from a template
|
5
8
|
class FormulaGenerator < Thor::Group
|
6
9
|
include Thor::Actions
|
7
10
|
|
@@ -12,8 +15,8 @@ module ChemistryKit
|
|
12
15
|
end
|
13
16
|
|
14
17
|
def copy_file
|
15
|
-
template
|
16
|
-
template
|
18
|
+
template 'formula.tt', "./formulas/#{name}.rb"
|
19
|
+
template 'beaker_with_formula.tt', "./beakers/#{name}_beaker.rb"
|
17
20
|
end
|
18
21
|
|
19
22
|
end
|
@@ -1,16 +1,18 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
1
3
|
module ChemistryKit
|
2
4
|
module CLI
|
3
5
|
module Helpers
|
6
|
+
# this loader returns the formulas found in a path according to these rules:
|
7
|
+
# - directories loaded in alpha order
|
8
|
+
# - children directories loaded before parents
|
9
|
+
# - files loaded in alpha order
|
10
|
+
# - lib directories are loaded before any other
|
11
|
+
# - rules stack
|
4
12
|
class FormulaLoader
|
5
|
-
#this loader returns the formulas found in a path according to these rules:
|
6
|
-
#- directories loaded in alpha order
|
7
|
-
#- children directories loaded before parents
|
8
|
-
#- files loaded in alpha order
|
9
|
-
#- lib directories are loaded before any other
|
10
|
-
#- rules stack
|
11
13
|
|
12
14
|
def initialize
|
13
|
-
@formulas =
|
15
|
+
@formulas = []
|
14
16
|
end
|
15
17
|
|
16
18
|
def get_formulas(path)
|
@@ -19,25 +21,36 @@ module ChemistryKit
|
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
24
|
+
|
22
25
|
def gather(path)
|
23
|
-
#get all the directories for path
|
24
|
-
directories =
|
26
|
+
# get all the directories for path
|
27
|
+
directories = get_directories path
|
25
28
|
if directories.empty?
|
26
|
-
#if there are no directories is empty just get all the files and add it to the instance variable
|
27
|
-
@formulas.concat Dir.glob(File.join(path,
|
29
|
+
# if there are no directories is empty just get all the files and add it to the instance variable
|
30
|
+
@formulas.concat Dir.glob(File.join(path, '*.rb')).sort
|
28
31
|
else
|
29
|
-
#otherwise for each of them recurse into it
|
32
|
+
# otherwise for each of them recurse into it
|
30
33
|
directories.sort!
|
31
|
-
|
32
|
-
#if there is a lib directory, put that at the front
|
33
|
-
directories.delete('lib')
|
34
|
-
directories.unshift('lib')
|
35
|
-
end
|
34
|
+
move_lib_to_top_of_stack directories
|
36
35
|
directories.each do |directory|
|
37
36
|
gather(File.join(path, directory))
|
38
37
|
end
|
39
|
-
#and then after add the files in the original parent
|
40
|
-
@formulas.concat Dir.glob(File.join(path,
|
38
|
+
# and then after add the files in the original parent
|
39
|
+
@formulas.concat Dir.glob(File.join(path, '*.rb')).sort
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_directories(path)
|
44
|
+
Dir.entries(path).select do |entry|
|
45
|
+
(File.directory? File.join(path, entry)) && !(entry == '.' || entry == '..')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def move_lib_to_top_of_stack(directories)
|
50
|
+
if directories.include? 'lib'
|
51
|
+
# if there is a lib directory, put that at the front
|
52
|
+
directories.delete('lib')
|
53
|
+
directories.unshift('lib')
|
41
54
|
end
|
42
55
|
end
|
43
56
|
end
|
data/lib/chemistrykit/cli/new.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
1
3
|
require 'thor/group'
|
2
4
|
|
3
5
|
module ChemistryKit
|
4
6
|
module CLI
|
7
|
+
# Creates a new test harness
|
5
8
|
class New < Thor::Group
|
6
9
|
include Thor::Actions
|
7
10
|
|
@@ -12,11 +15,11 @@ module ChemistryKit
|
|
12
15
|
end
|
13
16
|
|
14
17
|
def create_project
|
15
|
-
directory
|
18
|
+
directory 'templates/chemistrykit', File.join(Dir.getwd, name)
|
16
19
|
end
|
17
20
|
|
18
21
|
def notify
|
19
|
-
say
|
22
|
+
say 'Your test harness has been created.'
|
20
23
|
end
|
21
24
|
|
22
25
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module ChemistryKit
|
6
|
+
# Default configuration class
|
7
|
+
class Configuration
|
8
|
+
|
9
|
+
attr_accessor :concurrency,
|
10
|
+
:base_url,
|
11
|
+
:selenium_connect
|
12
|
+
|
13
|
+
def initialize(hash)
|
14
|
+
# set defaults
|
15
|
+
@concurrency = 1
|
16
|
+
|
17
|
+
# overide with argument
|
18
|
+
populate_with_hash hash
|
19
|
+
validate_config
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.initialize_with_yaml(file)
|
23
|
+
self.new symbolize_keys YAML.load_file file
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def populate_with_hash(hash)
|
29
|
+
hash.each do |key, value|
|
30
|
+
begin
|
31
|
+
self.send "#{key}=", value unless value.nil?
|
32
|
+
rescue NoMethodError
|
33
|
+
raise ArgumentError.new "The config key: \"#{key}\" is unknown!"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_config
|
39
|
+
if @selenium_connect && @selenium_connect[:host] != 'saucelabs' && @concurrency > 1
|
40
|
+
raise ArgumentError.new 'Concurrency is only supported for the host: "saucelabs"!'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# private static to symbolize recursivly a loaded yaml
|
45
|
+
def self.symbolize_keys(hash)
|
46
|
+
hash.reduce({}) do |result, (key, value)|
|
47
|
+
new_key = key.class == String ? key.to_sym : key
|
48
|
+
new_value = value.class == Hash ? symbolize_keys(value) : value
|
49
|
+
result[new_key] = new_value
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|