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
@@ -0,0 +1,76 @@
1
+ require 'cucumber'
2
+ module Kymera
3
+ module Cucumber
4
+ class Runner
5
+
6
+ #This is the test runner. It is responsible for the actual running of the test. It takes in 3 parameters. The first being options(Array). These are the options to be used
7
+ #for the cucumber test run. The run_id(String), this is a unique id identifying the test run that this test is for. It is used as the channel name for publishing results
8
+ #on the results bus. And lastly results_bus(SSocket), this is a socket object representing the results bus. This is what is used for publishing results to that bus
9
+ def initialize(options, run_id, result_bus = nil)
10
+ @options = options
11
+ @result_bus = result_bus
12
+ @run_id = run_id
13
+ ENV["AUTOTEST"] = "1" if $stdout.tty?
14
+ end
15
+
16
+ #This is kicking off the test. Takes in 3 parameters, test(String) is the test to be executed. options(Array) is an array of the options to be used with this test run. By default,
17
+ #it uses the options passed in with the constructor. run_id(String) is the id of test run that this test is associated with. This is also defaulted with what was passed in
18
+ #with the constructor
19
+ def run_test(test, branch, options = @options, run_id = @run_id)
20
+ _results = ''
21
+ _options = ''
22
+ options.each do |option|
23
+ _options += " #{option}"
24
+ end
25
+
26
+ switch_to_branch(branch)
27
+
28
+ puts "Running test: #{test}"
29
+ io = Object::IO.popen("bundle exec cucumber #{test} #{_options}")
30
+ until io.eof? do
31
+ result = io.gets
32
+ unless @result_bus.nil?
33
+ @result_bus.publish_message(run_id, result)
34
+ end
35
+ _results += result
36
+ end
37
+ Process.wait2(io.pid)
38
+ _results
39
+ end
40
+
41
+ private
42
+
43
+ #TODO - add support for git
44
+ #The worker machines have to be setup for public/private key authentication with hg. Otherwise, this will lockup the system waiting for a password
45
+ def switch_to_branch(branch)
46
+ io = Object::IO.popen('hg branch')
47
+ current_branch = io.gets.chomp
48
+ if current_branch == branch
49
+ update_current_branch
50
+ else
51
+ output = ''
52
+ io = Object::IO.popen("hg update #{branch}")
53
+ until io.eof?
54
+ output << io.gets
55
+ end
56
+ Process.wait2(io.pid)
57
+ puts output
58
+ update_current_branch
59
+ end
60
+ end
61
+
62
+ def update_current_branch
63
+ output = ''
64
+ io = Object::IO.popen("hg pull -b . -u")
65
+ until io.eof?
66
+ output << io.gets
67
+ end
68
+ Process.wait2(io.pid)
69
+ puts output
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+
76
+ end
@@ -0,0 +1,31 @@
1
+ module Kymera
2
+ module Cucumber
3
+ class DryRunFormatter
4
+
5
+ def initialize(step_mother, io, options)
6
+ @io = io
7
+ end
8
+
9
+ def scenario_name(*args)
10
+ #if args[0].include?("Outline")
11
+ # @scenario_name = args[2].split(':')[0]
12
+ #else
13
+ $stdout << args[2]
14
+ $stdout << "\n"
15
+ #end
16
+ end
17
+
18
+ #def before_table_row(*args)
19
+ #
20
+ # args.each do |arg|
21
+ # unless arg.line < 1 || arg.send(:header?) == true
22
+ # $stdout << "#{@scenario_name + ":" + arg.line.to_s }"
23
+ # $stdout << "\n"
24
+ # end
25
+ #
26
+ # end
27
+ #end
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,36 @@
1
+ require_relative 'dry_run_formatter'
2
+
3
+ module Kymera
4
+
5
+ module Cucumber
6
+ class TestParser
7
+
8
+ def initialize(tests, options)
9
+ @tests = tests
10
+ @options = options
11
+ end
12
+
13
+ def parse_tests
14
+ tests = dry_run(["cucumber", @tests, '--dry-run -f DryRunFormatter', @options].compact.join(" ")).split("\n")
15
+ refined_tests =[]
16
+ tests.delete_at(0) if tests[0].downcase.include?('using')
17
+ tests.each do |test|
18
+ refined_tests << test.gsub('\\','/')
19
+ end
20
+ $stdout << "The number of scenarios found to be executed: #{refined_tests.count}"
21
+ $stdout << "\n"
22
+ refined_tests
23
+ end
24
+
25
+ private
26
+
27
+ def dry_run(cmd)
28
+ $stdout << "Preprocessing test files"
29
+ $stdout << "\n"
30
+ tr = Thread.new(cmd) { |c| `#{c}`}
31
+ tr.join
32
+ tr.value
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'szmq/szmq'
2
+ require 'json'
3
+ require 'mongo'
4
+
5
+ module Kymera
6
+
7
+ class MongoDriver
8
+ include Mongo
9
+
10
+
11
+ def self.log_results(log, address = "localhost", port = 27017, database = 'default_db', collection = 'default_collection')
12
+ puts "Sending results to mongodb..."
13
+ MongoDriver.new(address, port, database, collection).write_log(log)
14
+ end
15
+
16
+
17
+ #This can be initialized by specifying the address and the port of the mongodb server. By default, this expects that the
18
+ #mongodb server is located on the same machine as the calling code. A collection name will also be defaulted if not passed in.
19
+ #By default, this will be 'default_db'
20
+ def initialize(address = "localhost", port = 27017, database = 'default_db', collection = 'default_db')
21
+ puts "Initializing db connection.."
22
+ @db_client = MongoClient.new(address, port).db(database)
23
+ puts "Assigning collection..."
24
+ @collection = collection
25
+ end
26
+
27
+
28
+ #The @param log needs to be a JSON string.
29
+ def write_log(log)
30
+ puts "Getting collection..."
31
+ coll = @db_client["#{@collection}"]
32
+ puts "Sending insert request.."
33
+ coll.insert(JSON.parse log)
34
+ puts "Request completed"
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,64 @@
1
+ require 'socket'
2
+
3
+ module Kymera
4
+
5
+ def self.processor_count
6
+ @processor_count ||= case RbConfig::CONFIG['host_os']
7
+ when /darwin9/
8
+ `hwprefs cpu_count`.to_i
9
+ when /darwin/
10
+ (hwprefs_available? ? `hwprefs thread_count` : `sysctl -n hw.ncpu`).to_i
11
+ when /linux|cygwin/
12
+ `grep -c ^processor /proc/cpuinfo`.to_i
13
+ when /(net|open|free)bsd/
14
+ `sysctl -n hw.ncpu`.to_i
15
+ when /mswin|mingw/
16
+ require 'win32ole'
17
+ wmi = WIN32OLE.connect("winmgmts://")
18
+ cpu = wmi.ExecQuery("select NumberOfLogicalProcessors from Win32_Processor")
19
+ cpu.to_enum.first.NumberOfLogicalProcessors
20
+ when /solaris2/
21
+ `psrinfo -p`.to_i # this is physical cpus afaik
22
+ else
23
+ $stderr.puts "Unknown architecture ( #{RbConfig::CONFIG["host_os"]} ) assuming one processor."
24
+ 1
25
+ end
26
+ end
27
+
28
+ def self.is_linux?
29
+ case RbConfig::CONFIG['host_os']
30
+ when /linux|darwin/
31
+ true
32
+ else
33
+ false
34
+ end
35
+
36
+ end
37
+
38
+
39
+ def self.ip_address
40
+ ips = Socket.ip_address_list
41
+ ip = ''
42
+ ips.each do |i|
43
+ ip = i.ip_address if i.ipv4? && i.ip_address.start_with?("10")
44
+ end
45
+ ip
46
+ end
47
+
48
+
49
+
50
+ def self.host_name
51
+ Socket.gethostname
52
+ end
53
+
54
+ def self.wait_for(&block)
55
+ found = false
56
+ i = 0
57
+ until i == 60 || found
58
+ found = yield
59
+ sleep 1
60
+ i +=1
61
+ end
62
+ found
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'szmq/szmq'
2
+ require 'json'
3
+
4
+ module Kymera
5
+
6
+ class ResultsBus
7
+
8
+ def initialize
9
+ config = Kymera::Config.new
10
+ @zmq = Kymera::SZMQ.new
11
+ @incoming_socket = @zmq.socket("tcp://*:#{config.result_bus["pub_port"]}", 'xsub')
12
+ @outgoing_socket = @zmq.socket("tcp://*:#{config.result_bus["sub_port"]}", 'xpub')
13
+ end
14
+
15
+ def start_bus
16
+ puts "Results bus started..."
17
+ @zmq.start_pub_sub_proxy(@incoming_socket, @outgoing_socket)
18
+ end
19
+
20
+
21
+
22
+
23
+ end
24
+
25
+
26
+ end
@@ -0,0 +1,252 @@
1
+ require 'ffi-rzmq'
2
+
3
+ module Kymera
4
+ class SZMQ
5
+
6
+ def initialize()
7
+ SZMQ.context
8
+
9
+ end
10
+
11
+ def self.context
12
+ @context ||= ZMQ::Context.new
13
+ end
14
+
15
+ #returns a SSocket object
16
+ def socket(address, type)
17
+ SSocket.new(address, type)
18
+ end
19
+
20
+ #Takes two SSocket objects. One representing the client and other representing the worker. Upon receiving a INT command (ctrl + c), the proxy still be shut down
21
+ #and the frontend and backend sockets will be closed
22
+ def start_proxy(frontend_socket, backend_socket)
23
+
24
+ trap ("INT") do
25
+ puts "\nStopping proxy..."
26
+ frontend_socket.close
27
+ backend_socket.close
28
+ @close = true
29
+ end
30
+
31
+ frontend_socket.bind
32
+ backend_socket.bind
33
+
34
+ ZMQ::Device.new(frontend_socket.send(:get_socket), backend_socket.send(:get_socket))
35
+ #ZMQ::Device.new(backend_socket.send(:get_socket), frontend_socket.send(:get_socket))
36
+
37
+ #while !@close do
38
+ # text = "\r"
39
+ # text << "Online"
40
+ # space = " "
41
+ # 0.upto(2) do
42
+ # STDOUT.print text
43
+ # sleep 0.5
44
+ # STDOUT.print "\r#{space * (text.length - 1)}"
45
+ # sleep 0.5
46
+ # end
47
+ #end
48
+ end
49
+
50
+ def start_pub_sub_proxy(front_end, back_end)
51
+ trap ("INT") do
52
+ puts "\nStopping proxy..."
53
+ front_end.close
54
+ back_end.close
55
+ @close = true
56
+ end
57
+
58
+ front_end.bind
59
+ back_end.bind
60
+
61
+ ZMQ::Device.new(front_end.send(:get_socket), back_end.send(:get_socket))
62
+
63
+
64
+ end
65
+
66
+
67
+ private
68
+
69
+ def error_check(rc)
70
+ if ZMQ::Util.resultcode_ok?(rc)
71
+ false
72
+ else
73
+ STDERR.puts "Operation failed, [#{ZMQ::Util.errno}] description [#{ZMQ::Util.error_string}]"
74
+ caller(1).each { |callstack| STDERR.puts(callstack)}
75
+ raise SystemExit
76
+ end
77
+ end
78
+ end
79
+
80
+ class SSocket
81
+
82
+ def initialize(address, type)
83
+ @socket_types = %w(request reply dealer router pub sub push pull xpub xsub)
84
+ @context = SZMQ.context
85
+ @address = address
86
+ @socket_type_string = type
87
+ @socket_type = get_socket_type(type)
88
+ if @socket_types.include?(type.downcase)
89
+ @socket = @context.socket(@socket_type)
90
+ #for some reason if the socket is a push socket the linger option is causing the message not to get sent
91
+ @socket.setsockopt(ZMQ::LINGER, 0) unless @socket_type_string == 'push'
92
+ else
93
+ raise "#{type} is not a valid socket type"
94
+ end
95
+ end
96
+
97
+ def bind(address = @address)
98
+ if address.nil?
99
+ raise "An address must be set or passed"
100
+ end
101
+ error_check(@socket.bind(address))
102
+ end
103
+
104
+ def connect(address = @address)
105
+ if address.nil?
106
+ raise "An address must be set or passed"
107
+ end
108
+ error_check(@socket.connect(address))
109
+ end
110
+
111
+ def subscribe(channels, &block)
112
+ raise "This socket is not of type SUB and cannot subscribe to a channel" unless @socket_type_string == 'sub'
113
+ if channels.is_a? String
114
+ #Debug code
115
+ #puts "Subscribing to #{channels}"
116
+ error_check(@socket.setsockopt(ZMQ::SUBSCRIBE, channels))
117
+ elsif channels.is_a? Array
118
+ channels.each do |channel|
119
+ #debug code
120
+ #puts "Subscribing to #{channel}"
121
+ error_check(@socket.setsockopt(ZMQ::SUBSCRIBE, channel))
122
+ end
123
+ end
124
+ connect
125
+ channel = ''
126
+ message = ''
127
+ loop do
128
+ @socket.recv_string(channel)
129
+ @socket.recv_string(message)
130
+ if block_given?
131
+ yield(channel, message)
132
+ else
133
+ [channel, message]
134
+ end
135
+ end
136
+ end
137
+
138
+ def publish_message(channel, message)
139
+ raise 'this socket is not of type PUB and cannot publish a message' unless @socket_type_string == 'pub'
140
+ @socket.send_string(channel, ZMQ::SNDMORE)
141
+ @socket.send_string(message)
142
+ end
143
+
144
+ def close
145
+ error_check(@socket.close)
146
+ end
147
+
148
+ def send_message(message)
149
+ trap ("INT") do
150
+ puts "Received interrupt..."
151
+ @socket.close
152
+ end
153
+ if @socket_type == ZMQ::REQ
154
+ @socket.send_string(message)
155
+ reply = ''
156
+ @socket.recv_string(reply)
157
+ reply
158
+ #end
159
+ else
160
+ @socket.send_string(message)
161
+ nil
162
+ end
163
+ end
164
+
165
+ #This method listens for messages coming in and then processes them will the block passed into the method. If no block is passed, messages will be received but
166
+ #will then be dropped on the floor. If the socket is of type REP and no block is given, the receive method will reply with "0" indicating that the message was received
167
+ #currently, the result of the block is sent back as a reply for REP sockets. This may change later
168
+ #TODO - Currently, the send_string method is causing the interupt to be delayed until the next message is received. need to find a way to fix this
169
+ #TODO - add support for SUB sockets
170
+ def receive(&block)
171
+
172
+ trap ("INT") do
173
+ puts "Received interrupt..."
174
+ @close = true
175
+ end
176
+ received_message = ''
177
+
178
+ if @socket_type == ZMQ::PULL
179
+ loop do
180
+ break if @close
181
+ @socket.recv_string(received_message)
182
+ if block_given?
183
+ yield(received_message)
184
+ end
185
+ end
186
+
187
+ elsif @socket_type == ZMQ::REP
188
+ reply_message = ''
189
+ loop do
190
+ break if @close
191
+ unless @socket.recv_string(received_message) == -1
192
+ @socket.recv_string(received_message)
193
+ if block_given?
194
+ reply_message = yield(received_message)
195
+ else
196
+ reply_message = "0"
197
+ end
198
+ end
199
+ @socket.send_string(reply_message)
200
+ end
201
+ else
202
+ raise "Socket type of #{@socket_type_string} does not receive messages"
203
+ end
204
+ end
205
+
206
+
207
+ private
208
+
209
+ def error_check(rc)
210
+ if ZMQ::Util.resultcode_ok?(rc)
211
+ false
212
+ else
213
+ STDERR.puts "Operation failed, [#{ZMQ::Util.errno}] description [#{ZMQ::Util.error_string}]"
214
+ caller(1).each { |callstack| STDERR.puts(callstack)}
215
+ raise SystemExit
216
+ end
217
+ end
218
+
219
+ def get_socket
220
+ @socket
221
+ end
222
+
223
+ def get_socket_type(type)
224
+ case type
225
+ when 'request'
226
+ ZMQ::REQ
227
+ when 'reply'
228
+ ZMQ::REP
229
+ when 'dealer'
230
+ ZMQ::DEALER
231
+ when 'router'
232
+ ZMQ::ROUTER
233
+ when 'pub'
234
+ ZMQ::PUB
235
+ when 'sub'
236
+ ZMQ::SUB
237
+ when 'push'
238
+ ZMQ::PUSH
239
+ when 'pull'
240
+ ZMQ::PULL
241
+ when 'xpub'
242
+ ZMQ::XPUB
243
+ when 'xsub'
244
+ ZMQ::XSUB
245
+ else
246
+ nil
247
+ end
248
+ end
249
+
250
+ end
251
+ end
252
+
@@ -0,0 +1,126 @@
1
+ require_relative 'szmq/szmq'
2
+ require_relative 'mongo_driver'
3
+ require 'chronic'
4
+ require 'json'
5
+
6
+ module Kymera
7
+
8
+ class TestResultsCollector
9
+
10
+ def initialize
11
+ @config = Kymera::Config.new
12
+ @zmq = Kymera::SZMQ.new
13
+ @inc_socket = @zmq.socket("tcp://*:#{@config.result_collector["inc_listening_port"]}", 'pull')
14
+ @out_socket = @zmq.socket(@config.result_collector["result_bus_address"], 'pub')
15
+ @inc_socket.bind
16
+ @out_socket.connect
17
+ end
18
+
19
+ def listen
20
+ puts "Test collector started..."
21
+ test_count = ''
22
+ start_test_count = ''
23
+ @run_id = ''
24
+ results = ''
25
+ runner = ''
26
+ start_time = ''
27
+ count = 0
28
+ @inc_socket.receive do |message|
29
+ parsed_message = JSON.parse message
30
+ if count < 1
31
+ @run_id = parsed_message["run_id"]
32
+ test_count = parsed_message["test_count"].to_i
33
+ start_test_count = parsed_message["test_count"]
34
+ runner = parsed_message["runner"]
35
+ results << parsed_message["results"]
36
+ start_time = parsed_message["start_time"]
37
+ puts 'Results run started with the following configuration:'
38
+ puts "Run ID: #{@run_id}"
39
+ puts "Test count: #{test_count}"
40
+ test_count -= 1
41
+ count +=1
42
+ elsif test_count > 0
43
+ results << parsed_message["results"]
44
+ test_count -= 1
45
+ end
46
+
47
+ if test_count <= 0
48
+ finalize_results(start_test_count, @run_id, results, runner, start_time)
49
+ test_count = ''
50
+ @run_id = ''
51
+ results = ''
52
+ runner = ''
53
+ start_time = ''
54
+ count = 0
55
+ end
56
+
57
+
58
+ end
59
+
60
+ end
61
+
62
+ private
63
+
64
+ def finalize_results(test_count, run_id, results, runner, start_time)
65
+ if runner.downcase == 'cucumber'
66
+
67
+ begin
68
+ #r_results = Kymera::Cucumber::ResultsParser.summarize_results(results)
69
+ puts "Summarizing results.."
70
+ r_results = Kymera::Cucumber::ResultsParser.summarize_results(results)
71
+ puts "Getting pass count..."
72
+ pass_count = Kymera::Cucumber::ResultsParser.scenario_counts[:pass]
73
+ puts "Getting fail count..."
74
+ fail_count = Kymera::Cucumber::ResultsParser.scenario_counts[:fail]
75
+ # r_results = Kymera::Cucumber::ResultsParser.summarize_results(results)
76
+ puts "Converting results to html..."
77
+ html_results = Kymera::Cucumber::HTMLResultsParser.to_html(results)
78
+ puts "Converting summary to html..."
79
+ html_summary = Kymera::Cucumber::HTMLResultsParser.to_html(r_results)
80
+ puts "Setting end time"
81
+ end_time = Time.now
82
+ # puts html_results
83
+ # Kymera::MongoDriver.log_results(build_test_log(test_count, run_id, results, r_results), '10.6.49.83', 27017, 'apollo', 'test_runs')
84
+ puts "Starting database logging processes..."
85
+ Kymera::MongoDriver.log_results(build_test_log(test_count, run_id, html_results, html_summary, start_time, end_time.to_s, pass_count, fail_count), @config.result_collector["mongodb_address"],
86
+ @config.result_collector["mongodb_port"].to_i, @config.result_collector["mongodb_database_name"], @config.result_collector["mongodb_collection_name"])
87
+ puts "Setting run id..."
88
+ rescue => e
89
+ puts "There was an error in the logging process:"
90
+ puts e
91
+ ensure
92
+ run_id = "end_#{@run_id}"
93
+ puts "Sending results to client..."
94
+ @out_socket.publish_message(run_id, r_results)
95
+ end
96
+ end
97
+ end
98
+
99
+ def build_test_log(test_count, run_id, results, summary, start_time, end_time, pass_count, fail_count)
100
+ begin
101
+ log_message = {}
102
+ log_message["run_id"] = run_id
103
+ log_message["test_count"] = test_count
104
+ log_message["results"] = results
105
+ log_message["summary"] = summary
106
+ log_message["start_time"] = start_time
107
+ log_message["end_time"] = end_time
108
+ log_message["duration"] = Chronic.parse(end_time) - Chronic.parse(start_time)
109
+ log_message["pass_count"] = pass_count
110
+ log_message["fail_count"] = fail_count
111
+
112
+ JSON.generate log_message
113
+ rescue => e
114
+ puts e
115
+ end
116
+
117
+ end
118
+
119
+
120
+
121
+
122
+
123
+ end
124
+
125
+
126
+ end
@@ -0,0 +1,3 @@
1
+ module Kymera
2
+ VERSION = "0.1.0"
3
+ end