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.
Files changed (59) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +15 -5
  3. data/Gemfile +4 -0
  4. data/README.md +13 -1
  5. data/Rakefile +3 -3
  6. data/chemistrykit.gemspec +7 -3
  7. data/features/brew.feature +1 -1
  8. data/features/chemists.feature +56 -1
  9. data/features/concurrency.feature +2 -2
  10. data/features/exit_status.feature +1 -0
  11. data/features/logging.feature +88 -85
  12. data/features/reporting.feature +105 -0
  13. data/features/step_definitions/steps.rb +4 -4
  14. data/features/support/env.rb +1 -1
  15. data/features/tags.feature +1 -0
  16. data/lib/chemistrykit/chemist.rb +6 -1
  17. data/lib/chemistrykit/chemist/repository/csv_chemist_repository.rb +1 -1
  18. data/lib/chemistrykit/cli/cli.rb +33 -12
  19. data/lib/chemistrykit/configuration.rb +2 -2
  20. data/lib/chemistrykit/formula/formula_lab.rb +13 -0
  21. data/lib/chemistrykit/{parallel_tests_mods.rb → parallel_tests/rspec/runner.rb} +5 -5
  22. data/lib/chemistrykit/reporting/html_report_assembler.rb +170 -0
  23. data/lib/chemistrykit/rspec/html_formatter.rb +241 -0
  24. data/lib/chemistrykit/rspec/j_unit_formatter.rb +124 -0
  25. data/report/config.rb +28 -0
  26. data/report/index.html +213 -0
  27. data/report/javascripts/foundation/foundation.abide.js +194 -0
  28. data/report/javascripts/foundation/foundation.alerts.js +52 -0
  29. data/report/javascripts/foundation/foundation.clearing.js +516 -0
  30. data/report/javascripts/foundation/foundation.cookie.js +74 -0
  31. data/report/javascripts/foundation/foundation.dropdown.js +177 -0
  32. data/report/javascripts/foundation/foundation.forms.js +533 -0
  33. data/report/javascripts/foundation/foundation.interchange.js +280 -0
  34. data/report/javascripts/foundation/foundation.joyride.js +850 -0
  35. data/report/javascripts/foundation/foundation.js +440 -0
  36. data/report/javascripts/foundation/foundation.magellan.js +135 -0
  37. data/report/javascripts/foundation/foundation.orbit.js +412 -0
  38. data/report/javascripts/foundation/foundation.placeholder.js +179 -0
  39. data/report/javascripts/foundation/foundation.reveal.js +330 -0
  40. data/report/javascripts/foundation/foundation.section.js +400 -0
  41. data/report/javascripts/foundation/foundation.tooltips.js +208 -0
  42. data/report/javascripts/foundation/foundation.topbar.js +300 -0
  43. data/report/javascripts/vendor/custom.modernizr.js +4 -0
  44. data/report/javascripts/vendor/jquery.js +9789 -0
  45. data/report/sass/_normalize.scss +402 -0
  46. data/report/sass/_settings.scss +1301 -0
  47. data/report/sass/app.scss +571 -0
  48. data/report/stylesheets/app.css +3636 -0
  49. data/spec/integration/lib/chemistrykit/formula/formula_lab_spec.rb +26 -2
  50. data/spec/integration/lib/chemistrykit/reporting/html_reporting_assembler_spec.rb +18 -0
  51. data/spec/support/evidence/results_0.html +30 -0
  52. data/spec/support/evidence/results_1.html +27 -0
  53. data/spec/unit/lib/chemistrykit/chemist/repository/csv_chemist_repository_spec.rb +15 -2
  54. data/spec/unit/lib/chemistrykit/chemist_spec.rb +17 -1
  55. data/spec/unit/lib/chemistrykit/configuration_spec.rb +2 -2
  56. data/spec/unit/lib/chemistrykit/formula/formula_lab_spec.rb +7 -0
  57. data/spec/unit/lib/chemistrykit/reporting/html_reporting_assembler_spec.rb +22 -0
  58. metadata +94 -13
  59. 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 =~ /parallel_part_\w{8}-(\w{4}-){3}\w{12}\.xml/
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 =~ /.+_failshot_.+\.png/
29
+ count += 1 if file =~ /failshot\.png/
30
30
  when 'report'
31
- count += 1 if file =~ /.+_saucejob_.+\.log/
31
+ count += 1 if file =~ /sauce_job\.log/
32
32
  when 'sauce log'
33
- count += 1 if file =~ /.+_serverlog_.+\.log/
33
+ count += 1 if file =~ /server\.log/
34
34
  end
35
35
  end
36
36
  count.should == number.to_i
@@ -4,6 +4,6 @@ require 'aruba/cucumber'
4
4
 
5
5
  Before do
6
6
  FileUtils.rm_rf('build/tmp')
7
- @aruba_timeout_seconds = 90
7
+ @aruba_timeout_seconds = 150
8
8
  @dirs = ['build/tmp']
9
9
  end
@@ -7,6 +7,7 @@ Feature: Listing all the tags
7
7
  Given I run `ckit new tags-test`
8
8
  And I cd to "tags-test"
9
9
 
10
+ @announce
10
11
  Scenario: Get the tag for a single beaker
11
12
  Given a file named "beakers/bookie_beaker.rb" with:
12
13
  """
@@ -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
@@ -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/parallel_tests_mods'
18
- require 'chemistrykit/j_unit'
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
- c.add_formatter(config.log.format, File.join(Dir.getwd, config.log.path, config.log.results_file))
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 = 'JUnit'
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 = 'JUnit' if key == :format && value =~ /junit/i
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
- cmd = [exe, options[:test_options]].compact.join(' ')
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
- uuid = SecureRandom.uuid
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
+