cukeregator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 ThoughtWorks, Inc.
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.textile ADDED
@@ -0,0 +1,50 @@
1
+ h2. Cukeregator
2
+
3
+ Provides a summary view of multiple "cucumber":https://github.com/aslakhellesoy/cucumber html output files.
4
+
5
+ h3. Examples
6
+
7
+ * With the gem installed, from within your project directory:
8
+
9
+ <pre>
10
+ $ cukeregator tmp/cucumber*.html > parallel_run_summary.html
11
+ </pre>
12
+
13
+ This command will aggregate the results of however many output files are matched by the shell glob, and write those results to a document with the same kind of summary as the normal cuke output. It also lists the summary for each individual results file, and links back to it whenever you want the detail.
14
+
15
+ * A demo from within your local clone of this git repo:
16
+
17
+ <pre>
18
+ $ ./bin/cukeregator fixtures/*.html > cukeregated.html
19
+ </pre>
20
+
21
+ Summarizes my test files, which are filtered samples from the 0.9.4 Cucumber project's feature suite.
22
+
23
+ h3. Tests
24
+
25
+ <pre>
26
+ $ rake
27
+ </pre>
28
+
29
+ h3. Installation
30
+
31
+ <pre>
32
+ $ gem install cukeregator
33
+ </pre>
34
+
35
+ h3. Dependencies
36
+
37
+ @Nokogiri@ is used to parse the cucumber files, as well as to build the summary file.
38
+
39
+
40
+ h3. TODO's
41
+
42
+ * styling
43
+ * handle 'undefined' status explicitly
44
+
45
+ h3. We'll See
46
+
47
+ * dump Nokogiri for Haml? (Would have to hand parse in the reader without Nokogiri.)
48
+ * enable STDIN as source of file list input in the bin?
49
+ * Rake task as cmd line convenince?
50
+ * path list option for cases when a glob won't work?
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ task :default do
8
+ Rake.application.tasks_in_scope(["cukeregator:test"]).each do |t|
9
+ t.invoke
10
+ end
11
+ end
12
+
13
+ namespace :cukeregator do
14
+
15
+ task :readme do
16
+ require 'redcloth'
17
+ puts RedCloth.new(File.read("README.textile")).to_html
18
+ end
19
+
20
+ namespace :test do
21
+ desc "unit specs"
22
+ RSpec::Core::RakeTask.new(:unit) do |t|
23
+ t.pattern = "spec/cukeregator/*_spec.rb"
24
+ t.rspec_opts = ["--color" , "--format" , "doc" ]
25
+ end
26
+ end
27
+
28
+ RDOC_OPTS = ["--all" , "--quiet" , "--line-numbers" , "--inline-source",
29
+ "--main", "README.textile",
30
+ "--title", "Cukeregator: many into one"]
31
+ XTRA_RDOC = %w{README.textile LICENSE }
32
+
33
+ Rake::RDocTask.new do |rd|
34
+ rd.rdoc_dir = "doc/rdoc"
35
+ rd.rdoc_files.include("**/*.rb")
36
+ rd.rdoc_files.add(XTRA_RDOC)
37
+ rd.options = RDOC_OPTS
38
+ end
39
+
40
+ spec = Gem::Specification.new do |s|
41
+ s.name = 'cukeregator'
42
+ s.version = '0.1.0'
43
+ s.rubyforge_project = s.name
44
+
45
+ s.platform = Gem::Platform::RUBY
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = XTRA_RDOC
48
+ s.rdoc_options += RDOC_OPTS
49
+ s.summary = "aggregates many cucumber results files into one summary page"
50
+ s.description = s.summary
51
+ s.author = "Tim Camper"
52
+ s.email = 'twcamper@thoughtworks.com'
53
+ s.homepage = 'http://github.com/twcamper/cukeregator'
54
+ s.required_ruby_version = '>= 1.8.7'
55
+ s.add_dependency('nokogiri', '>= 1.4.0')
56
+ s.default_executable = "cukeregator"
57
+ s.executables = [s.default_executable]
58
+
59
+ s.files = %w(LICENSE README.textile Rakefile) +
60
+ FileList["lib/**/*.{rb,css}", "bin/*"].to_a
61
+
62
+ s.require_path = "lib"
63
+ end
64
+
65
+ Rake::GemPackageTask.new(spec) do |p|
66
+ p.need_zip = true
67
+ p.need_tar = true
68
+ p.gem_spec = spec
69
+ end
70
+ end
data/bin/cukeregator ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../") + '/lib/cukeregator'
4
+
5
+ puts Cukeregator::HtmlGenerator.new(Cukeregator::Aggregator.new(ARGV)).doc
@@ -0,0 +1,101 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cukeregator
3
+ module SummaryFormatter
4
+ def count(which)
5
+ total = yield("total_#{which}s".to_sym)
6
+ "#{total} #{which}#{'s' if total> 1}"
7
+ end
8
+ def totals_inner_html(&block)
9
+ totals = count(:scenario, &block)
10
+ totals += status_counts(:scenario, &block)
11
+ totals += "<br />"
12
+ totals += count(:step, &block)
13
+ totals += status_counts(:step, &block)
14
+ end
15
+ def status_counts(which)
16
+ totals = yield("#{which}_totals".to_sym)
17
+ counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
18
+ "#{totals[status]} #{status}" if totals[status] > 0
19
+ end.compact
20
+ " (#{counts.join(', ')})"
21
+ end
22
+ def duration_inner_html(s)
23
+ "Combined Duration <strong>#{s}</strong>"
24
+ end
25
+ extend(SummaryFormatter)
26
+ end
27
+ class Aggregator
28
+ include Status
29
+
30
+ attr_reader :docs
31
+
32
+ def initialize(files)
33
+ @docs = files.map do |f|
34
+ HtmlReader.new(File.read(f), f)
35
+ end
36
+ end
37
+
38
+ def scenario_totals
39
+ @scenario_totals ||= sum_hash(:scenario_totals)
40
+ end
41
+
42
+ def total_scenarios
43
+ @total_scenarios ||= sum(:total_scenarios)
44
+ end
45
+
46
+ def totals_inner_html
47
+ SummaryFormatter.totals_inner_html {|method| self.send(method) }
48
+ end
49
+
50
+ def duration_inner_html
51
+ SummaryFormatter.duration_inner_html(duration_string)
52
+ end
53
+ def total_steps
54
+ @total_steps ||= sum(:total_steps)
55
+ end
56
+
57
+ def step_totals
58
+ @step_totals ||= sum_hash(:step_totals)
59
+ end
60
+
61
+ def duration
62
+ @duration ||= @docs.inject(0) do |result, doc|
63
+ result += doc.duration
64
+ result
65
+ end
66
+ end
67
+
68
+ def duration_string
69
+ hours = (duration / 3600).to_i
70
+ min_sec = duration % 3600
71
+ minutes = (min_sec / 60).to_i
72
+ seconds = min_sec % 60
73
+
74
+ s = ""
75
+ s += "#{hours}h" if hours > 0
76
+ s += "#{minutes}m"
77
+ s += "#{seconds.to_s[0..5]}s"
78
+ s
79
+ end
80
+
81
+ private
82
+
83
+ def sum(which)
84
+ docs.inject(0) do |result, doc|
85
+ result += doc.send(which)
86
+ result
87
+ end
88
+ end
89
+
90
+ def sum_hash(which)
91
+ h = Hash.new(0)
92
+ docs.each do |d|
93
+ d.send(which).each do |key, value|
94
+ h[key] += value
95
+ end
96
+ end
97
+ h
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cukeregator
3
+ class HtmlGenerator
4
+ def initialize(aggregator)
5
+ @aggregator = aggregator
6
+ @doc = new_doc
7
+ end
8
+
9
+ def doc
10
+ @doc.root << head
11
+ @doc.root << body
12
+ @doc.to_html
13
+ end
14
+
15
+ def new_doc
16
+ doc =Nokogiri::XML::Document.new
17
+ root = Nokogiri::XML::Node.new('html', doc)
18
+ root.create_internal_subset( 'html', "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")
19
+ doc << root
20
+ doc
21
+ end
22
+
23
+ def head
24
+ h = new_node(:head)
25
+ title = new_node(:title)
26
+ title.content = "All Cucumber Results"
27
+ h << title
28
+ h << inline_css
29
+ h
30
+ end
31
+
32
+ def body
33
+ b = new_node(:body)
34
+ b << summary
35
+ b << cucumbers
36
+ b
37
+ end
38
+
39
+ def summary
40
+ s = new_node(:div, @aggregator.status)
41
+ s['id'] = "summary"
42
+ s << summary_p(@aggregator, 'totals')
43
+ s << summary_p(@aggregator, 'duration')
44
+ s
45
+ end
46
+
47
+ def cucumbers
48
+ c = new_node(:table)
49
+ c['id'] = "cucumbers"
50
+ tb = new_node(:tbody)
51
+ @aggregator.docs.each do |doc_data|
52
+ tb << row(doc_data)
53
+ end
54
+ c << tb
55
+ c
56
+ end
57
+
58
+ def row(doc_data)
59
+ tr = new_node(:tr, doc_data.status)
60
+ td = new_node(:td, 'result-detail')
61
+ td << link_for(doc_data.path)
62
+ tr << td
63
+
64
+ td = new_node(:td, 'result-summary')
65
+ td << summary_p(doc_data, 'totals')
66
+ td << summary_p(doc_data, 'duration')
67
+ tr << td
68
+ tr
69
+ end
70
+
71
+ def link_for(path)
72
+ a = new_node(:a)
73
+ a['href'] = path
74
+ a.content = path
75
+ a
76
+ end
77
+
78
+ def new_node(name, class_name = nil)
79
+ n = Nokogiri::XML::Node.new(name.to_s, @doc)
80
+ n['class'] = class_name.to_s if class_name
81
+ n
82
+ end
83
+
84
+ def summary_p(o, which)
85
+ p = new_node(:p, which)
86
+ p.inner_html = o.send("#{which}_inner_html")
87
+ p
88
+ end
89
+
90
+ def inline_css
91
+ style = new_node(:style)
92
+ style['type'] = 'text/css'
93
+ style.content = "\n#{File.read(File.expand_path(File.dirname(__FILE__)) + '/style.css')}"
94
+ style
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,73 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cukeregator
3
+
4
+ class HtmlReader
5
+ include Status
6
+
7
+ attr_reader :totals_inner_html, :total_scenarios, :scenario_totals, :total_steps, :step_totals
8
+ attr_reader :duration_inner_html, :duration, :path
9
+
10
+ def initialize(html, path)
11
+ @scripts = parse_scripts(Nokogiri::HTML(html))
12
+ @path = path
13
+ @duration = parse_duration
14
+ @total_scenarios, @scenario_totals = parse_totals(:scenario)
15
+ @total_steps, @step_totals = parse_totals(:step)
16
+ end
17
+
18
+ def totals_inner_html
19
+ @totals_inner_html ||= js_string(:totals)
20
+ end
21
+
22
+ def duration_inner_html
23
+ @duration_inner_html ||= js_string(:duration)
24
+ end
25
+
26
+ private
27
+ def parse_totals(which)
28
+ totals_inner_html =~ /(\d+) #{which}s? \(([^\)]+)/
29
+ total = $1
30
+ raise("no total string found for '#{which}'") unless total
31
+ breakdown = $2
32
+ raise("no breakdown string found for '#{which}'") unless breakdown
33
+ return total.to_i, to_hash(breakdown)
34
+ end
35
+
36
+ def parse_duration
37
+ time_string = duration_inner_html[/(\d+h)?\d+m\d+\.\d\d\ds/]
38
+ hours_minutes_seconds = time_string.split(/[hms]/)
39
+
40
+ duration = 0.0
41
+ if hours_minutes_seconds.size == 3
42
+ duration += hours_minutes_seconds.shift.to_f * 3600.0
43
+ end
44
+ duration += hours_minutes_seconds[0].to_f * 60.0
45
+ duration += hours_minutes_seconds[1].to_f
46
+ duration
47
+ end
48
+
49
+ def js_string(which)
50
+ js = @scripts.grep(/#{which}/).to_s
51
+ js =~ /innerHTML[=\s]+"([^\"]+)/
52
+ inner_html = $1
53
+ raise "no innerHTML found for '#{which}' in scripts" unless inner_html
54
+ inner_html
55
+ end
56
+
57
+ def to_hash(s)
58
+ result = Hash.new(0)
59
+ s.split(',').each do | pair|
60
+ count, name = pair.strip.split(' ')
61
+ result[name.to_sym] = count.to_i
62
+ end
63
+ result
64
+ end
65
+
66
+ # our data lies in javascript functions at the bottom of the body, because
67
+ # Aslak can't put up the totals until the suite completes
68
+ def parse_scripts(doc)
69
+ doc.search("body script").map {|script| script.text }
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,11 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cukeregator
3
+ module Status
4
+ def status
5
+ return :failed if scenario_totals[:failed] > 0
6
+ return :passed if scenario_totals[:passed] > 0
7
+ return :pending if scenario_totals[:pending] > 0
8
+ return :undefined
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ .passed { background-color: #65C400 }
2
+ .failed { background-color: #C40D0D }
3
+ .pending { background-color: #FAF834 }
@@ -0,0 +1,24 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ require 'rubygems'
3
+ require 'nokogiri'
4
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
5
+ require 'cukeregator/status'
6
+ require 'cukeregator/html_reader'
7
+ require 'cukeregator/aggregator'
8
+ require 'cukeregator/html_generator'
9
+
10
+ module Cukeregator
11
+
12
+ def print(files)
13
+ files.each do |f|
14
+ puts File.read f
15
+ end
16
+ end
17
+
18
+ def docs(files)
19
+ Aggregator.new(files).docs
20
+ end
21
+
22
+
23
+ extend(Cukeregator)
24
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cukeregator
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Tim Camper
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-27 00:00:00 -04:00
19
+ default_executable: cukeregator
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 0
34
+ version: 1.4.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: aggregates many cucumber results files into one summary page
38
+ email: twcamper@thoughtworks.com
39
+ executables:
40
+ - cukeregator
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - README.textile
45
+ - LICENSE
46
+ files:
47
+ - LICENSE
48
+ - README.textile
49
+ - Rakefile
50
+ - lib/cukeregator/aggregator.rb
51
+ - lib/cukeregator/html_generator.rb
52
+ - lib/cukeregator/html_reader.rb
53
+ - lib/cukeregator/status.rb
54
+ - lib/cukeregator.rb
55
+ - lib/cukeregator/style.css
56
+ - bin/cukeregator
57
+ has_rdoc: true
58
+ homepage: http://github.com/twcamper/cukeregator
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --all
64
+ - --quiet
65
+ - --line-numbers
66
+ - --inline-source
67
+ - --main
68
+ - README.textile
69
+ - --title
70
+ - "Cukeregator: many into one"
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 57
79
+ segments:
80
+ - 1
81
+ - 8
82
+ - 7
83
+ version: 1.8.7
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project: cukeregator
96
+ rubygems_version: 1.4.2
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: aggregates many cucumber results files into one summary page
100
+ test_files: []
101
+