summaryrefslogtreecommitdiffstats
path: root/cucumber/features/support/extra_hooks.rb
blob: 16196a558a065c08b38c0120de4bc61e5d33e6c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# Make the code below work with cucumber >= 2.0. Once we stop
# supporting <2.0 we should probably do this differently, but this way
# we can easily support both at the same time.
begin
  if not(Cucumber::Core::Ast::Feature.instance_methods.include?(:accept_hook?))
    require 'gherkin/tag_expression'
    class Cucumber::Core::Ast::Feature
      # Code inspired by Cucumber::Core::Test::Case.match_tags?() in
      # cucumber-ruby-core 1.1.3, lib/cucumber/core/test/case.rb:~59.
      def accept_hook?(hook)
        tag_expr = Gherkin::TagExpression.new(hook.tag_expressions.flatten)
        tags = @tags.map do |t|
          Gherkin::Formatter::Model::Tag.new(t.name, t.line)
        end
        tag_expr.evaluate(tags)
      end
    end
  end
rescue NameError => e
  raise e if e.to_s != "uninitialized constant Cucumber::Core"
end

# Sort of inspired by Cucumber::RbSupport::RbHook (from cucumber
# < 2.0) but really we just want an object with a 'tag_expressions'
# attribute to make accept_hook?() (used below) happy.
class SimpleHook
  attr_reader :tag_expressions

  def initialize(tag_expressions, proc)
    @tag_expressions = tag_expressions
    @proc = proc
  end

  def invoke(arg)
    @proc.call(arg)
  end
end

def BeforeFeature(*tag_expressions, &block)
  $before_feature_hooks ||= []
  $before_feature_hooks << SimpleHook.new(tag_expressions, block)
end

def AfterFeature(*tag_expressions, &block)
  $after_feature_hooks ||= []
  $after_feature_hooks << SimpleHook.new(tag_expressions, block)
end

require 'cucumber/formatter/console'
if not($at_exit_print_artifacts_dir_patching_done)
  module Cucumber::Formatter::Console
    if method_defined?(:print_stats)
      alias old_print_stats print_stats
    end
    def print_stats(*args)
      if Dir.exists?(ARTIFACTS_DIR) and Dir.entries(ARTIFACTS_DIR).size > 2
        @io.puts "Artifacts directory: #{ARTIFACTS_DIR}"
        @io.puts
      end
      if self.class.method_defined?(:old_print_stats)
        old_print_stats(*args)
      end
    end
  end
  $at_exit_print_artifacts_dir_patching_done = true
end

def info_log(message = "", options = {})
  options[:color] = :clear
  # This trick allows us to use a module's (~private) method on a
  # one-off basis.
  cucumber_console = Class.new.extend(Cucumber::Formatter::Console)
  puts cucumber_console.format_string(message, options[:color])
end

def debug_log(message, options = {})
  $debug_log_fns.each { |fn| fn.call(message, options) } if $debug_log_fns
end

require 'cucumber/formatter/pretty'
# Backport part of commit af940a8 from the cucumber-ruby repo. This
# fixes the "out hook output" for the Pretty formatter so stuff
# written via `puts` after a Scenario has run its last step will be
# written, instead of delayed to the next Feature/Scenario (if any) or
# dropped completely (if not).
# XXX: This can be removed once we stop supporting Debian Jessie
# around when Debian Stretch is released.
if Gem::Version.new(Cucumber::VERSION) < Gem::Version.new('2.0.0.beta.4')
  module Cucumber
    module Formatter
      class Pretty
        def after_feature_element(feature_element)
          print_messages
          @io.puts
          @io.flush
        end
      end
    end
  end
end

module ExtraFormatters
  # This is a null formatter in the sense that it doesn't ever output
  # anything. We only use it do hook into the correct events so we can
  # add our extra hooks.
  class ExtraHooks
    def initialize(*args)
      # We do not care about any of the arguments.
    end

    def before_feature(feature)
      if $before_feature_hooks
        $before_feature_hooks.each do |hook|
          hook.invoke(feature) if feature.accept_hook?(hook)
        end
      end
    end

    def after_feature(feature)
      if $after_feature_hooks
        $after_feature_hooks.reverse.each do |hook|
          hook.invoke(feature) if feature.accept_hook?(hook)
        end
      end
    end
  end

  # The pretty formatter with debug logging mixed into its output.
  class PrettyDebug < Cucumber::Formatter::Pretty
    def initialize(*args)
      super(*args)
      $debug_log_fns ||= []
      $debug_log_fns << self.method(:debug_log)
    end

    def debug_log(message, options)
      options[:color] ||= :blue
      @io.puts(format_string(message, options[:color]))
      @io.flush
    end
  end

end

module Cucumber
  module Cli
    class Options
      BUILTIN_FORMATS['pretty_debug'] =
        [
          'ExtraFormatters::PrettyDebug',
          'Prints the feature with debugging information - in colours.'
        ]
      BUILTIN_FORMATS['debug'] = BUILTIN_FORMATS['pretty_debug']
    end
  end
end

AfterConfiguration do |config|
  # Cucumber may read this file multiple times, and hence run this
  # AfterConfiguration hook multiple times. We only want our
  # ExtraHooks formatter to be loaded once, otherwise the hooks would
  # be run miltiple times.
  extra_hooks = ['ExtraFormatters::ExtraHooks', '/dev/null']
  config.formats << extra_hooks if not(config.formats.include?(extra_hooks))
end