aruba 0.12.0 → 0.13.0

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/History.md +75 -55
  4. data/Rakefile +1 -1
  5. data/aruba.gemspec +0 -1
  6. data/features/api/command/run_simple.feature +37 -2
  7. data/features/api/command/stderr.feature +46 -0
  8. data/features/api/command/stdout.feature +46 -0
  9. data/features/configuration/activate_announcer_on_command_failure.feature +38 -0
  10. data/features/steps/command/run.feature +28 -0
  11. data/features/steps/command/shell.feature +155 -0
  12. data/features/steps/core/announce.feature +80 -0
  13. data/features/support/aruba.rb +3 -2
  14. data/lib/aruba/api/command.rb +7 -10
  15. data/lib/aruba/colorizer.rb +108 -0
  16. data/lib/aruba/config.rb +4 -2
  17. data/lib/aruba/cucumber/command.rb +12 -0
  18. data/lib/aruba/cucumber/hooks.rb +10 -0
  19. data/lib/aruba/errors.rb +6 -0
  20. data/lib/aruba/event_bus.rb +59 -0
  21. data/lib/aruba/event_bus/name_resolver.rb +168 -0
  22. data/lib/aruba/generators/script_file.rb +46 -0
  23. data/lib/aruba/matchers/path/have_permissions.rb +1 -1
  24. data/lib/aruba/platforms/announcer.rb +30 -23
  25. data/lib/aruba/platforms/filesystem_status.rb +68 -0
  26. data/lib/aruba/platforms/simple_table.rb +14 -7
  27. data/lib/aruba/platforms/unix_platform.rb +11 -2
  28. data/lib/aruba/platforms/unix_which.rb +0 -2
  29. data/lib/aruba/platforms/windows_which.rb +0 -2
  30. data/lib/aruba/processes/basic_process.rb +8 -0
  31. data/lib/aruba/processes/spawn_process.rb +31 -12
  32. data/lib/aruba/rspec.rb +12 -8
  33. data/lib/aruba/runtime.rb +2 -2
  34. data/lib/aruba/setup.rb +5 -2
  35. data/lib/aruba/version.rb +1 -1
  36. data/script/bootstrap +8 -0
  37. data/spec/aruba/api_spec.rb +1 -1
  38. data/spec/aruba/platform/simple_table_spec.rb +2 -2
  39. data/spec/event_bus/name_resolver_spec.rb +68 -0
  40. data/spec/event_bus_spec.rb +160 -0
  41. data/spec/spec_helper.rb +0 -3
  42. data/spec/support/configs/aruba.rb +5 -0
  43. metadata +22 -17
@@ -201,3 +201,83 @@ Feature: Announce output during test run
201
201
  """
202
202
  $ export MY_VAR=my\ value\ \
203
203
  """
204
+
205
+ Scenario: Announce file system status of command
206
+ This will output information like owner, group, atime, mtime, ctime, size,
207
+ mode and if command is executable.
208
+
209
+ Given an executable named "bin/cli" with:
210
+ """bash
211
+ #!/usr/bin/env bash
212
+
213
+ echo 'Hello World'
214
+ """
215
+ And a file named "features/exit_status.feature" with:
216
+ """cucumber
217
+ Feature: Announce
218
+ @announce-command-filesystem-status
219
+ Scenario: Run command
220
+ And I run `cli`
221
+ Then the exit status should be 0
222
+ """
223
+ When I run `cucumber`
224
+ Then the features should all pass
225
+ And the output should contain:
226
+ """
227
+ # mode => 755
228
+ """
229
+ And the output should contain:
230
+ """
231
+ # owner
232
+ """
233
+ And the output should contain:
234
+ """
235
+ # group
236
+ """
237
+ And the output should contain:
238
+ """
239
+ # ctime
240
+ """
241
+ And the output should contain:
242
+ """
243
+ # mtime
244
+ """
245
+ And the output should contain:
246
+ """
247
+ # atime
248
+ """
249
+ And the output should contain:
250
+ """
251
+ # size
252
+ """
253
+ And the output should contain:
254
+ """
255
+ # executable
256
+ """
257
+
258
+ Scenario: Announce content of command
259
+ This will output the content of the executable command. Be careful doing
260
+ this with binary executables. This hook should be used with scripts only.
261
+
262
+ Given an executable named "bin/cli" with:
263
+ """bash
264
+ #!/usr/bin/env bash
265
+
266
+ echo 'Hello World'
267
+ """
268
+ And a file named "features/exit_status.feature" with:
269
+ """cucumber
270
+ Feature: Announce
271
+ @announce-command-content
272
+ Scenario: Run command
273
+ And I run `cli`
274
+ Then the exit status should be 0
275
+ """
276
+ When I run `cucumber`
277
+ Then the features should all pass
278
+ And the output should contain:
279
+ """
280
+ #!/usr/bin/env bash
281
+
282
+ echo 'Hello World'
283
+ """
@@ -1,6 +1,7 @@
1
1
  require 'aruba/cucumber'
2
2
 
3
3
  Aruba.configure do |config|
4
- config.exit_timeout = 120
5
- config.io_wait_timeout = 2
4
+ config.exit_timeout = 120
5
+ config.io_wait_timeout = 2
6
+ config.activate_announcer_on_command_failure = [:stderr, :stdout, :command]
6
7
  end
@@ -168,14 +168,6 @@ module Aruba
168
168
  working_directory = expand_path('.')
169
169
  event_bus = aruba.event_bus
170
170
 
171
- aruba.announcer.announce(:full_environment, environment)
172
- aruba.announcer.announce(:timeout, 'exit', exit_timeout)
173
- aruba.announcer.announce(:timeout, 'io wait', io_wait_timeout)
174
- aruba.announcer.announce(:wait_time, 'startup wait time', startup_wait_time)
175
-
176
- aruba.announcer.announce(:directory, working_directory)
177
- aruba.announcer.announce(:command, cmd)
178
-
179
171
  cmd = Aruba.platform.detect_ruby(cmd)
180
172
 
181
173
  mode = if Aruba.process
@@ -285,8 +277,13 @@ module Aruba
285
277
  end
286
278
 
287
279
  if fail_on_error
288
- expect(command).to have_finished_in_time
289
- expect(command).to be_successfully_executed
280
+ begin
281
+ expect(command).to have_finished_in_time
282
+ expect(command).to be_successfully_executed
283
+ rescue RSpec::Expectations::ExpectationNotMetError => e
284
+ aruba.announcer.activate(aruba.config.activate_announcer_on_command_failure)
285
+ raise e
286
+ end
290
287
  end
291
288
  end
292
289
  # rubocop:enable Metrics/CyclomaticComplexity
@@ -0,0 +1,108 @@
1
+ module Aruba
2
+ # The ANSIColor module can be used for namespacing and mixed into your own
3
+ # classes.
4
+ module AnsiColor
5
+ # :stopdoc:
6
+ ATTRIBUTES = [
7
+ [ :clear , 0 ],
8
+ [ :reset , 0 ], # synonym for :clear
9
+ [ :bold , 1 ],
10
+ [ :dark , 2 ],
11
+ [ :italic , 3 ], # not widely implemented
12
+ [ :underline , 4 ],
13
+ [ :underscore , 4 ], # synonym for :underline
14
+ [ :blink , 5 ],
15
+ [ :rapid_blink , 6 ], # not widely implemented
16
+ [ :negative , 7 ], # no reverse because of String#reverse
17
+ [ :concealed , 8 ],
18
+ [ :strikethrough, 9 ], # not widely implemented
19
+ [ :black , 30 ],
20
+ [ :red , 31 ],
21
+ [ :green , 32 ],
22
+ [ :yellow , 33 ],
23
+ [ :blue , 34 ],
24
+ [ :magenta , 35 ],
25
+ [ :cyan , 36 ],
26
+ [ :white , 37 ],
27
+ [ :on_black , 40 ],
28
+ [ :on_red , 41 ],
29
+ [ :on_green , 42 ],
30
+ [ :on_yellow , 43 ],
31
+ [ :on_blue , 44 ],
32
+ [ :on_magenta , 45 ],
33
+ [ :on_cyan , 46 ],
34
+ [ :on_white , 47 ]
35
+ ]
36
+
37
+ ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
38
+ # :startdoc:
39
+
40
+ # Returns true, if the coloring function of this module
41
+ # is switched on, false otherwise.
42
+ def self.coloring?
43
+ @coloring
44
+ end
45
+
46
+ # Turns the coloring on or off globally, so you can easily do
47
+ # this for example:
48
+ # Cucumber::Term::ANSIColor::coloring = STDOUT.isatty
49
+ def self.coloring=(val)
50
+ @coloring = val
51
+ end
52
+ self.coloring = true
53
+
54
+ ATTRIBUTES.each do |c, v|
55
+ define_method(c) do |string|
56
+ result = ''
57
+ result << "\e[#{v}m" if Aruba::AnsiColor.coloring?
58
+ if block_given?
59
+ result << yield
60
+ elsif string
61
+ result << string
62
+ elsif respond_to?(:to_str)
63
+ result << to_str
64
+ else
65
+ return result #only switch on
66
+ end
67
+ result << "\e[0m" if Aruba::AnsiColor.coloring?
68
+ result
69
+ end
70
+ end
71
+
72
+ # Regular expression that is used to scan for ANSI-sequences while
73
+ # uncoloring strings.
74
+ COLORED_REGEXP = /\e\[(?:[34][0-7]|[0-9])?m/
75
+
76
+ def self.included(klass)
77
+ if klass == String
78
+ ATTRIBUTES.delete(:clear)
79
+ ATTRIBUTE_NAMES.delete(:clear)
80
+ end
81
+ end
82
+
83
+ # Returns an uncolored version of the string, that is all
84
+ # ANSI-sequences are stripped from the string.
85
+ def uncolored(string = nil) # :yields:
86
+ if block_given?
87
+ yield.gsub(COLORED_REGEXP, '')
88
+ elsif string
89
+ string.gsub(COLORED_REGEXP, '')
90
+ elsif respond_to?(:to_str)
91
+ to_str.gsub(COLORED_REGEXP, '')
92
+ else
93
+ ''
94
+ end
95
+ end
96
+
97
+ # Returns an array of all Aruba::Platforms::AnsiColor attributes as symbols.
98
+ def attributes
99
+ ATTRIBUTE_NAMES
100
+ end
101
+ end
102
+ end
103
+
104
+ module Aruba
105
+ class Colorizer
106
+ include Aruba::AnsiColor
107
+ end
108
+ end
data/lib/aruba/config.rb CHANGED
@@ -26,9 +26,9 @@ module Aruba
26
26
  option_accessor :working_directory, :contract => { Aruba::Contracts::RelativePath => Aruba::Contracts::RelativePath }, :default => 'tmp/aruba'
27
27
 
28
28
  if RUBY_VERSION < '1.9'
29
- option_reader :fixtures_path_prefix, :contract => { None => String }, :default => '%'
29
+ option_reader :fixtures_path_prefix, :contract => { None => String }, :default => '%'
30
30
  else
31
- option_reader :fixtures_path_prefix, :contract => { None => String }, :default => ?%
31
+ option_reader :fixtures_path_prefix, :contract => { None => String }, :default => ?%
32
32
  end
33
33
 
34
34
  option_accessor :exit_timeout, :contract => { Num => Num }, :default => 15
@@ -64,6 +64,8 @@ module Aruba
64
64
 
65
65
  option_accessor :physical_block_size, :contract => { Aruba::Contracts::IsPowerOfTwo => Aruba::Contracts::IsPowerOfTwo }, :default => 512
66
66
  option_accessor :console_history_file, :contract => { String => String }, :default => '~/.aruba_history'
67
+
68
+ option_accessor :activate_announcer_on_command_failure, :contract => { ArrayOf[Symbol] => ArrayOf[Symbol] }, :default => []
67
69
  end
68
70
  end
69
71
 
@@ -1,6 +1,7 @@
1
1
  if Aruba::VERSION < '1.0.0'
2
2
  require 'aruba/cucumber/core'
3
3
  end
4
+ require 'aruba/generators/script_file'
4
5
 
5
6
  When(/^I run "(.*)"$/)do |cmd|
6
7
  warn(%{\e[35m The /^I run "(.*)"$/ step definition is deprecated. Please use the `backticks` version\e[0m})
@@ -28,6 +29,17 @@ When(/^I successfully run `(.*?)`(?: for up to (\d+) seconds)?$/)do |cmd, secs|
28
29
  run_simple(cmd, :fail_on_error => true, :exit_timeout => secs && secs.to_i)
29
30
  end
30
31
 
32
+ When(/^I run the following (?:commands|script)(?: (?:with|in) `([^`]+)`)?:$/) do |shell, commands|
33
+ prepend_environment_variable('PATH', expand_path('bin') + ':')
34
+
35
+ Aruba.platform.mkdir(expand_path('bin'))
36
+ shell ||= Aruba.platform.default_shell
37
+
38
+ Aruba::ScriptFile.new(:interpreter => shell, :content => commands,
39
+ :path => expand_path('bin/myscript')).call
40
+ step 'I run `myscript`'
41
+ end
42
+
31
43
  When(/^I run "([^"]*)" interactively$/) do |cmd|
32
44
  Aruba.platform.deprecated(%{\e[35m The /^I run "([^"]*)" interactively$/ step definition is deprecated. Please use the `backticks` version\e[0m})
33
45
 
@@ -33,6 +33,14 @@ Before('@announce-command') do
33
33
  aruba.announcer.activate :command
34
34
  end
35
35
 
36
+ Before('@announce-command-content') do
37
+ aruba.announcer.activate :command_content
38
+ end
39
+
40
+ Before('@announce-command-filesystem-status') do
41
+ aruba.announcer.activate :command_filesystem_status
42
+ end
43
+
36
44
  Before('@announce-cmd') do
37
45
  Aruba.platform.deprecated 'The use of "@announce-cmd"-hook is deprecated. Please use "@announce-command"'
38
46
 
@@ -112,6 +120,8 @@ Before('@announce') do
112
120
  aruba.announcer.activate :stop_signal
113
121
  aruba.announcer.activate :timeout
114
122
  aruba.announcer.activate :wait_time
123
+ aruba.announcer.activate :command_content
124
+ aruba.announcer.activate :command_filesystem_status
115
125
  end
116
126
 
117
127
  Before('@debug') do
data/lib/aruba/errors.rb CHANGED
@@ -28,4 +28,10 @@ module Aruba
28
28
  # Raised if command was already started, otherwise aruba forgets about the
29
29
  # previous pid and you've got hidden commands run
30
30
  class CommandAlreadyStartedError < Error; end
31
+
32
+ # Raised if an event name cannot be resolved
33
+ class EventNameResolveError < StandardError; end
34
+
35
+ # Raised if given object is not an event
36
+ class NoEventError < StandardError; end
31
37
  end
@@ -0,0 +1,59 @@
1
+ require 'aruba/event_bus/name_resolver'
2
+ require 'aruba/errors'
3
+
4
+ module Aruba
5
+ # Event bus
6
+ #
7
+ # Implements and in-process pub-sub events broadcaster allowing multiple observers
8
+ # to subscribe to different events that fire as your tests are executed.
9
+ #
10
+ class EventBus
11
+ # Create EventBus
12
+ #
13
+ # @param [#transform] resolver
14
+ # A resolver which transforms Symbol, String, Class into an event Class.
15
+ def initialize(resolver)
16
+ @resolver = resolver
17
+ @handlers = Hash.new { |h, k| h[k] = [] }
18
+ end
19
+
20
+ # Register for an event
21
+ #
22
+ # @param [String, Symbol, Class, Array] event_ids
23
+ # If Array, register multiple events witht the same handler. If String,
24
+ # Symbol, Class register handler for given event.
25
+ #
26
+ # @param [#call] handler_object
27
+ # The handler object, needs to have method `#call`. Either
28
+ # `handler_object` or `block` can be defined. The handler object gets the
29
+ # event passed to `#call`.
30
+ #
31
+ # @yield
32
+ # Handler block which gets the event passed as parameter.
33
+ def register(event_ids, handler_object = nil, &handler_proc)
34
+ handler = handler_proc || handler_object
35
+
36
+ fail ArgumentError, 'Please pass either an object#call or a handler block' if handler.nil? || !handler.respond_to?(:call)
37
+
38
+ Array(event_ids).flatten.each do |id|
39
+ @handlers[
40
+ @resolver.transform(id).to_s
41
+ ] << handler
42
+ end
43
+
44
+ nil
45
+ end
46
+
47
+ # Broadcast an event
48
+ #
49
+ # @param [Object] event
50
+ # An object of registered event class. This object is passed to the event
51
+ # handler.
52
+ #
53
+ def notify(event)
54
+ fail NoEventError, 'Please pass an event object, not a class' if event.is_a?(Class)
55
+
56
+ @handlers[event.class.to_s].each { |handler| handler.call(event) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,168 @@
1
+ require 'aruba/errors'
2
+
3
+ # Event notification library
4
+ module Aruba
5
+ # EventBus
6
+ class EventBus
7
+ # Resolve name to Event name
8
+ class NameResolver
9
+ # @private
10
+ # Helpers for Resolvers
11
+ module ResolveHelpers
12
+ def camel_case(underscored_name)
13
+ if RUBY_VERSION < '1.9.3'
14
+ underscored_name.to_s.split('_').map { |word| word.upcase.chars.to_a[0] + word.chars.to_a[1..-1].join }.join
15
+ else
16
+ underscored_name.to_s.split('_').map { |word| word.upcase[0] + word[1..-1] }.join
17
+ end
18
+ end
19
+
20
+ # Thanks ActiveSupport
21
+ # (Only needed to support Ruby 1.9.3 and JRuby)
22
+ # rubocop:disable Metrics/CyclomaticComplexity
23
+ # rubocop:disable Metrics/MethodLength
24
+ def constantize(camel_cased_word)
25
+ names = camel_cased_word.split('::')
26
+
27
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
28
+ Object.const_get(camel_cased_word) if names.empty?
29
+
30
+ # Remove the first blank element in case of '::ClassName' notation.
31
+ names.shift if names.size > 1 && names.first.empty?
32
+
33
+ names.inject(Object) do |constant, name|
34
+ if constant == Object
35
+ constant.const_get(name)
36
+ else
37
+ candidate = constant.const_get(name)
38
+
39
+ if RUBY_VERSION < '1.9.3'
40
+ next candidate if constant.const_defined?(name)
41
+ else
42
+ next candidate if constant.const_defined?(name, false)
43
+ end
44
+
45
+ next candidate unless Object.const_defined?(name)
46
+
47
+ # Go down the ancestors to check if it is owned directly. The check
48
+ # stops when we reach Object or the end of ancestors tree.
49
+ # rubocop:disable Style/EachWithObject
50
+ constant = constant.ancestors.inject do |const, ancestor|
51
+ break const if ancestor == Object
52
+ break ancestor if ancestor.const_defined?(name, false)
53
+ const
54
+ end
55
+ # rubocop:enable Style/EachWithObject
56
+
57
+ # owner is in Object, so raise
58
+ constant.const_get(name, false)
59
+ end
60
+ end
61
+ end
62
+ # rubocop:enable Metrics/CyclomaticComplexity
63
+ # rubocop:enable Metrics/MethodLength
64
+ end
65
+
66
+ # @private
67
+ # Convert a class in to an event class
68
+ class ClassResolver
69
+ class << self
70
+ def match?(event_id)
71
+ event_id.is_a? Class
72
+ end
73
+
74
+ # Which types are supported
75
+ def supports
76
+ [Class]
77
+ end
78
+ end
79
+
80
+ def transform(_, event_id)
81
+ event_id
82
+ end
83
+ end
84
+
85
+ # @private
86
+ # Convert a string in to an event class
87
+ class StringResolver
88
+ include ResolveHelpers
89
+
90
+ class << self
91
+ def match?(event_id)
92
+ event_id.is_a? String
93
+ end
94
+
95
+ # Which types are supported
96
+ def supports
97
+ [String]
98
+ end
99
+ end
100
+
101
+ def transform(_, event_id)
102
+ constantize(event_id)
103
+ end
104
+ end
105
+
106
+ # @private
107
+ # Convert a symbol in to an event class
108
+ class SymbolResolver
109
+ include ResolveHelpers
110
+
111
+ class << self
112
+ def match?(event_id)
113
+ event_id.is_a? Symbol
114
+ end
115
+
116
+ # Which types are supported
117
+ def supports
118
+ [Symbol]
119
+ end
120
+ end
121
+
122
+ def transform(default_namespace, event_id)
123
+ constantize("#{default_namespace}::#{camel_case(event_id)}")
124
+ end
125
+ end
126
+
127
+ # @private
128
+ # Default failing resolver
129
+ #
130
+ # This comes into play if the user passes an invalid event type
131
+ class FailingResolver
132
+ class << self
133
+ def match?(event_id)
134
+ fail ArgumentError, %(Input type "#{event_id.class}" of event_id "#{event_id}" is invalid)
135
+ end
136
+
137
+ def supports
138
+ []
139
+ end
140
+ end
141
+ end
142
+
143
+ protected
144
+
145
+ attr_reader :resolvers, :default_namespace
146
+
147
+ public
148
+
149
+ def initialize(default_namespace)
150
+ @default_namespace = default_namespace
151
+
152
+ @resolvers = []
153
+ @resolvers << ClassResolver
154
+ @resolvers << StringResolver
155
+ @resolvers << SymbolResolver
156
+ @resolvers << FailingResolver
157
+ end
158
+
159
+ def transform(event_id)
160
+ resolvers.find { |r| r.match? event_id }.new.transform(default_namespace, event_id)
161
+ rescue => e
162
+ # rubocop:disable Metrics/LineLength
163
+ raise EventNameResolveError, %(Transforming "#{event_id}" into an event class failed. Supported types are: #{@resolvers.map(&:supports).flatten.join(', ')}. #{e.message}.\n\n#{e.backtrace.join("\n")})
164
+ # rubocop:enable Metrics/LineLength
165
+ end
166
+ end
167
+ end
168
+ end