gurke 1.0.1 → 2.0.0.dev.1.b17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/README.md +85 -38
- data/bin/gurke +5 -0
- data/features/gurke.feature +47 -0
- data/features/gurke.rb +12 -0
- data/features/gurke/backtrace_filtering.feature +36 -0
- data/features/gurke/filter_by_tags.feature +70 -0
- data/features/gurke/step_specific_definitions.feature +41 -0
- data/features/support/steps/cli_steps.rb +50 -0
- data/features/support/steps/file_steps.rb +32 -0
- data/gurke.gemspec +11 -7
- data/lib/gurke.rb +43 -16
- data/lib/gurke/background.rb +33 -0
- data/lib/gurke/builder.rb +107 -0
- data/lib/gurke/capybara.rb +28 -0
- data/lib/gurke/cli.rb +67 -0
- data/lib/gurke/configuration.rb +118 -0
- data/lib/gurke/dsl.rb +45 -0
- data/lib/gurke/feature.rb +55 -0
- data/lib/gurke/reporter.rb +98 -0
- data/lib/gurke/rspec.rb +5 -0
- data/lib/gurke/runner.rb +156 -0
- data/lib/gurke/scenario.rb +84 -0
- data/lib/gurke/step.rb +41 -0
- data/lib/gurke/step_definition.rb +31 -0
- data/lib/gurke/steps.rb +36 -0
- data/lib/gurke/tag.rb +41 -0
- data/lib/gurke/version.rb +14 -0
- metadata +59 -36
- data/.gitignore +0 -19
- data/Gemfile +0 -4
- data/Rakefile +0 -1
- data/lib/gurke/current.rb +0 -39
- data/lib/gurke/formatter.rb +0 -180
- data/lib/gurke/formatters/base.rb +0 -21
- data/lib/gurke/formatters/headless.rb +0 -108
- data/lib/gurke/hooks.rb +0 -42
- data/lib/gurke/patch/cucumber_cli_configuration.rb +0 -16
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
module FileSteps
|
3
|
+
def _write_file(path, content)
|
4
|
+
file = @__root.join(path)
|
5
|
+
|
6
|
+
FileUtils.mkdir_p(File.dirname(file))
|
7
|
+
File.write(file, content)
|
8
|
+
end
|
9
|
+
|
10
|
+
def _read_file(path)
|
11
|
+
file = @__root.join(path)
|
12
|
+
|
13
|
+
File.read(file)
|
14
|
+
end
|
15
|
+
|
16
|
+
step(/I am in a project using gurke/) do
|
17
|
+
_write_file 'Gemfile', <<-EOS
|
18
|
+
source 'https://rubygems.org'
|
19
|
+
gem 'gurke', path: '#{File.dirname(Gurke.root)}'
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
|
23
|
+
step(/a file "(.*?)" with the following content exists/) do |path, step|
|
24
|
+
_write_file(path, step.doc_string)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Then(/a file "(.*?)" with the following content exists/) do |path, step|
|
28
|
+
# expect(_read_file(path)).to eq step.doc_string
|
29
|
+
# end
|
30
|
+
end
|
31
|
+
|
32
|
+
Gurke.config.include FileSteps
|
data/gurke.gemspec
CHANGED
@@ -1,25 +1,29 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gurke/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |spec|
|
6
7
|
spec.name = 'gurke'
|
7
|
-
spec.version =
|
8
|
+
spec.version = Gurke::VERSION
|
8
9
|
spec.authors = ['Jan Graichen']
|
9
10
|
spec.email = %w(jg@altimos.de)
|
10
|
-
spec.description = %q{
|
11
|
-
spec.summary = %q{
|
11
|
+
spec.description = %q{An alternative gherkin feature runner inspired by rspec and turnip.}
|
12
|
+
spec.summary = %q{An alternative gherkin feature runner inspired by rspec and turnip.}
|
12
13
|
spec.homepage = 'https://github.com/jgraichen/gurke'
|
13
14
|
spec.license = 'MIT'
|
14
15
|
|
15
|
-
spec.files =
|
16
|
+
spec.files = Dir['**/*'].grep(%r{^((bin|lib|test|spec|features)/|.*\.gemspec|.*LICENSE.*|.*README.*|.*CHANGELOG.*)})
|
16
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
19
|
spec.require_paths = %w(lib)
|
19
20
|
|
20
|
-
spec.add_dependency '
|
21
|
-
spec.add_dependency '
|
21
|
+
spec.add_dependency 'trollop'
|
22
|
+
spec.add_dependency 'gherkin'
|
23
|
+
spec.add_dependency 'colorize'
|
22
24
|
|
23
25
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
-
|
26
|
+
|
27
|
+
# Append travis build number for auto-releases
|
28
|
+
spec.version = "#{spec.version}.1.b#{ENV['TRAVIS_BUILD_NUMBER']}" if ENV['TRAVIS_BUILD_NUMBER']
|
25
29
|
end
|
data/lib/gurke.rb
CHANGED
@@ -1,30 +1,57 @@
|
|
1
|
-
require '
|
1
|
+
require 'gurke/version'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
#
|
4
|
+
module Gurke
|
5
|
+
require 'gurke/feature'
|
6
|
+
require 'gurke/background'
|
7
|
+
require 'gurke/scenario'
|
8
|
+
require 'gurke/step'
|
9
|
+
require 'gurke/tag'
|
5
10
|
|
6
|
-
require 'gurke/
|
7
|
-
require 'gurke/
|
11
|
+
require 'gurke/dsl'
|
12
|
+
require 'gurke/builder'
|
13
|
+
require 'gurke/configuration'
|
14
|
+
require 'gurke/runner'
|
15
|
+
require 'gurke/steps'
|
16
|
+
require 'gurke/step_definition'
|
17
|
+
require 'gurke/reporter'
|
8
18
|
|
19
|
+
class Error < StandardError; end
|
20
|
+
class StepPending < Error; end
|
21
|
+
class StepAmbiguous < Error; end
|
9
22
|
|
10
|
-
module Gurke
|
11
23
|
class << self
|
12
|
-
|
13
|
-
|
24
|
+
#
|
25
|
+
# Return path to features directory.
|
26
|
+
#
|
27
|
+
# @return [Path] Feature directory.
|
28
|
+
#
|
29
|
+
def root
|
30
|
+
@root ||= Pathname.new(Dir.getwd).join('features')
|
14
31
|
end
|
15
32
|
|
16
|
-
|
17
|
-
|
33
|
+
# Return configuration object.
|
34
|
+
#
|
35
|
+
# @return [Configuration] Configuration object.
|
36
|
+
#
|
37
|
+
def config
|
38
|
+
@config ||= Configuration.new
|
18
39
|
end
|
19
40
|
|
20
|
-
|
21
|
-
|
41
|
+
# Yield configuration object.
|
42
|
+
#
|
43
|
+
# @yield [config] Yield configuration object.
|
44
|
+
# @yieldparam config [Configuration] Configuration object.
|
45
|
+
#
|
46
|
+
def configure
|
47
|
+
yield config if block_given?
|
22
48
|
end
|
23
49
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
50
|
+
# @api private
|
51
|
+
def world
|
52
|
+
@world ||= const_set('World', Module.new)
|
28
53
|
end
|
29
54
|
end
|
30
55
|
end
|
56
|
+
|
57
|
+
::Module.send(:include, Gurke::DSL)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Gurke
|
2
|
+
#
|
3
|
+
class Background
|
4
|
+
#
|
5
|
+
# Return path to file containing this background.
|
6
|
+
#
|
7
|
+
# @return [String] File path.
|
8
|
+
#
|
9
|
+
attr_reader :file
|
10
|
+
|
11
|
+
# Return line number where this background is defined.
|
12
|
+
#
|
13
|
+
# @return [Fixnum] Line number.
|
14
|
+
#
|
15
|
+
attr_reader :line
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
attr_reader :raw
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def initialize(file, line, raw)
|
22
|
+
@file, @line, @raw = file, line, raw
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return list of steps this background specifies.
|
26
|
+
#
|
27
|
+
# @return [Array<Step>] Steps.
|
28
|
+
#
|
29
|
+
def steps
|
30
|
+
@steps ||= []
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'gherkin'
|
2
|
+
|
3
|
+
module Gurke
|
4
|
+
#
|
5
|
+
class Builder
|
6
|
+
#
|
7
|
+
attr_reader :features, :options
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
@features = []
|
12
|
+
@language = 'en'
|
13
|
+
@parser = Gherkin::Parser::Parser.new(
|
14
|
+
self, true, 'root', false, @language)
|
15
|
+
|
16
|
+
@keywords = {}
|
17
|
+
Gherkin::I18n::LANGUAGES[@language].each do |k, v|
|
18
|
+
v.split('|').map(&:strip).each do |str|
|
19
|
+
@keywords[str] = k.to_sym if str != '*'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(feature_file)
|
25
|
+
@parser.parse(File.read(feature_file), feature_file, 0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def uri(raw)
|
29
|
+
@file = raw.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def feature(raw)
|
33
|
+
tags = raw.tags.map{|t| Tag.new(@file, t.line, t) }
|
34
|
+
|
35
|
+
@current_feature = Feature.new(@file, raw.line, tags, raw)
|
36
|
+
@features << @current_feature
|
37
|
+
end
|
38
|
+
|
39
|
+
def background(raw)
|
40
|
+
@current_context = Background.new(@file, raw.line, raw)
|
41
|
+
@current_feature.backgrounds << @current_context
|
42
|
+
end
|
43
|
+
|
44
|
+
def scenario(raw)
|
45
|
+
tags = raw.tags.map{|t| Tag.new(@file, t.line, t) }
|
46
|
+
tags += @current_feature.tags
|
47
|
+
|
48
|
+
@current_context = Scenario.new(@file, raw.line, tags, raw)
|
49
|
+
|
50
|
+
unless filtered?(@current_context)
|
51
|
+
@current_feature.scenarios << @current_context
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def step(raw)
|
56
|
+
type = get_type(raw.keyword.strip)
|
57
|
+
|
58
|
+
@current_context.steps << Step.new(@file, raw.line, type, raw)
|
59
|
+
end
|
60
|
+
|
61
|
+
def eof(*)
|
62
|
+
@features.reject!{|f| f.scenarios.empty? }
|
63
|
+
@current_context = nil
|
64
|
+
@current_feature = nil
|
65
|
+
@file = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_type(keyword)
|
69
|
+
case (kw = @keywords.fetch(keyword))
|
70
|
+
when :and, :but
|
71
|
+
if (step = @current_context.steps.last)
|
72
|
+
step.type
|
73
|
+
else
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
else
|
77
|
+
kw
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def filter_sets
|
82
|
+
@filter_sets ||= options[:tags].map do |list|
|
83
|
+
list.strip.split(/[,+\s]\s*/).map{|t| Filter.new(t) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def filtered?(scenario)
|
88
|
+
!filter_sets.reduce(false) do |memo, set|
|
89
|
+
memo || set.all?{|rule| rule.match? scenario }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Filter = Struct.new(:tag) do
|
94
|
+
def name
|
95
|
+
@name ||= negated? ? tag[1..-1] : tag
|
96
|
+
end
|
97
|
+
|
98
|
+
def negated?
|
99
|
+
tag[0] == '~'
|
100
|
+
end
|
101
|
+
|
102
|
+
def match?(taggable)
|
103
|
+
negated? != taggable.tags.any?{|t| t.name == name }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require 'gurke'
|
3
|
+
require 'capybara/dsl'
|
4
|
+
require 'capybara/rspec/matchers'
|
5
|
+
|
6
|
+
Gurke.configure do |c|
|
7
|
+
c.include Capybara::DSL, type: :feature
|
8
|
+
c.include Capybara::RSpecMatchers, type: :feature
|
9
|
+
|
10
|
+
c.before do
|
11
|
+
next unless self.class.include?(Capybara::DSL)
|
12
|
+
|
13
|
+
# if context.metadata[:js]
|
14
|
+
# Capybara.current_driver = Capybara.javascript_driver
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# if context.metadata[:driver]
|
18
|
+
# Capybara.current_driver = context.metadata[:driver]
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
c.after do
|
23
|
+
next unless self.class.include?(Capybara::DSL)
|
24
|
+
|
25
|
+
Capybara.reset_sessions!
|
26
|
+
Capybara.use_default_driver
|
27
|
+
end
|
28
|
+
end
|
data/lib/gurke/cli.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
|
3
|
+
module Gurke
|
4
|
+
#
|
5
|
+
class CLI
|
6
|
+
#
|
7
|
+
# Run CLI with given arguments.
|
8
|
+
#
|
9
|
+
# @param argv [Array<String>] Tokenized argument list.
|
10
|
+
#
|
11
|
+
def run(argv)
|
12
|
+
call parser.parse(argv), argv
|
13
|
+
rescue Trollop::VersionNeeded
|
14
|
+
print_version && exit
|
15
|
+
rescue Trollop::HelpNeeded
|
16
|
+
print_help && exit
|
17
|
+
rescue Trollop::CommandlineError => e
|
18
|
+
$stderr.puts "Error: #{e}"
|
19
|
+
$stderr.puts "Run with `-h' for more information on available arguments."
|
20
|
+
exit 255
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(options, files)
|
24
|
+
if File.exist?(Gurke.root.join('gurke.rb'))
|
25
|
+
require File.expand_path(Gurke.root.join('gurke.rb'))
|
26
|
+
end
|
27
|
+
|
28
|
+
options[:require].each do |r|
|
29
|
+
Dir[r].each{|f| require File.expand_path(f) }
|
30
|
+
end if options[:require].any?
|
31
|
+
|
32
|
+
files = Dir[options[:pattern].to_s] if files.empty? && options[:pattern]
|
33
|
+
status = Runner.new(files, options).run
|
34
|
+
|
35
|
+
Kernel.exit(status)
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_version
|
39
|
+
$stdout.puts <<-EOF.gsub(/^ {8}/, '')
|
40
|
+
gurke v#{Gurke::VERSION}
|
41
|
+
EOF
|
42
|
+
end
|
43
|
+
|
44
|
+
def print_help
|
45
|
+
parser.educate($stdout)
|
46
|
+
end
|
47
|
+
|
48
|
+
def parser
|
49
|
+
@parser ||= Trollop::Parser.new do
|
50
|
+
opt :help, 'Print this help.'
|
51
|
+
opt :version, 'Show program version information.'
|
52
|
+
opt :backtrace, 'Show full error backtraces.'
|
53
|
+
opt :pattern, 'File pattern matching feature files to be run.',
|
54
|
+
default: 'features/**/*.feature'
|
55
|
+
opt :require, 'Files matching this pattern will be required after'\
|
56
|
+
'loading environment but before running features.',
|
57
|
+
default: ['features/steps/**/*.rb',
|
58
|
+
'features/support/steps/**/*.rb'],
|
59
|
+
multi: true
|
60
|
+
opt :tags, 'Only run features and scenarios matching given tag '\
|
61
|
+
'filtering expression. TODO: Description.',
|
62
|
+
default: ['~wip'],
|
63
|
+
multi: true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Gurke
|
2
|
+
#
|
3
|
+
class Configuration
|
4
|
+
#
|
5
|
+
# Define a before filter running before given action.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Gurke.before(:step) do
|
9
|
+
# puts step.description
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# @param action [Symbol] A defined action like `:feature`,
|
13
|
+
# `:scenario` or `:step`.
|
14
|
+
#
|
15
|
+
# @yield Before any matching action is executed.
|
16
|
+
#
|
17
|
+
def before(action = :scenario, opts = nil, &block)
|
18
|
+
BEFORE_HOOKS.append action, Hook.new(opts, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def around(action = :scenario, opts = nil, &block)
|
22
|
+
AROUND_HOOKS.append action, Hook.new(opts, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define a after filter running after given action.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Gurke.after(:step) do
|
29
|
+
# puts step.description
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @param action [Symbol] A defined action like `:feature`,
|
33
|
+
# `:scenario` or `:step`.
|
34
|
+
#
|
35
|
+
# @yield After any matching action is executed.
|
36
|
+
#
|
37
|
+
def after(action = :scenario, opts = nil, &block)
|
38
|
+
AFTER_HOOKS.append action, Hook.new(opts, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Include given module into all or specific features or
|
42
|
+
# scenarios.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# Gurke.include(MyTestMethods)
|
46
|
+
#
|
47
|
+
# @param mod [Module] Module to include.
|
48
|
+
# @param opts [Hash] Options.
|
49
|
+
#
|
50
|
+
def include(mod, opts = {})
|
51
|
+
inclusions << Inclusion.new(mod, opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def inclusions
|
56
|
+
@inclusions ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
def hooks
|
61
|
+
@hooks ||= Hooks.new
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
class Inclusion
|
66
|
+
attr_reader :mod, :opts
|
67
|
+
|
68
|
+
def initialize(mod, opts)
|
69
|
+
@mod = mod
|
70
|
+
@opts = opts
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
class HookSet
|
76
|
+
attr_reader :hooks
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@hooks = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def for(action)
|
83
|
+
hooks[action] ||= []
|
84
|
+
end
|
85
|
+
|
86
|
+
def append(action, hook)
|
87
|
+
self.for(action) << hook
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
BEFORE_HOOKS = HookSet.new
|
92
|
+
AROUND_HOOKS = HookSet.new
|
93
|
+
AFTER_HOOKS = HookSet.new
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
class Hook
|
97
|
+
attr_reader :opts, :block
|
98
|
+
|
99
|
+
def initialize(opts, &block)
|
100
|
+
@opts = opts
|
101
|
+
@block = block
|
102
|
+
end
|
103
|
+
|
104
|
+
def match?(context)
|
105
|
+
!opts.any?{|k, v| context.metadata[k] != v }
|
106
|
+
end
|
107
|
+
|
108
|
+
def run(context, *args)
|
109
|
+
block = @block
|
110
|
+
if context
|
111
|
+
context.instance_exec(*args, &block)
|
112
|
+
else
|
113
|
+
block.call(*args)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|