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.
- data/bin/nukumber +14 -0
- data/lib/nukumber.rb +29 -0
- data/lib/nukumber/builder.rb +104 -0
- data/lib/nukumber/cli.rb +101 -0
- data/lib/nukumber/errors.rb +18 -0
- data/lib/nukumber/helpers.rb +5 -0
- data/lib/nukumber/hooks.rb +23 -0
- data/lib/nukumber/model.rb +9 -0
- data/lib/nukumber/model/background.rb +17 -0
- data/lib/nukumber/model/examples.rb +16 -0
- data/lib/nukumber/model/feature.rb +33 -0
- data/lib/nukumber/model/feature_element.rb +47 -0
- data/lib/nukumber/model/scenario.rb +20 -0
- data/lib/nukumber/model/scenario_outline.rb +21 -0
- data/lib/nukumber/model/step.rb +15 -0
- data/lib/nukumber/model/table.rb +53 -0
- data/lib/nukumber/reporter.rb +54 -0
- data/lib/nukumber/reporter/colour.rb +138 -0
- data/lib/nukumber/reporter/html.rb +143 -0
- data/lib/nukumber/reporter/html_css.rb +169 -0
- data/lib/nukumber/reporter/mono.rb +19 -0
- data/lib/nukumber/runtime.rb +237 -0
- data/lib/nukumber/version.rb +7 -0
- metadata +117 -0
data/bin/nukumber
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'nukumber'
|
7
|
+
|
8
|
+
begin
|
9
|
+
Nukumber::Cli.new(ARGV.dup)
|
10
|
+
rescue => e
|
11
|
+
STDERR.puts("#{e.message} (#{e.class})")
|
12
|
+
STDERR.puts(e.backtrace.join("\n"))
|
13
|
+
Kernel.exit(1)
|
14
|
+
end
|
data/lib/nukumber.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'gherkin/rubify'
|
2
|
+
require 'gherkin/parser/parser'
|
3
|
+
require 'gherkin/formatter/filter_formatter'
|
4
|
+
require 'gherkin/formatter/tag_count_formatter'
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
require 'nukumber/errors'
|
8
|
+
|
9
|
+
require 'nukumber/model'
|
10
|
+
require 'nukumber/model/feature'
|
11
|
+
require 'nukumber/model/feature_element'
|
12
|
+
require 'nukumber/model/background'
|
13
|
+
require 'nukumber/model/scenario'
|
14
|
+
require 'nukumber/model/scenario_outline'
|
15
|
+
require 'nukumber/model/table'
|
16
|
+
require 'nukumber/model/examples'
|
17
|
+
require 'nukumber/model/step'
|
18
|
+
|
19
|
+
require 'nukumber/reporter'
|
20
|
+
require 'nukumber/reporter/colour'
|
21
|
+
require 'nukumber/reporter/mono'
|
22
|
+
require 'nukumber/reporter/html'
|
23
|
+
require 'nukumber/reporter/html_css'
|
24
|
+
|
25
|
+
require 'nukumber/builder'
|
26
|
+
require 'nukumber/cli'
|
27
|
+
require 'nukumber/helpers'
|
28
|
+
require 'nukumber/hooks'
|
29
|
+
require 'nukumber/runtime'
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "gherkin"
|
2
|
+
|
3
|
+
module Nukumber
|
4
|
+
|
5
|
+
class GherkinBuilder
|
6
|
+
# Builds the Nukumber Model from the output of the gherkin parser.
|
7
|
+
|
8
|
+
include Gherkin::Rubify
|
9
|
+
|
10
|
+
attr_reader :features
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@features = []
|
14
|
+
@all_longsyms = []
|
15
|
+
@all_shortsyms = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_names
|
19
|
+
if @all_longsyms.include? @current_feature_element.longsym
|
20
|
+
raise Nukumber::NameCollisionError, @current_feature_element.longsym
|
21
|
+
elsif @all_longsyms.include? @current_feature_element.shortsym or @all_shortsyms.include? @current_feature_element.shortsym
|
22
|
+
raise Nukumber::NameCollisionWarning, @current_feature_element.shortsym
|
23
|
+
end
|
24
|
+
@all_longsyms << @current_feature_element.longsym
|
25
|
+
@all_shortsyms << @current_feature_element.shortsym
|
26
|
+
end
|
27
|
+
|
28
|
+
def feature(gherkin_feature)
|
29
|
+
@current_feature = Nukumber::Model::Feature.new(
|
30
|
+
gherkin_feature.name.strip,
|
31
|
+
gherkin_feature.line,
|
32
|
+
gherkin_feature.description.strip,
|
33
|
+
$feature_file_path
|
34
|
+
)
|
35
|
+
@current_feature.tags = gherkin_feature.tags.map(&:name)
|
36
|
+
@features << @current_feature
|
37
|
+
end
|
38
|
+
|
39
|
+
def background(gherkin_background)
|
40
|
+
@current_feature_element = Nukumber::Model::Background.new(
|
41
|
+
gherkin_background.name.strip,
|
42
|
+
gherkin_background.line,
|
43
|
+
gherkin_background.description.strip,
|
44
|
+
@current_feature
|
45
|
+
)
|
46
|
+
@current_feature.background = @current_feature_element
|
47
|
+
check_names
|
48
|
+
end
|
49
|
+
|
50
|
+
def scenario(gherkin_scenario)
|
51
|
+
@current_feature_element = Nukumber::Model::Scenario.new(
|
52
|
+
gherkin_scenario.name.strip,
|
53
|
+
gherkin_scenario.line,
|
54
|
+
gherkin_scenario.description.strip,
|
55
|
+
@current_feature
|
56
|
+
)
|
57
|
+
@current_feature_element.tags = gherkin_scenario.tags.map(&:name)
|
58
|
+
@current_feature.feature_elements << @current_feature_element
|
59
|
+
check_names
|
60
|
+
end
|
61
|
+
|
62
|
+
def scenario_outline(gherkin_outline)
|
63
|
+
@current_feature_element = Nukumber::Model::ScenarioOutline.new(
|
64
|
+
gherkin_outline.name.strip,
|
65
|
+
gherkin_outline.line,
|
66
|
+
gherkin_outline.description.strip,
|
67
|
+
@current_feature
|
68
|
+
)
|
69
|
+
@current_feature_element.tags = gherkin_outline.tags.map(&:name)
|
70
|
+
@current_feature.feature_elements << @current_feature_element
|
71
|
+
check_names
|
72
|
+
end
|
73
|
+
|
74
|
+
def examples(gherkin_examples)
|
75
|
+
@current_feature_element.examples = Nukumber::Model::Examples.new(
|
76
|
+
gherkin_examples.name.strip,
|
77
|
+
gherkin_examples.line,
|
78
|
+
gherkin_examples.keyword,
|
79
|
+
Nukumber::Model::Table.new(gherkin_examples.rows)
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def step(gherkin_step)
|
84
|
+
@current_feature_element.steps << Nukumber::Model::Step.new(
|
85
|
+
gherkin_step.name.strip,
|
86
|
+
gherkin_step.line,
|
87
|
+
gherkin_step.keyword,
|
88
|
+
@current_feature_element,
|
89
|
+
Nukumber::Model::Table.new(gherkin_step.rows)
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def eof
|
94
|
+
@current_feature = nil
|
95
|
+
@current_feature_element = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def syntax_error(*)
|
99
|
+
raise Nukumber::SyntaxError
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/lib/nukumber/cli.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module Nukumber
|
2
|
+
|
3
|
+
class Cli
|
4
|
+
|
5
|
+
def initialize(args, out_stream = STDOUT)
|
6
|
+
|
7
|
+
directories = {
|
8
|
+
:features => 'features',
|
9
|
+
:definitions => 'test_definitions',
|
10
|
+
:support => 'support'
|
11
|
+
}
|
12
|
+
|
13
|
+
reporter_type = 'Mono'
|
14
|
+
reporter_type = 'Colour' if out_stream.tty?
|
15
|
+
|
16
|
+
filters = []
|
17
|
+
filenames = []
|
18
|
+
|
19
|
+
i = 0
|
20
|
+
while i < args.size do
|
21
|
+
|
22
|
+
# named arguments
|
23
|
+
if %w( -h --help ).include? args[i]
|
24
|
+
print_help(out_stream)
|
25
|
+
return
|
26
|
+
|
27
|
+
elsif %w( -fd --features ).include? args[i] and (i+1) < args.size
|
28
|
+
directories[:features] = args[i+1]
|
29
|
+
i += 2
|
30
|
+
next
|
31
|
+
|
32
|
+
elsif %w( -sd --support ).include? args[i] and (i+1) < args.size
|
33
|
+
directories[:support] = args[i+1]
|
34
|
+
i += 2
|
35
|
+
next
|
36
|
+
|
37
|
+
elsif %w( -dd --definitions ).include? args[i] and (i+1) < args.size
|
38
|
+
directories[:definitions] = args[i+1]
|
39
|
+
i += 2
|
40
|
+
next
|
41
|
+
|
42
|
+
elsif %w( -t --tag ).include? args[i] and (i+1) < args.size
|
43
|
+
filters << args[i+1] if args[i+1][0, 1] == '@'
|
44
|
+
i += 2
|
45
|
+
next
|
46
|
+
|
47
|
+
elsif %w( -f --format ).include? args[i] and i + 1 < args.size
|
48
|
+
if %w( c colour ).include? args[i+1]
|
49
|
+
reporter_type = 'Colour'
|
50
|
+
elsif %w( m mono ).include? args[i+1]
|
51
|
+
reporter_type = 'Mono'
|
52
|
+
elsif %w( h html ).include? args[i+1]
|
53
|
+
reporter_type = 'Html'
|
54
|
+
elsif %w( s silent ).include? args[i+1]
|
55
|
+
reporter_type = 'Abstract'
|
56
|
+
else
|
57
|
+
raise "\"#{args[i+1]}\" is an invalid argument to \"#{args[i]}\""
|
58
|
+
end
|
59
|
+
i += 2
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
# filename patterns
|
64
|
+
arr = args[i].split(/:/)
|
65
|
+
filenames << arr.shift
|
66
|
+
arr.each { |n| filters << n.to_i unless n.to_i == 0 }
|
67
|
+
|
68
|
+
# increment and loop
|
69
|
+
i += 1
|
70
|
+
end
|
71
|
+
|
72
|
+
reporter = eval("Nukumber::Reporter::#{reporter_type}").new(out_stream)
|
73
|
+
|
74
|
+
@runtime = Nukumber::Runtime.new(
|
75
|
+
directories,
|
76
|
+
filenames,
|
77
|
+
filters,
|
78
|
+
reporter
|
79
|
+
)
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def print_help(out_stream)
|
84
|
+
out_stream.puts <<HERE
|
85
|
+
Usage: nukumber [options] [FILENAMEPATTERN[:LINE]]
|
86
|
+
-fd, --features X : Specify feature directory name
|
87
|
+
-dd, --definitions X : Specify test definitions subdirectory name
|
88
|
+
-sd, --support X : Specify support code subdirectory name
|
89
|
+
-t, --tag X : Only run tests with this tag
|
90
|
+
-f, --format X : Specify output format; options are c/colour,
|
91
|
+
m/mono, h/html, s/silent
|
92
|
+
FILENAMEPATTERN : Only run feature files whose names include
|
93
|
+
this string
|
94
|
+
LINE : Optional addition to FILENAMEPATTERN; filters
|
95
|
+
to a specific line number
|
96
|
+
HERE
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Nukumber
|
2
|
+
|
3
|
+
class UndefinedTestError < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
class PendingTestError < RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
class NameCollisionError < RuntimeError
|
10
|
+
end
|
11
|
+
|
12
|
+
class NameCollisionWarning < RuntimeError
|
13
|
+
end
|
14
|
+
|
15
|
+
class SyntaxError < RuntimeError
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
def Before(*tags, &proc)
|
2
|
+
unless tags.empty?
|
3
|
+
tags.first.split(',').each do |t|
|
4
|
+
$before_hooks[t.strip.to_sym] ||= []
|
5
|
+
$before_hooks[t.strip.to_sym] << proc
|
6
|
+
end
|
7
|
+
return
|
8
|
+
end
|
9
|
+
$before_hooks[:each] ||= []
|
10
|
+
$before_hooks[:each] << proc
|
11
|
+
end
|
12
|
+
|
13
|
+
def After(*tags, &proc)
|
14
|
+
unless tags.empty?
|
15
|
+
tags.first.split(',').each do |t|
|
16
|
+
$after_hooks[t.strip.to_sym] ||= []
|
17
|
+
$after_hooks[t.strip.to_sym] << proc
|
18
|
+
end
|
19
|
+
return
|
20
|
+
end
|
21
|
+
$after_hooks[:each] ||= []
|
22
|
+
$after_hooks[:each] << proc
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Nukumber
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class Examples
|
6
|
+
attr_reader :name, :line, :keyword
|
7
|
+
attr_accessor :table
|
8
|
+
|
9
|
+
def initialize(name, line, keyword, table)
|
10
|
+
@name, @line, @keyword, @table = name, line, keyword, table
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Nukumber
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class Feature
|
6
|
+
attr_reader :name, :line, :description, :file_path
|
7
|
+
attr_accessor :feature_elements, :tags, :background
|
8
|
+
|
9
|
+
def initialize(name, line, description, file_path)
|
10
|
+
if name.length == 0
|
11
|
+
raise "Features must be named (#{file_path}:#{line})"
|
12
|
+
elsif name[0,1] !~ /[a-zA-Z]/
|
13
|
+
raise "Feature names must begin with a letter (#{file_path}:#{line})"
|
14
|
+
end
|
15
|
+
@name, @line, @description = name, line, description
|
16
|
+
@feature_elements, @tags = [], []
|
17
|
+
@background = nil
|
18
|
+
@file_path = file_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def keyword
|
22
|
+
'Feature'
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{keyword} \"#{name}\""
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Nukumber
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class FeatureElement
|
6
|
+
attr_reader :name, :line, :description
|
7
|
+
attr_reader :feature
|
8
|
+
attr_accessor :steps
|
9
|
+
|
10
|
+
def initialize(name, line, description, feature)
|
11
|
+
if name.length == 0
|
12
|
+
raise "Feature elements must be named (#{feature.file_path}:#{line})"
|
13
|
+
elsif name[0, 1] !~ /[a-zA-Z]/
|
14
|
+
raise "Feature element names must begin with a letter (#{feature.file_path}:#{line})"
|
15
|
+
end
|
16
|
+
@name, @line, @description, @feature = name, line, description, feature
|
17
|
+
@steps = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def shortsym
|
21
|
+
name.nukesym
|
22
|
+
end
|
23
|
+
|
24
|
+
def longsym
|
25
|
+
(@feature.name.nukesym.to_s + LONGSYM_DELIM + name.nukesym.to_s).to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#{keyword} \"#{name}\""
|
30
|
+
end
|
31
|
+
|
32
|
+
def step_args
|
33
|
+
step_args = []
|
34
|
+
@steps.each do |s|
|
35
|
+
step_args << s.args.all_row_hashes unless s.args.empty?
|
36
|
+
end
|
37
|
+
while step_args.is_a? Array and step_args.size == 1
|
38
|
+
step_args = step_args.first
|
39
|
+
end
|
40
|
+
step_args
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Nukumber
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class Scenario < FeatureElement
|
6
|
+
attr_accessor :tags
|
7
|
+
|
8
|
+
def initialize(name, line, description, feature)
|
9
|
+
super(name, line, description, feature)
|
10
|
+
@tags = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def keyword
|
14
|
+
'Scenario'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|