chemistrykit 3.8.1 → 3.9.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG.md +15 -5
- data/Gemfile +4 -0
- data/README.md +13 -1
- data/Rakefile +3 -3
- data/chemistrykit.gemspec +7 -3
- data/features/brew.feature +1 -1
- data/features/chemists.feature +56 -1
- data/features/concurrency.feature +2 -2
- data/features/exit_status.feature +1 -0
- data/features/logging.feature +88 -85
- data/features/reporting.feature +105 -0
- data/features/step_definitions/steps.rb +4 -4
- data/features/support/env.rb +1 -1
- data/features/tags.feature +1 -0
- data/lib/chemistrykit/chemist.rb +6 -1
- data/lib/chemistrykit/chemist/repository/csv_chemist_repository.rb +1 -1
- data/lib/chemistrykit/cli/cli.rb +33 -12
- data/lib/chemistrykit/configuration.rb +2 -2
- data/lib/chemistrykit/formula/formula_lab.rb +13 -0
- data/lib/chemistrykit/{parallel_tests_mods.rb → parallel_tests/rspec/runner.rb} +5 -5
- data/lib/chemistrykit/reporting/html_report_assembler.rb +170 -0
- data/lib/chemistrykit/rspec/html_formatter.rb +241 -0
- data/lib/chemistrykit/rspec/j_unit_formatter.rb +124 -0
- data/report/config.rb +28 -0
- data/report/index.html +213 -0
- data/report/javascripts/foundation/foundation.abide.js +194 -0
- data/report/javascripts/foundation/foundation.alerts.js +52 -0
- data/report/javascripts/foundation/foundation.clearing.js +516 -0
- data/report/javascripts/foundation/foundation.cookie.js +74 -0
- data/report/javascripts/foundation/foundation.dropdown.js +177 -0
- data/report/javascripts/foundation/foundation.forms.js +533 -0
- data/report/javascripts/foundation/foundation.interchange.js +280 -0
- data/report/javascripts/foundation/foundation.joyride.js +850 -0
- data/report/javascripts/foundation/foundation.js +440 -0
- data/report/javascripts/foundation/foundation.magellan.js +135 -0
- data/report/javascripts/foundation/foundation.orbit.js +412 -0
- data/report/javascripts/foundation/foundation.placeholder.js +179 -0
- data/report/javascripts/foundation/foundation.reveal.js +330 -0
- data/report/javascripts/foundation/foundation.section.js +400 -0
- data/report/javascripts/foundation/foundation.tooltips.js +208 -0
- data/report/javascripts/foundation/foundation.topbar.js +300 -0
- data/report/javascripts/vendor/custom.modernizr.js +4 -0
- data/report/javascripts/vendor/jquery.js +9789 -0
- data/report/sass/_normalize.scss +402 -0
- data/report/sass/_settings.scss +1301 -0
- data/report/sass/app.scss +571 -0
- data/report/stylesheets/app.css +3636 -0
- data/spec/integration/lib/chemistrykit/formula/formula_lab_spec.rb +26 -2
- data/spec/integration/lib/chemistrykit/reporting/html_reporting_assembler_spec.rb +18 -0
- data/spec/support/evidence/results_0.html +30 -0
- data/spec/support/evidence/results_1.html +27 -0
- data/spec/unit/lib/chemistrykit/chemist/repository/csv_chemist_repository_spec.rb +15 -2
- data/spec/unit/lib/chemistrykit/chemist_spec.rb +17 -1
- data/spec/unit/lib/chemistrykit/configuration_spec.rb +2 -2
- data/spec/unit/lib/chemistrykit/formula/formula_lab_spec.rb +7 -0
- data/spec/unit/lib/chemistrykit/reporting/html_reporting_assembler_spec.rb +22 -0
- metadata +94 -13
- data/lib/chemistrykit/j_unit.rb +0 -121
@@ -15,7 +15,7 @@ Then(/^there should be "(.*?)" unique results files in the "(.*?)" directory$/)
|
|
15
15
|
files = Dir.glob(File.join(current_dir, logs_path, '*.xml'))
|
16
16
|
count = 0
|
17
17
|
files.each do |file|
|
18
|
-
count += 1 if file =~ /
|
18
|
+
count += 1 if file =~ /junit_\d+\.xml/
|
19
19
|
end
|
20
20
|
count.should == number_files.to_i
|
21
21
|
end
|
@@ -26,11 +26,11 @@ Then(/^there should be "(.*?)" "(.*?)" log files in "(.*?)"$/) do |number, type,
|
|
26
26
|
files.each do |file|
|
27
27
|
case type
|
28
28
|
when 'failed image'
|
29
|
-
count += 1 if file =~
|
29
|
+
count += 1 if file =~ /failshot\.png/
|
30
30
|
when 'report'
|
31
|
-
count += 1 if file =~
|
31
|
+
count += 1 if file =~ /sauce_job\.log/
|
32
32
|
when 'sauce log'
|
33
|
-
count += 1 if file =~
|
33
|
+
count += 1 if file =~ /server\.log/
|
34
34
|
end
|
35
35
|
end
|
36
36
|
count.should == number.to_i
|
data/features/support/env.rb
CHANGED
data/features/tags.feature
CHANGED
data/lib/chemistrykit/chemist.rb
CHANGED
@@ -15,10 +15,15 @@ module ChemistryKit
|
|
15
15
|
|
16
16
|
def data=(data)
|
17
17
|
data.each do |key, value|
|
18
|
-
send("#{key}=", value) unless key == :key
|
18
|
+
send("#{key}=", value) unless key == :key || key == :with
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
def with(chemist)
|
23
|
+
send("#{chemist.type}=", chemist)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
22
27
|
# allow this object to be set with arbitrary key value data
|
23
28
|
def method_missing(name, *arguments)
|
24
29
|
value = arguments[0]
|
@@ -34,7 +34,7 @@ module ChemistryKit
|
|
34
34
|
@tables.each do |table|
|
35
35
|
chemist_data = table.find { |row| row[:key] == key }
|
36
36
|
chemist = make_chemist(key, chemist_data[:type], chemist_data) if chemist_data
|
37
|
-
return fetch_from_cache chemist
|
37
|
+
return fetch_from_cache(chemist) if chemist
|
38
38
|
end
|
39
39
|
raise ArgumentError, "Chemist for type \"#{key}\" not found!"
|
40
40
|
end
|
data/lib/chemistrykit/cli/cli.rb
CHANGED
@@ -14,9 +14,13 @@ require 'chemistrykit/chemist/repository/csv_chemist_repository'
|
|
14
14
|
require 'selenium_connect'
|
15
15
|
require 'chemistrykit/configuration'
|
16
16
|
require 'parallel_tests'
|
17
|
-
require 'chemistrykit/
|
18
|
-
require 'chemistrykit/
|
17
|
+
require 'chemistrykit/parallel_tests/rspec/runner'
|
18
|
+
require 'chemistrykit/rspec/j_unit_formatter'
|
19
19
|
|
20
|
+
require 'rspec/core/formatters/html_formatter'
|
21
|
+
require 'chemistrykit/rspec/html_formatter'
|
22
|
+
|
23
|
+
require 'chemistrykit/reporting/html_report_assembler'
|
20
24
|
module ChemistryKit
|
21
25
|
module CLI
|
22
26
|
|
@@ -40,22 +44,22 @@ module ChemistryKit
|
|
40
44
|
desc 'tags', 'Lists all tags in use in the test harness.'
|
41
45
|
def tags
|
42
46
|
beakers = Dir.glob(File.join(Dir.getwd, 'beakers/*'))
|
43
|
-
RSpec.configure do |c|
|
47
|
+
::RSpec.configure do |c|
|
44
48
|
c.add_setting :used_tags
|
45
|
-
c.before(:suite) { RSpec.configuration.used_tags = [] }
|
49
|
+
c.before(:suite) { ::RSpec.configuration.used_tags = [] }
|
46
50
|
c.around(:each) do |example|
|
47
51
|
standard_keys = [:example_group, :example_group_block, :description_args, :caller, :execution_result]
|
48
52
|
example.metadata.each do |key, value|
|
49
53
|
tag = "#{key}:#{value}" unless standard_keys.include?(key)
|
50
|
-
RSpec.configuration.used_tags.push tag unless RSpec.configuration.used_tags.include?(tag) || tag.nil?
|
54
|
+
::RSpec.configuration.used_tags.push tag unless ::RSpec.configuration.used_tags.include?(tag) || tag.nil?
|
51
55
|
end
|
52
56
|
end
|
53
57
|
c.after(:suite) do
|
54
58
|
puts "\nTags used in harness:\n\n"
|
55
|
-
puts RSpec.configuration.used_tags.sort
|
59
|
+
puts ::RSpec.configuration.used_tags.sort
|
56
60
|
end
|
57
61
|
end
|
58
|
-
RSpec::Core::Runner.run(beakers)
|
62
|
+
::RSpec::Core::Runner.run(beakers)
|
59
63
|
end
|
60
64
|
|
61
65
|
desc 'brew', 'Run ChemistryKit'
|
@@ -108,15 +112,26 @@ module ChemistryKit
|
|
108
112
|
|
109
113
|
# based on concurrency parameter run tests
|
110
114
|
if config.concurrency > 1 && ! options['parallel']
|
111
|
-
run_in_parallel beakers, config.concurrency, @tags, options
|
115
|
+
exit_code = run_in_parallel beakers, config.concurrency, @tags, options
|
112
116
|
else
|
113
|
-
run_rspec beakers
|
117
|
+
exit_code = run_rspec beakers
|
114
118
|
end
|
115
119
|
|
120
|
+
process_html unless options['parallel']
|
121
|
+
exit_code unless options['parallel']
|
116
122
|
end
|
117
123
|
|
118
124
|
protected
|
119
125
|
|
126
|
+
def process_html
|
127
|
+
puts 'PROCESS HTML CALLED'
|
128
|
+
File.join(Dir.getwd, 'evidence')
|
129
|
+
results_folder = File.join(Dir.getwd, 'evidence')
|
130
|
+
output_file = File.join(Dir.getwd, 'evidence', 'final_results.html')
|
131
|
+
assembler = ChemistryKit::Reporting::HtmlReportAssembler.new(results_folder, output_file)
|
132
|
+
assembler.assemble
|
133
|
+
end
|
134
|
+
|
120
135
|
def override_configs(options, config)
|
121
136
|
# TODO: expand this to allow for more overrides as needed
|
122
137
|
config.log.results_file = options['results_file'] if options['results_file']
|
@@ -157,7 +172,7 @@ module ChemistryKit
|
|
157
172
|
|
158
173
|
# rubocop:disable MethodLength
|
159
174
|
def rspec_config(config) # Some of these bits work and others don't
|
160
|
-
RSpec.configure do |c|
|
175
|
+
::RSpec.configure do |c|
|
161
176
|
c.treat_symbols_as_metadata_keys_with_true_values = true
|
162
177
|
unless options[:all]
|
163
178
|
c.filter_run @tags[:filter] unless @tags[:filter].nil?
|
@@ -209,12 +224,18 @@ module ChemistryKit
|
|
209
224
|
c.output_stream = $stdout
|
210
225
|
c.add_formatter 'progress'
|
211
226
|
|
227
|
+
html_log_name = options[:parallel] ? "results_#{options[:parallel]}.html" : 'results_0.html'
|
228
|
+
|
229
|
+
c.add_formatter(ChemistryKit::RSpec::HtmlFormatter, File.join(Dir.getwd, config.log.path, html_log_name))
|
230
|
+
# c.add_formatter(::RSpec::Core::Formatters::HtmlFormatter, File.join(Dir.getwd, config.log.path, html_log_name))
|
231
|
+
|
212
232
|
# for rspec-retry
|
213
233
|
c.verbose_retry = true # for rspec-retry
|
214
234
|
c.default_retry_count = config.retries_on_failure
|
215
235
|
|
216
236
|
if config.concurrency == 1 || options['parallel']
|
217
|
-
|
237
|
+
junit_log_name = options[:parallel] ? "junit_#{options[:parallel]}.xml" : 'junit.xml'
|
238
|
+
c.add_formatter(ChemistryKit::RSpec::JUnitFormatter, File.join(Dir.getwd, config.log.path, junit_log_name))
|
218
239
|
end
|
219
240
|
end
|
220
241
|
end
|
@@ -230,7 +251,7 @@ module ChemistryKit
|
|
230
251
|
end
|
231
252
|
|
232
253
|
def run_rspec(beakers)
|
233
|
-
RSpec::Core::Runner.run(beakers)
|
254
|
+
::RSpec::Core::Runner.run(beakers)
|
234
255
|
end
|
235
256
|
end # CkitCLI
|
236
257
|
end # CLI
|
@@ -25,7 +25,7 @@ module ChemistryKit
|
|
25
25
|
@log = OpenStruct.new
|
26
26
|
@log.path = 'evidence'
|
27
27
|
@log.results_file = 'results_junit.xml'
|
28
|
-
@log.format = '
|
28
|
+
@log.format = 'ChemistryKit::RSpec::JUnitFormatter'
|
29
29
|
|
30
30
|
# overide with argument
|
31
31
|
populate_with_hash hash
|
@@ -33,7 +33,7 @@ module ChemistryKit
|
|
33
33
|
|
34
34
|
def log=(log_hash)
|
35
35
|
log_hash.each do |key, value|
|
36
|
-
value = '
|
36
|
+
value = 'ChemistryKit::RSpec::JUnitFormatter' if key == :format && value =~ /junit/i
|
37
37
|
@log.send("#{key}=", value) unless value.nil?
|
38
38
|
end
|
39
39
|
end
|
@@ -14,6 +14,7 @@ module ChemistryKit
|
|
14
14
|
@chemist_repository = chemist_repository
|
15
15
|
raise ArgumentError, "Formula directory \"#{formulas_dir}\" does not exist!" unless File.directory?(formulas_dir)
|
16
16
|
@formulas_dir = formulas_dir
|
17
|
+
@sub_chemists = []
|
17
18
|
end
|
18
19
|
|
19
20
|
def using(formula_key)
|
@@ -23,6 +24,7 @@ module ChemistryKit
|
|
23
24
|
|
24
25
|
def mix(formula_key = nil)
|
25
26
|
@formula = formula_key ||= formula
|
27
|
+
compose_chemists unless @sub_chemists.empty?
|
26
28
|
raise ArgumentError, 'A formula key must be defined!' unless @formula
|
27
29
|
formula_file = find_formula_file
|
28
30
|
class_name = get_class_name formula_file
|
@@ -49,8 +51,19 @@ module ChemistryKit
|
|
49
51
|
self
|
50
52
|
end
|
51
53
|
|
54
|
+
def and_with(key)
|
55
|
+
@sub_chemists << @chemist_repository.load_chemist_by_key(key)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
52
59
|
private
|
53
60
|
|
61
|
+
def compose_chemists
|
62
|
+
@sub_chemists.each do |sub_chemist|
|
63
|
+
@chemist.with sub_chemist
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
54
67
|
def find_formula_file
|
55
68
|
Find.find(@formulas_dir) do |path|
|
56
69
|
return path if path =~ /.*\/#{@formula}\.rb$/
|
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'parallel_tests/rspec/runner'
|
4
4
|
require 'parallel_tests/test/runner'
|
5
|
-
require 'securerandom'
|
6
5
|
|
7
6
|
module ParallelTests
|
8
7
|
module RSpec
|
9
8
|
# Monkey Patching the ParallelTest RSpec Runner class to work with CKit's config and binary
|
9
|
+
# TODO: we could probably make this its own typed runner versus extinding the rspec
|
10
|
+
# one if this continues to get used
|
10
11
|
class Runner < ParallelTests::Test::Runner
|
11
12
|
class << self
|
12
13
|
|
@@ -18,7 +19,8 @@ module ParallelTests
|
|
18
19
|
# cmd = [exe, options[:test_options], (rspec_2_color if version == 2), spec_opts, *test_files].compact.join(" ")
|
19
20
|
# NOTE: The above line was modified to conform to ckit's command line constraints
|
20
21
|
|
21
|
-
|
22
|
+
# Passes the process number into so that any generated assets can be identified.
|
23
|
+
cmd = [exe, "--parallel=#{process_number}", options[:test_options]].compact.join(' ')
|
22
24
|
cmd << test_files.join(' ')
|
23
25
|
|
24
26
|
# This concatenates the command into `bundle exec ckit brew --beakers=beaker1 beaker2 beaker3 etc`
|
@@ -32,9 +34,7 @@ module ParallelTests
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def determine_executable
|
35
|
-
|
36
|
-
file_name = "parallel_part_#{uuid}.xml"
|
37
|
-
"bundle exec ckit brew --parallel --results_file #{file_name}"
|
37
|
+
'bundle exec ckit brew'
|
38
38
|
end
|
39
39
|
|
40
40
|
def test_file_name
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module ChemistryKit
|
6
|
+
module Reporting
|
7
|
+
class HtmlReportAssembler
|
8
|
+
# TODO: Refactor to remove this cop exception
|
9
|
+
# rubocop:disable MethodLength, ParameterLists
|
10
|
+
def initialize(results_path, output_file)
|
11
|
+
@results_path = results_path
|
12
|
+
@output_file = output_file
|
13
|
+
@groups = []
|
14
|
+
@total_reactions = 0
|
15
|
+
@total_failures = 0
|
16
|
+
@total_duration = 0
|
17
|
+
@total_pending = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def assemble
|
21
|
+
result_files = Dir.glob(File.join(@results_path, 'results_*.html'))
|
22
|
+
|
23
|
+
result_files.each do |file|
|
24
|
+
doc = Nokogiri.HTML(open(file))
|
25
|
+
|
26
|
+
doc.css('.results').each do |element|
|
27
|
+
@total_reactions += element['data-count'].to_i
|
28
|
+
@total_failures += element['data-failures'].to_i
|
29
|
+
@total_pending += element['data-pendings'].to_i
|
30
|
+
@total_duration += element['data-duration'].to_f
|
31
|
+
end
|
32
|
+
|
33
|
+
doc.css('.example-group').each do |group|
|
34
|
+
@groups << group
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
status = @total_failures > 0 ? 'failing' : 'passing'
|
39
|
+
|
40
|
+
File.open(@output_file, 'w') do |file|
|
41
|
+
|
42
|
+
file.write '<!DOCTYPE html>'
|
43
|
+
file.write '<!--[if IE 8]> <html class="no-js lt-ie9" lang="en" > <![endif]-->'
|
44
|
+
file.write '<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->'
|
45
|
+
|
46
|
+
file.write get_report_head 'passing'
|
47
|
+
file.write '<body>'
|
48
|
+
file.write get_report_header
|
49
|
+
file.write '<div class="report">'
|
50
|
+
file.write get_report_summary status, @groups.count, @total_reactions, @total_failures, @total_pending, @total_duration
|
51
|
+
|
52
|
+
@groups.each do |group|
|
53
|
+
file.write group
|
54
|
+
end
|
55
|
+
|
56
|
+
file.write '</div>' # closes .report
|
57
|
+
|
58
|
+
file.write get_report_endscripts
|
59
|
+
file.write '</body>'
|
60
|
+
file.write '</html>'
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_report_head(state)
|
67
|
+
build_fragment do |doc|
|
68
|
+
doc.head do
|
69
|
+
doc.meta(charset: 'utf-8')
|
70
|
+
doc.meta(name: 'viewport', content: 'width=device-width')
|
71
|
+
doc.title { doc.text "ChemistryKit - #{state.capitalize}" }
|
72
|
+
doc.style(type: 'text/css') do
|
73
|
+
doc.text load_from_file(File.join(File.dirname(File.expand_path(__FILE__)), '../../../report/', 'stylesheets', 'app.css'))
|
74
|
+
end
|
75
|
+
doc.link(href: 'http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css', rel: 'stylesheet')
|
76
|
+
doc.script do
|
77
|
+
doc.text load_from_file(File.join(File.dirname(File.expand_path(__FILE__)), '../../../report/', 'javascripts', 'vendor', 'custom.modernizr.js'))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_report_header
|
84
|
+
build_fragment do |doc|
|
85
|
+
doc.header(class: 'row') do
|
86
|
+
doc.div(class: 'large-12 columns') do
|
87
|
+
doc.h2 { doc.text 'ChemistryKit Brew Results' }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_report_summary(status, beakers, reactions, failures, pendings, duration)
|
94
|
+
if status == 'passing'
|
95
|
+
icon = 'icon-ok-sign'
|
96
|
+
message = ' Brew Passing'
|
97
|
+
else
|
98
|
+
icon = 'icon-warning-sign'
|
99
|
+
message = ' Brew Failing'
|
100
|
+
end
|
101
|
+
build_fragment do |doc|
|
102
|
+
doc.div(class: 'summary') do
|
103
|
+
doc.div(class: "row status #{status}") do
|
104
|
+
doc.div(class: 'large-12 columns') do
|
105
|
+
doc.h1 do
|
106
|
+
doc.i(class: icon)
|
107
|
+
doc.text message
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
doc.div(class: 'row details') do
|
112
|
+
doc.div(class: 'large-12 columns') do
|
113
|
+
doc.ul(class: 'large-block-grid-5') do
|
114
|
+
doc.li do
|
115
|
+
doc.i(class: 'icon-beaker passing-color')
|
116
|
+
doc.p { doc.text "#{beakers.to_s} Beakers" }
|
117
|
+
end
|
118
|
+
doc.li do
|
119
|
+
doc.i(class: 'icon-tint reaction-color')
|
120
|
+
doc.p { doc.text "#{reactions.to_s} Reactions" }
|
121
|
+
end
|
122
|
+
doc.li do
|
123
|
+
doc.i(class: 'icon-warning-sign failing-color')
|
124
|
+
doc.p { doc.text "#{failures.to_s} Failures" }
|
125
|
+
end
|
126
|
+
doc.li do
|
127
|
+
doc.i(class: 'icon-question-sign pending-color')
|
128
|
+
doc.p { doc.text "#{pendings.to_s} Pending" }
|
129
|
+
end
|
130
|
+
doc.li do
|
131
|
+
doc.i(class: 'icon-time')
|
132
|
+
doc.p { doc.text "#{ sprintf("%.1i", duration / 60) }m Duration" }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_report_endscripts
|
142
|
+
build_fragment do |doc|
|
143
|
+
doc.script do
|
144
|
+
doc.text load_from_file(File.join(File.dirname(File.expand_path(__FILE__)), '../../../report/', 'javascripts', 'vendor', 'jquery.js'))
|
145
|
+
end
|
146
|
+
doc.script do
|
147
|
+
doc.text load_from_file(File.join(File.dirname(File.expand_path(__FILE__)), '../../../report/', 'javascripts', 'foundation', 'foundation.js'))
|
148
|
+
end
|
149
|
+
doc.script do
|
150
|
+
doc.text load_from_file(File.join(File.dirname(File.expand_path(__FILE__)), '../../../report/', 'javascripts', 'foundation', 'foundation.section.js'))
|
151
|
+
end
|
152
|
+
doc.script { doc.text '$(document).foundation();' }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_fragment
|
157
|
+
final = Nokogiri::HTML::DocumentFragment.parse ''
|
158
|
+
Nokogiri::HTML::Builder.with(final) do |doc|
|
159
|
+
yield doc
|
160
|
+
end
|
161
|
+
final.to_html
|
162
|
+
end
|
163
|
+
|
164
|
+
def load_from_file(path)
|
165
|
+
File.open(path, 'rb') { |file| file.read }
|
166
|
+
end
|
167
|
+
# rubocop:enable MethodLength, ParameterLists
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'rspec/core/formatters/base_text_formatter'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'erb'
|
6
|
+
require 'rspec/core/formatters/snippet_extractor'
|
7
|
+
require 'pygments'
|
8
|
+
require 'securerandom'
|
9
|
+
|
10
|
+
module ChemistryKit
|
11
|
+
module RSpec
|
12
|
+
class HtmlFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
|
13
|
+
include ERB::Util # for the #h method
|
14
|
+
def initialize(output)
|
15
|
+
super(output)
|
16
|
+
@example_group_number = 0
|
17
|
+
@example_number = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def message(message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def start(example_count)
|
24
|
+
super(example_count)
|
25
|
+
@output_html = ''
|
26
|
+
end
|
27
|
+
|
28
|
+
def example_group_started(example_group)
|
29
|
+
@example_group = example_group
|
30
|
+
@example_group_html = ''
|
31
|
+
@example_group_number += 1
|
32
|
+
@example_group_status = 'passing'
|
33
|
+
end
|
34
|
+
|
35
|
+
def example_group_finished(example_group)
|
36
|
+
@output_html << build_fragment do |doc|
|
37
|
+
doc.div(class: "row example-group #{@example_group_status}") do
|
38
|
+
doc.div(class: 'large-12 columns') do
|
39
|
+
doc.h3 do
|
40
|
+
doc.i(class: 'icon-beaker')
|
41
|
+
doc.text ' ' + example_group.description
|
42
|
+
end
|
43
|
+
doc.div(class: 'examples') do
|
44
|
+
doc << @example_group_html
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def example_started(example)
|
52
|
+
super(example)
|
53
|
+
@example_number += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def example_passed(example)
|
57
|
+
@example_group_html += render_example('passing', example) {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def example_pending(example)
|
61
|
+
super(example)
|
62
|
+
@example_group_html += render_example('pending', example) do |doc|
|
63
|
+
doc.div(class: 'row exception') do
|
64
|
+
doc.div(class: 'large-12 columns') do
|
65
|
+
doc.pre do
|
66
|
+
doc.text "PENDING: #{example.metadata[:execution_result][:pending_message]}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def example_failed(example)
|
74
|
+
super(example)
|
75
|
+
exception = example.metadata[:execution_result][:exception]
|
76
|
+
@example_group_status = 'failing'
|
77
|
+
@example_group_html += render_example('failing', example) do |doc|
|
78
|
+
doc.div(class: 'row exception') do
|
79
|
+
doc.div(class: 'large-12 columns') do
|
80
|
+
doc.pre do
|
81
|
+
message = exception.message if exception
|
82
|
+
doc.text message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
doc.div(class: 'row code-snippet') do
|
87
|
+
doc.div(class: 'large-12 columns') do
|
88
|
+
doc << render_code(exception)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
doc << render_extra_content(example)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# TODO: put the right methods private, or better yet, pull this stuff out into its own
|
96
|
+
# set of classes
|
97
|
+
def render_extra_content(example)
|
98
|
+
build_fragment do |doc|
|
99
|
+
doc.div(class: 'row extra-content') do
|
100
|
+
doc.div(class: 'large-12 columns') do
|
101
|
+
doc.div(class: 'section-container auto', 'data-section' => '') do
|
102
|
+
doc << render_stack_trace(example)
|
103
|
+
doc << render_log_if_found(example, 'server.log')
|
104
|
+
doc << render_log_if_found(example, 'chromedriver.log')
|
105
|
+
doc << render_log_if_found(example, 'firefox.log')
|
106
|
+
doc << render_log_if_found(example, 'sauce_job.log')
|
107
|
+
doc << render_dom_html_if_found(example)
|
108
|
+
doc << render_failshot_if_found(example)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def render_dom_html_if_found(example)
|
116
|
+
# TODO: pull out the common code for checking if the log file exists
|
117
|
+
beaker_folder = slugify(@example_group.description)
|
118
|
+
example_folder = slugify(@example_group.description + '_' + example.description)
|
119
|
+
path = File.join(Dir.getwd, 'evidence', beaker_folder, example_folder, 'dom.html')
|
120
|
+
if File.exist?(path)
|
121
|
+
render_section('Dom Html') do |doc|
|
122
|
+
doc << Pygments.highlight(File.read(path), lexer: 'html')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# TODO: replace the section id with a uuid or something....
|
128
|
+
def render_failshot_if_found(example)
|
129
|
+
beaker_folder = slugify(@example_group.description)
|
130
|
+
example_folder = slugify(@example_group.description + '_' + example.description)
|
131
|
+
path = File.join(Dir.getwd, 'evidence', beaker_folder, example_folder, 'failshot.png')
|
132
|
+
if File.exist?(path)
|
133
|
+
render_section('Failure Screenshot') do |doc|
|
134
|
+
doc.img(src: path)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def render_log_if_found(example, log)
|
140
|
+
beaker_folder = slugify(@example_group.description)
|
141
|
+
example_folder = slugify(@example_group.description + '_' + example.description)
|
142
|
+
log_path = File.join(Dir.getwd, 'evidence', beaker_folder, example_folder, log)
|
143
|
+
if File.exist?(log_path)
|
144
|
+
render_section(log.capitalize) do |doc|
|
145
|
+
doc.pre do
|
146
|
+
doc.text File.open(log_path, 'rb') { |file| file.read }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def slugify(string)
|
153
|
+
string.downcase.strip.gsub(' ', '_').gsub(/[^\w-]/, '')
|
154
|
+
end
|
155
|
+
|
156
|
+
def render_stack_trace(example)
|
157
|
+
exception = example.metadata[:execution_result][:exception]
|
158
|
+
render_section('Stack Trace') do |doc|
|
159
|
+
doc.pre do
|
160
|
+
doc.text format_backtrace(exception.backtrace, example).join("\n")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def render_code(exception)
|
166
|
+
backtrace = exception.backtrace.map { |line| backtrace_line(line) }
|
167
|
+
backtrace.compact!
|
168
|
+
@snippet_extractor ||= ::RSpec::Core::Formatters::SnippetExtractor.new
|
169
|
+
"<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(backtrace)}</code></pre>"
|
170
|
+
end
|
171
|
+
|
172
|
+
def render_section(title)
|
173
|
+
panel_id = SecureRandom.uuid
|
174
|
+
build_fragment do |doc|
|
175
|
+
doc.section do
|
176
|
+
doc.p(class: 'title', 'data-section-title' => '') do
|
177
|
+
doc.a(href: "#panel#{panel_id}") { doc.text title }
|
178
|
+
end
|
179
|
+
doc.div(class: 'content', 'data-section-content' => '') do
|
180
|
+
yield doc
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def render_example(status, example)
|
187
|
+
build_fragment do |doc|
|
188
|
+
doc.div(class: "row example #{status}") do
|
189
|
+
doc.div(class: 'large-12 columns') do
|
190
|
+
doc.div(class: 'row example-heading') do
|
191
|
+
doc.div(class: 'large-9 columns') do
|
192
|
+
doc.p { doc.text example.description.capitalize }
|
193
|
+
end
|
194
|
+
doc.div(class: 'large-3 columns text-right') do
|
195
|
+
doc.p { doc.text sprintf('%.0i', example.execution_result[:run_time]) + 's' }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
doc.div(class: 'row example-body') do
|
199
|
+
doc.div(class: 'large-12 columns') { yield doc }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def dump_failures
|
207
|
+
end
|
208
|
+
|
209
|
+
def dump_pending
|
210
|
+
end
|
211
|
+
|
212
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
213
|
+
output = build_fragment do |doc|
|
214
|
+
doc.div(
|
215
|
+
class: 'results',
|
216
|
+
'data-count' => example_count.to_s,
|
217
|
+
'data-duration' => duration.to_s,
|
218
|
+
'data-failures' => failure_count.to_s,
|
219
|
+
'data-pendings' => pending_count.to_s
|
220
|
+
) { doc << @output_html }
|
221
|
+
end
|
222
|
+
@output.puts output
|
223
|
+
end
|
224
|
+
|
225
|
+
# def extra_failure_content(exception)
|
226
|
+
# super + "<h1>Ya'll know we be failing.</h1>"
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
def build_fragment
|
230
|
+
final = Nokogiri::HTML::DocumentFragment.parse ''
|
231
|
+
Nokogiri::HTML::Builder.with(final) do |doc|
|
232
|
+
yield doc
|
233
|
+
end
|
234
|
+
final.to_html
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
|