gurke 1.0.1 → 2.0.0.dev.1.b17
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.
- 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
|