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 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
@@ -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,5 @@
1
+ class String
2
+ def nukesym
3
+ self.downcase.gsub(/\W/, '_').to_sym
4
+ end
5
+ 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,9 @@
1
+ module Nukumber
2
+
3
+ module Model
4
+
5
+ LONGSYM_DELIM = '___'
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,17 @@
1
+ module Nukumber
2
+
3
+ module Model
4
+
5
+ class Background < FeatureElement
6
+ def keyword
7
+ 'Background'
8
+ end
9
+
10
+ def tags
11
+ []
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ 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