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.
@@ -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