nukumber 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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