kymera 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.idea/.name +1 -0
  4. data/.idea/.rakeTasks +7 -0
  5. data/.idea/encodings.xml +5 -0
  6. data/.idea/inspectionProfiles/Project_Default.xml +7 -0
  7. data/.idea/inspectionProfiles/profiles_settings.xml +7 -0
  8. data/.idea/kymera.iml +187 -0
  9. data/.idea/misc.xml +5 -0
  10. data/.idea/modules.xml +9 -0
  11. data/.idea/scopes/scope_settings.xml +5 -0
  12. data/.idea/vcs.xml +7 -0
  13. data/.idea/workspace.xml +1035 -0
  14. data/Gemfile +13 -0
  15. data/LICENSE.txt +22 -0
  16. data/README.md +96 -0
  17. data/Rakefile +1 -0
  18. data/bin/kymera +63 -0
  19. data/kymera.gemspec +31 -0
  20. data/lib/kymera/broker.rb +112 -0
  21. data/lib/kymera/client.rb +98 -0
  22. data/lib/kymera/config/config.rb +36 -0
  23. data/lib/kymera/cucumber/cucumber_html_parser.rb +80 -0
  24. data/lib/kymera/cucumber/cucumber_results_parser.rb +93 -0
  25. data/lib/kymera/cucumber/cucumber_test_runner.rb +76 -0
  26. data/lib/kymera/cucumber/dry_run_formatter.rb +31 -0
  27. data/lib/kymera/cucumber/test_parser.rb +36 -0
  28. data/lib/kymera/mongo_driver.rb +39 -0
  29. data/lib/kymera/platform_utils.rb +64 -0
  30. data/lib/kymera/results_bus.rb +26 -0
  31. data/lib/kymera/szmq/szmq.rb +252 -0
  32. data/lib/kymera/test_results_collector.rb +126 -0
  33. data/lib/kymera/version.rb +3 -0
  34. data/lib/kymera/worker.rb +83 -0
  35. data/lib/kymera.rb +88 -0
  36. data/lib/spec/broker_spec.rb +25 -0
  37. data/lib/spec/client_spec.rb +21 -0
  38. data/lib/spec/client_test_run_spec.rb +3 -0
  39. data/lib/spec/config_options.txt +51 -0
  40. data/lib/spec/full_run_for_linux_spec.rb +44 -0
  41. data/lib/spec/full_run_spec.rb +44 -0
  42. data/lib/spec/get_bus_data.rb +43 -0
  43. data/lib/spec/html_parser_spec.rb +61 -0
  44. data/lib/spec/html_parsing_alg.txt +31 -0
  45. data/lib/spec/json_message_example.txt +6 -0
  46. data/lib/spec/mongo_driver_spec.rb +5 -0
  47. data/lib/spec/plain_broker_spec.rb +44 -0
  48. data/lib/spec/plain_reply_socket_spec.rb +13 -0
  49. data/lib/spec/plain_request_socket_spec.rb +13 -0
  50. data/lib/spec/result_bus_spec.rb +13 -0
  51. data/lib/spec/results_parser_test_run_spec.rb +18 -0
  52. data/lib/spec/send_bus_data.rb +16 -0
  53. data/lib/spec/startup_broker_bus_collector_spec.rb +35 -0
  54. data/lib/spec/test_file_paths_spec.rb +2 -0
  55. data/lib/spec/worker_spec.rb +15 -0
  56. data/lib/spec/worker_test_run_spec.rb +21 -0
  57. data/lib/spec/zmq_network_test_spec.rb +13 -0
  58. metadata +228 -0
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kymera.gemspec
4
+ gem 'cucumber'
5
+ gem 'ffi-rzmq'
6
+ gem 'chronic'
7
+ gem 'mongo', '1.10.2'
8
+ gem 'bson', '1.10.2'
9
+ gem 'bson_ext', '1.10.2'
10
+
11
+ gemspec
12
+
13
+
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