kymera 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/inspectionProfiles/Project_Default.xml +7 -0
- data/.idea/inspectionProfiles/profiles_settings.xml +7 -0
- data/.idea/kymera.iml +187 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +1035 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +96 -0
- data/Rakefile +1 -0
- data/bin/kymera +63 -0
- data/kymera.gemspec +31 -0
- data/lib/kymera/broker.rb +112 -0
- data/lib/kymera/client.rb +98 -0
- data/lib/kymera/config/config.rb +36 -0
- data/lib/kymera/cucumber/cucumber_html_parser.rb +80 -0
- data/lib/kymera/cucumber/cucumber_results_parser.rb +93 -0
- data/lib/kymera/cucumber/cucumber_test_runner.rb +76 -0
- data/lib/kymera/cucumber/dry_run_formatter.rb +31 -0
- data/lib/kymera/cucumber/test_parser.rb +36 -0
- data/lib/kymera/mongo_driver.rb +39 -0
- data/lib/kymera/platform_utils.rb +64 -0
- data/lib/kymera/results_bus.rb +26 -0
- data/lib/kymera/szmq/szmq.rb +252 -0
- data/lib/kymera/test_results_collector.rb +126 -0
- data/lib/kymera/version.rb +3 -0
- data/lib/kymera/worker.rb +83 -0
- data/lib/kymera.rb +88 -0
- data/lib/spec/broker_spec.rb +25 -0
- data/lib/spec/client_spec.rb +21 -0
- data/lib/spec/client_test_run_spec.rb +3 -0
- data/lib/spec/config_options.txt +51 -0
- data/lib/spec/full_run_for_linux_spec.rb +44 -0
- data/lib/spec/full_run_spec.rb +44 -0
- data/lib/spec/get_bus_data.rb +43 -0
- data/lib/spec/html_parser_spec.rb +61 -0
- data/lib/spec/html_parsing_alg.txt +31 -0
- data/lib/spec/json_message_example.txt +6 -0
- data/lib/spec/mongo_driver_spec.rb +5 -0
- data/lib/spec/plain_broker_spec.rb +44 -0
- data/lib/spec/plain_reply_socket_spec.rb +13 -0
- data/lib/spec/plain_request_socket_spec.rb +13 -0
- data/lib/spec/result_bus_spec.rb +13 -0
- data/lib/spec/results_parser_test_run_spec.rb +18 -0
- data/lib/spec/send_bus_data.rb +16 -0
- data/lib/spec/startup_broker_bus_collector_spec.rb +35 -0
- data/lib/spec/test_file_paths_spec.rb +2 -0
- data/lib/spec/worker_spec.rb +15 -0
- data/lib/spec/worker_test_run_spec.rb +21 -0
- data/lib/spec/zmq_network_test_spec.rb +13 -0
- metadata +228 -0
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 jakesa
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Kymera
|
2
|
+
|
3
|
+
Kymera is a distributed system built on ZeroMQ for running Cucumber tests across a network.
|
4
|
+
If you are at this page, first let me say Thank You for your interest. That being said, the Kymera gem
|
5
|
+
is in a very early stage of development and is currently build specifically for use in DAT Solutions© internal testing infrastructure.
|
6
|
+
I plan to make this more generic so it can be used with other systems, but for now the goal is to get something working for DAT Solutions©
|
7
|
+
If you are still are interested, please download the source code and hack away.
|
8
|
+
|
9
|
+
Also, please note that there are no reliable unit tests for this code yet. It is on my TODO list but not a very high priority. In addition, the code is rather
|
10
|
+
poorly annotated. My apologies for this. I will get to that as soon as I can.
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
##Architecture
|
15
|
+
The Kyemra gem is comprised of 5 components:
|
16
|
+
|
17
|
+
###Client
|
18
|
+
The client is the main entry point into the gem. This allows users to submit run requests to the framework. It is responsible for parsing the tests
|
19
|
+
and sending them out to the Broker
|
20
|
+
|
21
|
+
###Broker
|
22
|
+
The broker is the component that is responsible for creating and maintaining the test execution queues. When a queue is spawned, the tests are sent to the
|
23
|
+
connected workers in a round-robin format. When a worker signals that it is available for test execution, the broker will send it a test to run.
|
24
|
+
|
25
|
+
###Worker
|
26
|
+
The worker is the component that is responsible for actually running the tests. As the tests are ran, the worker will publish its output to the results bus. When
|
27
|
+
the test is completed it will send the entire output of that run to the test results collector for processing
|
28
|
+
|
29
|
+
###Results Collector
|
30
|
+
The result collector is responsible for taking all of the results from a test run, aggregating them and sending those results back to the client
|
31
|
+
Optionally, the collector can also send a complete version of the results, parsed into html, to a mongodb database for reporting purposes.
|
32
|
+
|
33
|
+
###Results Bus
|
34
|
+
The Results Bus is where all the results are published. The Client listens on this bus for both real time output of the test run as well as the signal
|
35
|
+
that the test run has completed.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
Please note that at the time of this writing, this gem has not been published. You will need to pull a copy of the repo and build the gem yourself
|
39
|
+
|
40
|
+
$ gem install kymera
|
41
|
+
|
42
|
+
This gem uses ZeroMQ. It will need to be installed. You can find the installation instructions on their website [here](http://zeromq.org/intro:get-the-software)
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
After installation is complete, you will need to generate the kymera_config.yaml file for gem configuration. This should be done in the same location
|
47
|
+
as you Cucumber project. For convenience, there is a command line tool included with the gem. To generate the config file enter the following command
|
48
|
+
|
49
|
+
$ kymera config
|
50
|
+
|
51
|
+
By default, the gem is setup to run everything locally and has the mongodb feature turned off. Before you can use the Kymera system, the following components
|
52
|
+
must be running:
|
53
|
+
* Broker
|
54
|
+
* Results Collector
|
55
|
+
* Results Bus
|
56
|
+
* At least one worker
|
57
|
+
|
58
|
+
They can be started individually or all at once:
|
59
|
+
|
60
|
+
All at once
|
61
|
+
|
62
|
+
$ kymera broker collector bus worker
|
63
|
+
|
64
|
+
Individually
|
65
|
+
|
66
|
+
$ kymera broker
|
67
|
+
$ kymera collector
|
68
|
+
|
69
|
+
|
70
|
+
Once all the necessary processes are started, you can start the a test run by calling the #run_tests method on the Kymera module
|
71
|
+
|
72
|
+
$ Kymera.run_tests('\Path\to\tests', 'cucumber', [-p default], 'develop', false)
|
73
|
+
|
74
|
+
|
75
|
+
The run_tests method takes the following parameters
|
76
|
+
* Test path -
|
77
|
+
This can be a path to a specific test or a path to a directory of tests. The system will parse all of the feature files for
|
78
|
+
tests it is supposed to run based on the run options passed in.
|
79
|
+
* Test runner -
|
80
|
+
This is the runner that they system is to use for running the tests. At the time of this writing, the only supported running is cucumber
|
81
|
+
* Runner options -
|
82
|
+
These are the options to be passed to the runner and what will be used to parse the tests to be executed.
|
83
|
+
* Branch name -
|
84
|
+
When a test is started on a worker, it pulls the specified branch for any changes and updates before running the test. This parameter tells it the branch
|
85
|
+
name
|
86
|
+
* Live results -
|
87
|
+
This parameter tells the system whether or not to display real time results. By default it is set to true. If set to false, there will be no console output
|
88
|
+
to the client until the run has been completed
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
1. Fork it
|
93
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
94
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
95
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
96
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/kymera
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'kymera'
|
3
|
+
|
4
|
+
args = ARGV
|
5
|
+
threads = []
|
6
|
+
trap ("INT") do
|
7
|
+
puts "\nStopping Kymera processes..."
|
8
|
+
threads.each do |thread|
|
9
|
+
thread.kill
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
if args.length > 1
|
15
|
+
args.each do |arg|
|
16
|
+
case arg
|
17
|
+
when "broker"
|
18
|
+
threads << Thread.new {Kymera.start_broker}
|
19
|
+
when "bus"
|
20
|
+
threads << Thread.new {Kymera.start_bus}
|
21
|
+
when "collector"
|
22
|
+
threads << Thread.new {Kymera.start_collector}
|
23
|
+
when "worker"
|
24
|
+
threads << Thread.new {Kymera.start_worker}
|
25
|
+
when "config"
|
26
|
+
threads << Thread.new {Kymera.generate_config}
|
27
|
+
else
|
28
|
+
threads.each do |thread|
|
29
|
+
thread.kill
|
30
|
+
end
|
31
|
+
|
32
|
+
raise "No valid parameters were passed in. Here is a list of the valid parameters:\nbroker\nbus\ncollector\nworker\nconfig\n"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
else
|
37
|
+
case args[0]
|
38
|
+
when "broker"
|
39
|
+
threads << Thread.new {Kymera.start_broker}
|
40
|
+
when "bus"
|
41
|
+
threads << Thread.new {Kymera.start_bus}
|
42
|
+
when "collector"
|
43
|
+
threads << Thread.new {Kymera.start_collector}
|
44
|
+
when "worker"
|
45
|
+
threads << Thread.new {Kymera.start_worker}
|
46
|
+
when "config"
|
47
|
+
threads << Thread.new {Kymera.generate_config}
|
48
|
+
else
|
49
|
+
threads.each do |thread|
|
50
|
+
thread.kill
|
51
|
+
end
|
52
|
+
|
53
|
+
raise "No valid parameters were passed in. Here is a list of the valid parameters:
|
54
|
+
broker\nbus\ncollector\nworker\nconfig\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
threads.each do |thread|
|
61
|
+
thread.join
|
62
|
+
end
|
63
|
+
|
data/kymera.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kymera/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kymera"
|
8
|
+
spec.version = Kymera::VERSION
|
9
|
+
spec.authors = ["jakesa"]
|
10
|
+
spec.email = ["jakes.sarate@dat.com"]
|
11
|
+
spec.description = 'Distributed Cucumber test runner'
|
12
|
+
spec.summary = 'Execute cucumber tests across a network'
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_dependency 'cucumber'
|
24
|
+
spec.add_dependency 'ffi-rzmq'
|
25
|
+
spec.add_dependency 'json'
|
26
|
+
spec.add_dependency 'mongo', '1.10.2'
|
27
|
+
spec.add_dependency 'bson', '1.10.2'
|
28
|
+
spec.add_dependency 'bson_ext', '~> 1.10.0'
|
29
|
+
spec.add_dependency 'chronic'
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative 'szmq/szmq'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Kymera
|
5
|
+
|
6
|
+
class Broker
|
7
|
+
|
8
|
+
#test_address is the port you want to listen on for incoming test runs. client_address is the internal address used for sending tests to the proxy
|
9
|
+
#worker_address is the port that the workers will connect to for test distribution. num_of_con is the number of concurrent requests you want running at any given time
|
10
|
+
#This number can be tuned depending on the machine the broker is running on.
|
11
|
+
def initialize
|
12
|
+
config = Kymera::Config.new
|
13
|
+
@zmq = Kymera::SZMQ.new
|
14
|
+
@num_of_connections = config.broker["number_of_connections"]
|
15
|
+
#This socket is for getting tests from the client
|
16
|
+
@client_address = "tcp://*:#{config.broker["client_listening_port"]}"
|
17
|
+
@internal_address = "tcp://*:#{config.broker["internal_worker_port"]}"
|
18
|
+
@worker_address = "tcp://*:#{config.broker["worker_listening_port"]}"
|
19
|
+
@test_socket = @zmq.socket(@client_address, 'pull')
|
20
|
+
@test_socket.bind
|
21
|
+
@front_end = @zmq.socket(@internal_address, 'router')
|
22
|
+
@back_end = @zmq.socket(@worker_address, 'dealer')
|
23
|
+
@proxy = Thread.new {@zmq.start_proxy(@front_end, @back_end)}
|
24
|
+
end
|
25
|
+
|
26
|
+
#This brings up the broker so that it can receive test run requests.
|
27
|
+
def start_broker
|
28
|
+
puts "Broker started..."
|
29
|
+
@test_socket.receive do |tests|
|
30
|
+
puts "Received test run request.."
|
31
|
+
start_test_run(tests)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
#This is the start of the test run and is called when the broker receives a test run request
|
38
|
+
def start_test_run(test_run)
|
39
|
+
test_run = JSON.parse(test_run)
|
40
|
+
@test_count = test_run["tests"].length
|
41
|
+
tests = test_run["tests"]
|
42
|
+
threads = []
|
43
|
+
|
44
|
+
report_test_config(test_run)
|
45
|
+
|
46
|
+
if tests.length > @num_of_connections.to_i
|
47
|
+
1.upto @num_of_connections do
|
48
|
+
test = tests.pop
|
49
|
+
break if test.nil?
|
50
|
+
threads << run_test(test, test_run)
|
51
|
+
end
|
52
|
+
work_queue(threads, tests, test_run)
|
53
|
+
puts "Tests Complete"
|
54
|
+
|
55
|
+
else
|
56
|
+
1.upto tests.length do
|
57
|
+
test = tests.pop
|
58
|
+
break if test.nil?
|
59
|
+
threads << run_test(test, test_run)
|
60
|
+
end
|
61
|
+
threads.each do |t|
|
62
|
+
t.join
|
63
|
+
end
|
64
|
+
puts "Tests Complete"
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
#If there are tests left over after the initial test start up, they are placed into a queue. The queue is then worked until all tests in the queue have been executed
|
72
|
+
def work_queue(threads, tests, options)
|
73
|
+
until tests.empty?
|
74
|
+
threads.delete_if {|t| !t.alive?}
|
75
|
+
if threads.length < @num_of_connections
|
76
|
+
test = tests.pop
|
77
|
+
break if test.nil?
|
78
|
+
threads << run_test(test, options)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
threads.each do |t|
|
82
|
+
t.join
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#This runs each test individually
|
87
|
+
def run_test(test, options)
|
88
|
+
port = @internal_address.split(':')[2]
|
89
|
+
Thread.new {
|
90
|
+
message = JSON.generate({:test => test, :runner => options["runner"], :options => options["options"], :run_id => options["run_id"],
|
91
|
+
:test_count => @test_count, :branch => options["branch"], :start_time => options["start_time"]})
|
92
|
+
socket = @zmq.socket("tcp://127.0.0.1:#{port}", 'request')
|
93
|
+
socket.connect
|
94
|
+
puts "Sending: #{message}"
|
95
|
+
socket.send_message(message)
|
96
|
+
socket.close}
|
97
|
+
end
|
98
|
+
|
99
|
+
#This gives a print out of the test run that was received
|
100
|
+
def report_test_config(test_run)
|
101
|
+
puts "Running test with the following configuration:"
|
102
|
+
puts "Branch: #{test_run["branch"]}"
|
103
|
+
puts "Runner: #{test_run["runner"]}"
|
104
|
+
puts "Run ID: #{test_run["run_id"]}"
|
105
|
+
puts "Runner Options: #{test_run["options"]}"
|
106
|
+
puts "Total number tests: #{test_run["tests"].length}"
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require_relative 'szmq/szmq'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Kymera
|
5
|
+
|
6
|
+
class Client
|
7
|
+
|
8
|
+
#The client is responsible for sending the run to the distributed network. It is responsible for parsing the tests and sending all needed information to the
|
9
|
+
#test broker
|
10
|
+
#The initializer take in a broker_address(String) identifying the location of the broker on the network, a results_bus_address(String) identifying the latching point of the bus where the client and get
|
11
|
+
#real-time test output as the tests are being executed and real_time(Boolean) indicating whether or not this client wants to get real-time updates. This is defaulted to true
|
12
|
+
def initialize(real_time = true)
|
13
|
+
config = Kymera::Config.new
|
14
|
+
@broker_address = config.client["broker_address"]
|
15
|
+
@results_bus_address = config.client["results_bus_address"]
|
16
|
+
@real_time = real_time.to_s
|
17
|
+
@zmq = Kymera::SZMQ.new
|
18
|
+
@client_id = Kymera::host_name
|
19
|
+
Client.run_id +=1
|
20
|
+
@full_run_id = @client_id + (Client.run_id.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.run_id=(num)
|
24
|
+
@run_id = num
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.run_id
|
28
|
+
@run_id ||= 0
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
#This is the kick off point for the test run. The tests parameter is the directory location of the tests you wish to run. This will be passed into a test parser that will determine
|
33
|
+
#which of the tests in the directory need to be run based on the options passed in. The runner parameter tells the system which test runner the system should use. Right now, the only
|
34
|
+
#supported test runner is Cucumber, but I would like to expand this at the very least to also support Rspec. The options parameter are the options to be passed into the specified runner
|
35
|
+
def run_tests(tests, runner, options, branch = 'develop')
|
36
|
+
@start_time = Time.now
|
37
|
+
tests = parse_tests(tests, runner, options)
|
38
|
+
test_run = {:tests => tests, :runner => runner, :run_id => @full_run_id, :options => options, :branch => branch, :start_time => @start_time.to_s }
|
39
|
+
socket = @zmq.socket(@broker_address, 'push')
|
40
|
+
socket.connect
|
41
|
+
message = JSON.generate(test_run)
|
42
|
+
socket.send_message(message)
|
43
|
+
|
44
|
+
channels = ["end_#{@full_run_id}"]
|
45
|
+
results_feed = @zmq.socket(@results_bus_address, 'sub')
|
46
|
+
if @real_time == "true"
|
47
|
+
channels << @full_run_id
|
48
|
+
end
|
49
|
+
results_feed.subscribe(channels) do |channel, results|
|
50
|
+
if channel == "end_#{@full_run_id}"
|
51
|
+
puts "###########Test Run Results########################"
|
52
|
+
puts results
|
53
|
+
results_feed.close
|
54
|
+
report_time_taken
|
55
|
+
exit
|
56
|
+
else
|
57
|
+
puts results
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
#This method is what parses the test directory into runnable tests. It takes in 3 parameters, the first being tests(String). This is the location for which the system looks for
|
66
|
+
#executable tests. This can also be a single test. The system will still parse it to make sure that it should be run based on the passed in options. The runner(String) tells the
|
67
|
+
#parser which test parser to use. Currently there is only support for a cucumber parser. I hope to expand support for Rspec as well. The options(Array), are the options that the
|
68
|
+
#test parser should use for parsing out the tests.
|
69
|
+
def parse_tests(tests, runner, options)
|
70
|
+
|
71
|
+
test_path = nil
|
72
|
+
if Kymera.is_linux?
|
73
|
+
if tests.include? 'c:'
|
74
|
+
test_path = tests.gsub('c:','~')
|
75
|
+
else
|
76
|
+
test_path = tests.gsub('C:','~')
|
77
|
+
end
|
78
|
+
else
|
79
|
+
test_path = tests
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
if runner.downcase == 'cucumber'
|
84
|
+
parser = Kymera::Cucumber::TestParser.new(test_path, options)
|
85
|
+
parser.parse_tests
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def report_time_taken
|
90
|
+
run_time = ((Time.now - @start_time)/60).to_s.match(/(\d+.\d{2})/)[0]
|
91
|
+
puts "Took #{run_time}m"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module Kymera
|
3
|
+
class Config
|
4
|
+
|
5
|
+
#The constructor reads in the config.yaml file, converts it into a hash and then defines methods on this class based on the
|
6
|
+
#different sections
|
7
|
+
def initialize
|
8
|
+
yaml_file = File.open(File.join(Dir.pwd, '/kymera_config.yaml'), 'r+')
|
9
|
+
yaml_file.rewind
|
10
|
+
@config_options = YAML.load(yaml_file.read)
|
11
|
+
@config_options.each do |key, value|
|
12
|
+
define_singleton_method(key){value}
|
13
|
+
end
|
14
|
+
yaml_file.close
|
15
|
+
end
|
16
|
+
|
17
|
+
#This takes any changes that were made to the different sections and updates the config.yaml file (more to the point, overrides the old file
|
18
|
+
# and replaces the entire file with the new values)
|
19
|
+
def update
|
20
|
+
yaml_file = File.open(File.join(Dir.pwd, '/kymera_config.yaml'), 'w+')
|
21
|
+
@config_options.to_yaml.split('\n').each do |line|
|
22
|
+
yaml_file.write(line)
|
23
|
+
end
|
24
|
+
yaml_file.rewind
|
25
|
+
str = yaml_file.read
|
26
|
+
yaml_file.close
|
27
|
+
str
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
@config_options.to_yaml
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
module Kymera
|
3
|
+
module Cucumber
|
4
|
+
class HTMLResultsParser
|
5
|
+
def self.to_html(results)
|
6
|
+
html_results = ''
|
7
|
+
results_array = results.split("\n")
|
8
|
+
|
9
|
+
if results_array.length < 3
|
10
|
+
html_results << "<div class='summary'>"
|
11
|
+
results_array.each do |line|
|
12
|
+
line.gsub!("\e[36m", "<span class='skip'>")
|
13
|
+
line.gsub!("\e[90m", "<span class='text'>")
|
14
|
+
line.gsub!("\e[31m", "<span class='error'>")
|
15
|
+
line.gsub!("\e[32m", "<span class='pass'>")
|
16
|
+
line.gsub!("\e[1m", "<span class='example'>")
|
17
|
+
line.gsub!("\e[0m", "</span>")
|
18
|
+
line.gsub!("[36m", "<span class='skip'>")
|
19
|
+
line.gsub!("[90m", "<span class='text'>")
|
20
|
+
line.gsub!("[31m", "<span class='error'>")
|
21
|
+
line.gsub!("[32m", "<span class='pass'>")
|
22
|
+
line.gsub!("[1m", "<span class='example'>")
|
23
|
+
line.gsub!("[0m", "</span>")
|
24
|
+
html_results << "#{line}<br>"
|
25
|
+
end
|
26
|
+
html_results << "</div>"
|
27
|
+
html_results
|
28
|
+
else
|
29
|
+
|
30
|
+
results_array.each do |line|
|
31
|
+
if line.start_with?("Using")
|
32
|
+
if html_results == ''
|
33
|
+
html_results << "<div class='feature'>"
|
34
|
+
html_results << "<p>"
|
35
|
+
html_results << "#{line}"
|
36
|
+
else
|
37
|
+
html_results << "</p></div>"
|
38
|
+
html_results << "<div class='feature'>"
|
39
|
+
html_results << "<p>"
|
40
|
+
html_results << "#{line}"
|
41
|
+
end
|
42
|
+
elsif line.start_with?("Feature")
|
43
|
+
html_results << "<span class='featureTitle'>#{line}</span><br>"
|
44
|
+
elsif line.lstrip.start_with?("Scenario")
|
45
|
+
line.gsub!("\e[90m", "<span class='text'>")
|
46
|
+
line.gsub!("[90m", "<span class='text'>")
|
47
|
+
line.gsub!("\e[0m", "</span>")
|
48
|
+
line.gsub!("[0m", "</span>")
|
49
|
+
html_results << "<span class='scenarioTitle'>#{line}</span><br>"
|
50
|
+
else
|
51
|
+
#\e[36m blue
|
52
|
+
#\e[90m gray
|
53
|
+
#\e[31m red
|
54
|
+
#\e[32m green
|
55
|
+
#\e[0m
|
56
|
+
line.gsub!("\e[36m", "<span class='skip'>")
|
57
|
+
line.gsub!("\e[90m", "<span class='text'>")
|
58
|
+
line.gsub!("\e[31m", "<span class='error'>")
|
59
|
+
line.gsub!("\e[32m", "<span class='pass'>")
|
60
|
+
line.gsub!("\e[1m", "<span class='example'>")
|
61
|
+
line.gsub!("\e[0m", "</span>")
|
62
|
+
line.gsub!("[36m", "<span class='skip'>")
|
63
|
+
line.gsub!("[90m", "<span class='text'>")
|
64
|
+
line.gsub!("[31m", "<span class='error'>")
|
65
|
+
line.gsub!("[32m", "<span class='pass'>")
|
66
|
+
line.gsub!("[1m", "<span class='example'>")
|
67
|
+
line.gsub!("[0m", "</span>")
|
68
|
+
html_results << "#{line}<br>"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
html_results << "</div>"
|
72
|
+
html_results
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Kymera
|
2
|
+
module Cucumber
|
3
|
+
class ResultsParser
|
4
|
+
|
5
|
+
#TODO: JS - Add support for detecting skipped/undefined steps
|
6
|
+
def self.parse
|
7
|
+
@results
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.summarize_results(results)
|
11
|
+
results_array = results.split("\n")
|
12
|
+
sort_order = %w[scenario step]
|
13
|
+
aggregate_results = sort_order.map do |group|
|
14
|
+
r_group_results = results_array.select {|l| l2 = l.scan(/^\d+ #{group}/); !l2.empty?}
|
15
|
+
sum_up_lines(r_group_results, group)
|
16
|
+
end.compact.join("\n")
|
17
|
+
aggregate_results << "\n#{sum_up_failed_scenarios(results_array)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.sum_up_lines(lines, group)
|
21
|
+
r_lines =[]
|
22
|
+
lines.each do |l|
|
23
|
+
r_lines << l.gsub('(','').gsub(')','')
|
24
|
+
end
|
25
|
+
|
26
|
+
_group, group_count, passed, passed_count, failed, failed_count = ''
|
27
|
+
|
28
|
+
group_lines = r_lines.map {|m| m.scan(/(\d+) (#{group})/)}
|
29
|
+
_group, group_count = count(group_lines) unless group_lines.empty?
|
30
|
+
|
31
|
+
failed_scenarios = r_lines.map {|m| m.scan(/(\d+) (failed)/)}
|
32
|
+
failed_scenarios.delete_if {|l| l.empty?}
|
33
|
+
failed, failed_count = count(failed_scenarios) unless failed_scenarios.empty?
|
34
|
+
|
35
|
+
passed_scenarios = r_lines.map {|m| m.scan(/(\d+) (passed)/)}
|
36
|
+
passed_scenarios.delete_if {|l| l.empty?}
|
37
|
+
passed, passed_count = count(passed_scenarios) unless passed_scenarios.empty?
|
38
|
+
|
39
|
+
if group_count > 1
|
40
|
+
_group += 's'
|
41
|
+
end
|
42
|
+
|
43
|
+
if _group.downcase.include? 'scenario'
|
44
|
+
@@p_count = ''
|
45
|
+
@@f_count = ''
|
46
|
+
@@p_count = passed_count unless passed_count.nil?
|
47
|
+
@@f_count = failed_count unless failed_count.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
if failed_scenarios.empty?
|
51
|
+
"#{group_count} #{_group} (\e[32m#{passed_count} #{passed}\e[0m)"
|
52
|
+
elsif passed_scenarios.empty?
|
53
|
+
"#{group_count} #{_group} (\e[31m#{failed_count} #{failed}\e[0m)"
|
54
|
+
else
|
55
|
+
"#{group_count} #{_group} (\e[31m#{failed_count} #{failed}\e[0m, \e[32m#{passed_count} #{passed}\e[0m)"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
#These variables are reset every time the sum_up_lines method is called
|
61
|
+
def self.scenario_counts
|
62
|
+
{pass: @@p_count, fail: @@f_count}
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def self.count(lines)
|
67
|
+
count = 0
|
68
|
+
word = ''
|
69
|
+
lines.each do |l|
|
70
|
+
count += l[0][0].to_i
|
71
|
+
word = l[0][1]
|
72
|
+
end
|
73
|
+
[word, count]
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.sum_up_failed_scenarios(results)
|
77
|
+
results = results.select{|l| l.include?('[31mcucumber')}
|
78
|
+
if results.empty?
|
79
|
+
''
|
80
|
+
else
|
81
|
+
results.map {|l| l << "\n"}
|
82
|
+
formatted_results = "\e[31mFailing Scenarios:\n"
|
83
|
+
results.each {|l| formatted_results << l}
|
84
|
+
formatted_results << "\e[0m"
|
85
|
+
formatted_results
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|