nukumber 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ module Nukumber
2
+
3
+ module Model
4
+
5
+ class ScenarioOutline < FeatureElement
6
+ attr_accessor :tags, :examples
7
+
8
+ def initialize(name, line, description, feature)
9
+ super(name, line, description, feature)
10
+ @tags = []
11
+ @examples = nil
12
+ end
13
+
14
+ def keyword
15
+ 'Scenario Outline'
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,15 @@
1
+ module Nukumber
2
+
3
+ module Model
4
+
5
+ class Step
6
+ attr_reader :name, :line, :keyword, :element, :args
7
+
8
+ def initialize(name, line, keyword, element, args)
9
+ @name, @line, @keyword, @element, @args = name, line, keyword, element, args
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,53 @@
1
+ module Nukumber
2
+
3
+ module Model
4
+
5
+ class Table
6
+ attr_reader :headers, :rows, :lengths
7
+
8
+ def initialize(rows)
9
+ @headers, @rows, @lengths, @empty = [], [], [], true
10
+ return if rows.nil?
11
+ @empty = false
12
+ rows.each_with_index do |r, i|
13
+ if i == 0
14
+ @headers = r.cells.map { |c| c.to_s }
15
+ @lengths = @headers.map { |h| h.length }
16
+ else
17
+ row = []
18
+ r.cells.each_with_index do |c, j|
19
+ row << c.to_s
20
+ @lengths[j] = [ c.to_s.length, @lengths[j] ].max
21
+ end
22
+ @rows << row
23
+ end
24
+ end
25
+ end
26
+
27
+ def empty?
28
+ @empty
29
+ end
30
+
31
+ def row_count
32
+ @rows.size
33
+ end
34
+
35
+ def row_hash(n)
36
+ out = {}
37
+ @rows[n].each_with_index do |val,i|
38
+ out[@headers[i]] = val
39
+ end
40
+ out
41
+ end
42
+
43
+ def all_row_hashes
44
+ out = []
45
+ row_count.times { |i| out << row_hash(i) }
46
+ out
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,54 @@
1
+ require 'time'
2
+
3
+ module Nukumber
4
+
5
+ module Reporter
6
+
7
+ class Abstract
8
+
9
+ def initialize(out)
10
+ @outstream = out
11
+ end
12
+
13
+ public
14
+
15
+ def begin_feature(feature) end
16
+ def begin_element(element) end
17
+ def undefined_element(element) end
18
+ def print_step(step, status) end
19
+ def print_example(table, row, status) end
20
+ def error(exception, element) end
21
+ def final_report(passed, failed, pending, undefined) end
22
+ def print_skeleton_code(elements) end
23
+ def terminate() end
24
+
25
+ protected
26
+ def filtered_backtrace(btrace)
27
+
28
+ filters = [
29
+ /\/lib\d*\/ruby\//,
30
+ /bin\//,
31
+ /gems\//,
32
+ /spec\/spec_helper\.rb/,
33
+ /lib\/rspec\/(core|expectations|matchers|mocks)/
34
+ ]
35
+
36
+ filtered = []
37
+ btrace.each do |l|
38
+ filter = false
39
+ filters.each do |b|
40
+ if l.match b
41
+ filter = true
42
+ break
43
+ end
44
+ end
45
+ filtered << l unless filter
46
+ end
47
+ filtered
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,138 @@
1
+ module Nukumber
2
+
3
+ module Reporter
4
+
5
+ class Colour < Abstract
6
+
7
+ protected
8
+
9
+ INDENT = ' '
10
+
11
+ def puts_empty_line
12
+ @outstream.puts "\n"
13
+ end
14
+
15
+ def puts_colour_indent(text, colour_code)
16
+ out = "\e[#{colour_code}m#{text}\e[0m"
17
+ @indent.times { out = INDENT + out }
18
+ @outstream.puts out
19
+ end
20
+
21
+ def puts_red(text) puts_colour_indent(text, 31) end
22
+ def puts_green(text) puts_colour_indent(text, 32) end
23
+ def puts_yellow(text) puts_colour_indent(text, 33) end
24
+ def puts_cyan(text) puts_colour_indent(text, 36) end
25
+ def puts_grey(text) puts_colour_indent(text, 37) end
26
+
27
+ def puts_status(text, status)
28
+ puts_green text if status == :passed
29
+ puts_red text if status == :failed
30
+ puts_cyan text if status == :pending
31
+ puts_cyan text if status == :outline
32
+ puts_yellow text if status == :undefined
33
+ end
34
+
35
+ def table_row_printable(table, row)
36
+ if row.nil? or row < 0
37
+ array = table.headers
38
+ else
39
+ array = table.rows[row]
40
+ end
41
+ str = "|"
42
+ array.each_with_index do |val, i|
43
+ str += " %-#{table.lengths[i]}s |" % [val]
44
+ end
45
+ str
46
+ end
47
+
48
+ public
49
+
50
+ def begin_feature(feature)
51
+ @indent = 0
52
+ puts_cyan "#{feature.keyword}: #{feature.name}"
53
+ end
54
+
55
+ def begin_element(element)
56
+ @indent = 1
57
+ puts_cyan "#{element.keyword}: #{element.name}"
58
+ puts_grey "#{element.feature.file_path.split('/').last}:#{element.line}"
59
+ if element.is_a? Nukumber::Model::ScenarioOutline
60
+ element.steps.each { |step| print_step(step, :outline) }
61
+ @indent = 3
62
+ puts_cyan table_row_printable(element.examples.table, nil)
63
+ end
64
+ end
65
+
66
+ def undefined_element(element)
67
+ @indent = 1
68
+ puts_yellow "#{element.keyword}: #{element.name}"
69
+ puts_grey "#{element.feature.file_path.split('/').last}:#{element.line}"
70
+ element.steps.each { |step| print_step(step, :undefined) }
71
+ if element.is_a? Nukumber::Model::ScenarioOutline
72
+ @indent = 3
73
+ (0..element.examples.table.row_count).each do |i|
74
+ puts_status(table_row_printable(element.examples.table, i - 1), :undefined)
75
+ end
76
+ end
77
+ end
78
+
79
+ def print_step(step, status = :passed)
80
+ @indent = 2
81
+ puts_status("#{step.keyword}#{step.name}", status)
82
+ @indent = 3
83
+ unless step.args.empty?
84
+ (0..step.args.row_count).each do |i|
85
+ puts_status(table_row_printable(step.args, i - 1), status)
86
+ end
87
+ end
88
+ end
89
+
90
+ def print_example(table, row, status = :passed)
91
+ puts_status(table_row_printable(table, row), status)
92
+ end
93
+
94
+ def error(exception, element)
95
+ @indent = 2
96
+ puts_red exception.message
97
+ @indent = 3
98
+ filtered_backtrace(exception.backtrace).each { |l| puts_red l }
99
+ puts_red "#{element.feature.file_path}:#{element.line}"
100
+ end
101
+
102
+ def final_report(passed, failed, pending, undefined)
103
+ @indent = 0
104
+ puts_green "\n#{passed.size} test#{passed.size == 1 ? '' : 's'} passed"
105
+ puts_cyan "#{pending.size} test#{pending.size == 1 ? '' : 's'} pending"
106
+ puts_yellow "#{undefined.size} test#{undefined.size == 1 ? '' : 's'} undefined"
107
+ puts_red "#{failed.size} test#{failed.size == 1 ? '' : 's'} failed#{failed.size == 0 ? '' : ':'}"
108
+ @indent = 1
109
+ failed.each { |f| puts_red "#{f.feature.file_path}:#{f.line} # #{f.name}" }
110
+ end
111
+
112
+ def print_skeleton_code(elements)
113
+ @indent = 0
114
+ puts_empty_line
115
+ puts_yellow "Build Nukumber test definitions something like this:"
116
+ puts_empty_line
117
+ elements.each do |element|
118
+ @indent = 0
119
+ puts_yellow "def #{element.shortsym}"
120
+ @indent = 1
121
+ puts_yellow "# $args is a Hash (or Array of Hashes) representing any step arguments"
122
+ puts_yellow "# $example is a Hash representing the current row for this outline" if element.is_a? Nukumber::Model::ScenarioOutline
123
+ puts_yellow "# Now precede each \"pass\" line below with the code it describes..."
124
+ puts_yellow "pending"
125
+ element.steps.each do |step|
126
+ puts_yellow "pass \"#{step.name.gsub('"', '\"')}\""
127
+ end
128
+ @indent = 0
129
+ puts_yellow "end"
130
+ puts_empty_line
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,143 @@
1
+ module Nukumber
2
+
3
+ module Reporter
4
+
5
+ class Html < Abstract
6
+
7
+ private
8
+
9
+ def add_row(nuke_table, row, table_node, status_class = nil)
10
+ if row.nil? or row < 0
11
+ array, tag = nuke_table.headers, 'th'
12
+ else
13
+ array, tag = nuke_table.rows[row], 'td'
14
+ end
15
+ row = node("tr", table_node)
16
+ row['class'] = status_class if status_class
17
+ array.each { |val| node(tag, row, {}, val) }
18
+ end
19
+
20
+ def node(type, parent = nil, attributes = {}, content = nil)
21
+ div = Nokogiri::XML::Node.new(type, @doc)
22
+ attributes.each_pair{ |k, v| div[k.to_s] = v }
23
+ div.content = content unless content.nil?
24
+ parent.add_child(div) unless parent.nil?
25
+ div
26
+ end
27
+
28
+
29
+ public
30
+
31
+ def initialize(out)
32
+ @outstream = out
33
+ @doc = Nokogiri::HTML::Document.new
34
+ @html = node("html", @doc)
35
+ node_head = node("head", @html)
36
+ node_style = node("style", node_head, {:type => 'text/css'})
37
+ node_style.inner_html = css
38
+ node_body = node("body", @html)
39
+ node("h1", node_body, {}, 'Nukumber test report')
40
+ end
41
+
42
+ def begin_feature(feature)
43
+ @node_feature = node('div', @html.at_css('body'), {:class => 'feature'})
44
+ node_header = node('div', @node_feature, {:class => 'header'})
45
+ node("h2", node_header, {}, "#{feature.keyword}: #{feature.name}")
46
+ unless feature.tags.empty?
47
+ node("div", node_header, {:class => 'tags'}, feature.tags.join(' '))
48
+ end
49
+ unless feature.description.empty?
50
+ node("p", node_header, {}, feature.description.gsub(/\n/,' '))
51
+ end
52
+ end
53
+
54
+ def begin_element(element)
55
+ @node_element = node("div", @node_feature, {:class => 'element'})
56
+ node_header = node('div', @node_element, {:class => 'header'})
57
+ node("h3", node_header, {}, "#{element.keyword}: #{element.name}")
58
+ node("div", node_header, {:class => 'address'}, "#{element.feature.file_path.split('/').last}:#{element.line}")
59
+ unless element.is_a? Nukumber::Model::Background or element.tags.empty?
60
+ node("div", node_header, {:class => 'tags'}, element.tags.join(' '))
61
+ end
62
+ unless element.description.empty?
63
+ node("p", node_header, {}, element.description.gsub(/\n/,' '))
64
+ end
65
+ if element.is_a? Nukumber::Model::ScenarioOutline
66
+ element.steps.each { |step| print_step(step, :outline) }
67
+ @node_ex_table = node("table", @node_element)
68
+ add_row(element.examples.table, nil, @node_ex_table)
69
+ end
70
+ end
71
+
72
+ def undefined_element(element)
73
+ @node_element = node("div", @node_feature, {:class => 'element undefined'})
74
+ node_header = node('div', @node_element, {:class => 'header'})
75
+ node("h3", node_header, {}, "#{element.keyword}: #{element.name}")
76
+ node("div", node_header, {:class => 'address'}, "#{element.feature.file_path.split('/').last}:#{element.line}")
77
+ unless element.is_a? Nukumber::Model::Background or element.tags.empty?
78
+ node("div", node_header, {:class => 'tags'}, element.tags.join(' '))
79
+ end
80
+ unless element.description.empty?
81
+ node("p", node_header, {}, element.description.gsub(/\n/,' '))
82
+ end
83
+ element.steps.each { |step| print_step(step, :undefined) }
84
+ if element.is_a? Nukumber::Model::ScenarioOutline
85
+ @node_ex_table = node("table", @node_element)
86
+ (0..element.examples.table.row_count).each do |i|
87
+ add_row(element.examples.table, i - 1, @node_ex_table, 'undefined')
88
+ end
89
+ end
90
+ end
91
+
92
+ def print_step(step, status)
93
+ node_step = node("div", @node_element, {:class => "step #{status.to_s}"})
94
+ node("p", node_step, {}, "#{step.keyword}#{step.name}")
95
+ unless step.args.empty?
96
+ node_table = Nokogiri::XML::Node.new "table", @doc
97
+ (0..step.args.row_count).each do |i|
98
+ add_row(step.args, i - 1, node_table)
99
+ end
100
+ node_step.add_child node_table
101
+ end
102
+ @node_element['class'] = "element #{status.to_s}"
103
+ end
104
+
105
+ def print_example(table, row, status)
106
+ add_row(table, row, @node_ex_table, status.to_s)
107
+ unless @node_element['class'] == "element failed"
108
+ @node_element['class'] = "element #{status.to_s}"
109
+ end
110
+ end
111
+
112
+ def error(exception, element)
113
+ node_error = node("div", @node_element, {:class => "error"})
114
+ node("p", node_error, {}, exception.message)
115
+ node_backtrace = node("div", node_error)
116
+ filtered_backtrace(exception.backtrace).each do |l|
117
+ node("p", node_backtrace, {}, l)
118
+ end
119
+ node("p", node_backtrace, {}, "#{element.feature.file_path}:#{element.line}")
120
+ @node_element['class'] = 'element failed'
121
+ end
122
+
123
+ def final_report(passed, failed, pending, undefined)
124
+ rpt = {:passed => passed, :failed => failed, :pending => pending, :undefined => undefined}
125
+ node_final_report = node("div", @html.at_css('body'), {:id => 'final_report'})
126
+ %w( passed failed pending undefined ).each do |str|
127
+ node("div", node_final_report, {:class => str}, "#{rpt[str.to_sym].size} test#{rpt[str.to_sym].size == 1 ? '' : 's'} #{str}")
128
+ end
129
+ node("div", node_final_report, {:class => "datetime"}, "#{Time.now}")
130
+ end
131
+
132
+ def print_skeleton_code(*)
133
+ end
134
+
135
+ def terminate()
136
+ @outstream.puts @doc.to_html
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,169 @@
1
+ module Nukumber
2
+
3
+ module Reporter
4
+
5
+ class Html < Abstract
6
+
7
+ private
8
+
9
+ def css()
10
+ <<HERE
11
+ <!--
12
+
13
+ /* http://meyerweb.com/eric/tools/css/reset/
14
+ end
15
+ v2.0 | 20110126
16
+ License: none (public domain)
17
+ */
18
+
19
+ html, body, div, span, applet, object, iframe,
20
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
21
+ a, abbr, acronym, address, big, cite, code,
22
+ del, dfn, em, img, ins, kbd, q, s, samp,
23
+ small, strike, strong, sub, sup, tt, var,
24
+ b, u, i, center, dl, dt, dd, ol, ul, li,
25
+ fieldset, form, label, legend, table, caption,
26
+ tbody, tfoot, thead, tr, th, td, article, aside,
27
+ canvas, details, embed, figure, figcaption,
28
+ footer, header, hgroup, menu, nav, output, ruby,
29
+ section, summary, time, mark, audio, video {
30
+ margin: 0;
31
+ padding: 0;
32
+ border: 0;
33
+ font-size: 100%;
34
+ font: inherit;
35
+ vertical-align: baseline;
36
+ }
37
+ /* HTML5 display-role reset for older browsers */
38
+ article, aside, details, figcaption, figure,
39
+ footer, header, hgroup, menu, nav, section {
40
+ display: block;
41
+ }
42
+ body {
43
+ line-height: 1;
44
+ }
45
+ ol, ul {
46
+ list-style: none;
47
+ }
48
+ blockquote, q {
49
+ quotes: none;
50
+ }
51
+ blockquote:before, blockquote:after,
52
+ q:before, q:after {
53
+ content: '';
54
+ content: none;
55
+ }
56
+ table {
57
+ border-collapse: collapse;
58
+ border-spacing: 0;
59
+ }
60
+
61
+ /* ========================== END MEYER RESET ========================== */
62
+
63
+
64
+
65
+ html {
66
+ font-family: monospace;
67
+ }
68
+
69
+ body {
70
+ padding: 10px;
71
+ line-height: 1.4;
72
+ }
73
+
74
+ h1 { font-size: 150%; font-weight: bold; }
75
+ h2 { font-size: 130%; font-weight: bold; }
76
+ h3 { font-size: 110%; font-weight: bold; }
77
+
78
+ table, th, td { border: 1px solid #000; padding: 3px; }
79
+ th { background-color: #FFF }
80
+
81
+ .feature {
82
+ padding: 10px;
83
+ margin-bottom: 10px;
84
+ }
85
+
86
+ .element {
87
+ width: 100%;
88
+ overflow: auto;
89
+ border: 1px dashed #000;
90
+ padding: 10px;
91
+ margin-bottom: 10px;
92
+ }
93
+
94
+ .header { margin-bottom: 1ex; min-height: 2.5ex; }
95
+ .header h3 { float:left; }
96
+ .header .address { float:right; font-size: 80%; }
97
+
98
+ .header .tags { font-weight: bold; }
99
+
100
+ .header p,
101
+ .header .tags,
102
+ .step {
103
+ clear: both;
104
+ }
105
+
106
+ .step { margin-bottom: 4px; }
107
+
108
+ .passed {
109
+ background-color: #8E6;
110
+ }
111
+
112
+ .element.passed {
113
+ background-color: #BFB;
114
+ }
115
+
116
+ .failed {
117
+ background-color: #D00;
118
+ }
119
+
120
+ .element.failed {
121
+ background-color: #FCC;
122
+ }
123
+
124
+ .pending, .outline {
125
+ background-color: #2FE;
126
+ }
127
+
128
+ .element.pending, .element.outline {
129
+ background-color: #CFF;
130
+ }
131
+
132
+ .undefined {
133
+ background-color: #FF2;
134
+ }
135
+
136
+ .error {
137
+ padding: 10px;
138
+ }
139
+
140
+ .error div {
141
+ background-color: #000;
142
+ color: #F00;
143
+ padding: 10px;
144
+ }
145
+
146
+ #final_report {
147
+ position: fixed;
148
+ top: 0;
149
+ right: 0;
150
+ border: 1px solid #000;
151
+ }
152
+
153
+ #final_report div {
154
+ padding: 6px;
155
+ }
156
+
157
+ #final_report .datetime {
158
+ background-color: #FFF;
159
+ }
160
+
161
+ -->
162
+ HERE
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+
169
+ end