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.
@@ -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
@@ -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 = '1.0.1'
8
+ spec.version = Gurke::VERSION
8
9
  spec.authors = ['Jan Graichen']
9
10
  spec.email = %w(jg@altimos.de)
10
- spec.description = %q{A description}
11
- spec.summary = %q{A summary}
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 = `git ls-files`.split($/)
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 'cucumber'
21
- spec.add_dependency 'activesupport'
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
- spec.add_development_dependency 'rake'
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
@@ -1,30 +1,57 @@
1
- require 'cucumber'
1
+ require 'gurke/version'
2
2
 
3
- require 'gurke/patch/cucumber_cli_configuration'
4
- require 'gurke/formatter'
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/current'
7
- require 'gurke/hooks'
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
- def current
13
- @current ||= Gurke::Current.instance
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
- def before(stage, &block)
17
- Hooks.before.add stage, &block
33
+ # Return configuration object.
34
+ #
35
+ # @return [Configuration] Configuration object.
36
+ #
37
+ def config
38
+ @config ||= Configuration.new
18
39
  end
19
40
 
20
- def after(stage, &block)
21
- Hooks.after.add stage, &block
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
- def step!(opts = {})
25
- if (fmt = Gurke::Formatter.instance)
26
- fmt.manual_step current.step, opts
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
@@ -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