From bc0f98be6d68f7ef6119a5a0f6849601b58d5faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20G=C3=BCnnewig?= Date: Sat, 28 Nov 2015 09:18:32 +0100 Subject: [PATCH] Make sure that environment variables set by external libraries are taken into account --- ...re => delete_environment_variable.feature} | 2 +- .../set_environment_variable.feature | 68 +++++++++ .../command_runtime_environment.feature | 129 ++++++++++++++++ lib/aruba/api/command.rb | 5 +- lib/aruba/api/core.rb | 2 + lib/aruba/config.rb | 2 +- lib/aruba/cucumber/hooks.rb | 3 - .../platforms/unix_environment_variables.rb | 143 ++++++++++++++---- lib/aruba/platforms/unix_platform.rb | 2 +- .../windows_environment_variables.rb | 6 +- lib/aruba/platforms/windows_platform.rb | 2 +- lib/aruba/rspec.rb | 5 +- lib/aruba/runtime.rb | 9 +- 13 files changed, 327 insertions(+), 51 deletions(-) rename features/api/environment/{remove_environment_variable.feature => delete_environment_variable.feature} (96%) create mode 100644 features/configuration/command_runtime_environment.feature diff --git a/features/api/environment/remove_environment_variable.feature b/features/api/environment/delete_environment_variable.feature similarity index 96% rename from features/api/environment/remove_environment_variable.feature rename to features/api/environment/delete_environment_variable.feature index 752a208c6..657c43d92 100644 --- a/features/api/environment/remove_environment_variable.feature +++ b/features/api/environment/delete_environment_variable.feature @@ -1,4 +1,4 @@ -Feature: Remove existing environment variable via API-method +Feature: Delete existing environment variable via API-method It is quite handy to modify the environment of a process. To make this possible, `aruba` provides several methods. One of these is diff --git a/features/api/environment/set_environment_variable.feature b/features/api/environment/set_environment_variable.feature index ca50daa7f..a09eff021 100644 --- a/features/api/environment/set_environment_variable.feature +++ b/features/api/environment/set_environment_variable.feature @@ -69,6 +69,46 @@ Feature: Set environment variable via API-method When I run `rspec` Then the specs should all pass + Scenario: Set variable via ENV + + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { ENV['REALLY_LONG_LONG_VARIABLE'] = '2' } + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'REALLY_LONG_LONG_VARIABLE=2' } + end + """ + When I run `rspec` + Then the specs should all pass + + Scenario: Existing variable set in before block in RSpec + + Setting environment variables with `#set_environment_variable('VAR', 'value')` takes + precedence before setting variables with `ENV['VAR'] = 'value'`. + + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { set_environment_variable 'REALLY_LONG_LONG_VARIABLE', '1' } + before(:each) { ENV['REALLY_LONG_LONG_VARIABLE'] = '2' } + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'REALLY_LONG_LONG_VARIABLE=1' } + end + """ + When I run `rspec` + Then the specs should all pass + Scenario: Run some ruby code in code with previously set environment The `#with_environment`-block makes the change environment temporary @@ -304,3 +344,31 @@ Feature: Set environment variable via API-method """ When I run `rspec` Then the specs should all pass + + Scenario: External ruby file / ruby gem modifying ENV + + There are some Rubygems around which need to modify ENV['NODE_PATH'] like + [`ruby-stylus`](https://github.com/forgecrafted/ruby-stylus/blob/e7293362dc8cbf550f7c317d721ba6b9087e8833/lib/stylus.rb#L168). + This is supported by aruba as well. + + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) do + require_relative File.expand_path('../../lib/my_library.rb', __FILE__) + end + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'LONG_LONG_VARIABLE=1' } + end + """ + And a file named "lib/my_library.rb" with: + """ + ENV['LONG_LONG_VARIABLE'] = '1' + """ + When I run `rspec` + Then the specs should all pass diff --git a/features/configuration/command_runtime_environment.feature b/features/configuration/command_runtime_environment.feature new file mode 100644 index 000000000..fc90abd4b --- /dev/null +++ b/features/configuration/command_runtime_environment.feature @@ -0,0 +1,129 @@ +Feature: Define default process environment + Say you want to have a default set of environment variables, then use this + code. + + ~~~ruby + Aruba.configure do |config| + config.command_runtime_environment = { 'LONG_LONG_VARIABLE' => 'x' } + end + ~~~ + + This can be changed via `#set_environment_variable`, + `#append_environment_variable`, `#delete_environment_variable` or + `#prepend_environment_variable`. + + Background: + Given I use the fixture "cli-app" + + Scenario: Overwrite existing variable with new default value + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + ENV['LONG_LONG_VARIABLE'] = 'y' + + Aruba.configure do |config| + config.command_runtime_environment = { 'LONG_LONG_VARIABLE' => 'x' } + end + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'LONG_LONG_VARIABLE=x' } + end + """ + When I run `rspec` + Then the specs should all pass + + Scenario: Overwrite default value for variable + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + ENV['LONG_LONG_VARIABLE'] = 'y' + + Aruba.configure do |config| + config.command_runtime_environment = { 'LONG_LONG_VARIABLE' => 'x' } + end + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { set_environment_variable 'LONG_LONG_VARIABLE', 'z' } + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'LONG_LONG_VARIABLE=z' } + end + """ + When I run `rspec` + Then the specs should all pass + + Scenario: Append value to default value + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + ENV['LONG_LONG_VARIABLE'] = 'y' + + Aruba.configure do |config| + config.command_runtime_environment = { 'LONG_LONG_VARIABLE' => 'x' } + end + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { append_environment_variable 'LONG_LONG_VARIABLE', 'z' } + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'LONG_LONG_VARIABLE=xz' } + end + """ + When I run `rspec` + Then the specs should all pass + + Scenario: Prepend value + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + ENV['LONG_LONG_VARIABLE'] = 'y' + + Aruba.configure do |config| + config.command_runtime_environment = { 'LONG_LONG_VARIABLE' => 'x' } + end + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { prepend_environment_variable 'LONG_LONG_VARIABLE', 'z' } + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).to include 'LONG_LONG_VARIABLE=zx' } + end + """ + When I run `rspec` + Then the specs should all pass + + Scenario: Remove variable from default set of variables + Given a file named "spec/environment_spec.rb" with: + """ruby + require 'spec_helper' + + ENV['LONG_LONG_VARIABLE'] = 'y' + + Aruba.configure do |config| + config.command_runtime_environment = { 'LONG_LONG_VARIABLE' => 'x' } + end + + RSpec.describe 'Environment command', :type => :aruba do + before(:each) { delete_environment_variable 'LONG_LONG_VARIABLE' } + + before(:each) { run('env') } + before(:each) { stop_all_commands } + + it { expect(last_command_started.output).not_to include 'LONG_LONG_VARIABLE' } + end + """ + When I run `rspec` + Then the specs should all pass diff --git a/lib/aruba/api/command.rb b/lib/aruba/api/command.rb index 40aaa79e4..9320b6892 100644 --- a/lib/aruba/api/command.rb +++ b/lib/aruba/api/command.rb @@ -164,7 +164,7 @@ def run(*args) @commands ||= [] @commands << cmd - environment = aruba.environment.to_h + environment = aruba.environment working_directory = expand_path('.') event_bus = aruba.event_bus @@ -202,7 +202,7 @@ def run(*args) :exit_timeout => exit_timeout, :io_wait_timeout => io_wait_timeout, :working_directory => working_directory, - :environment => environment, + :environment => environment.to_hash, :main_class => main_class, :stop_signal => stop_signal, :startup_wait_time => startup_wait_time, @@ -217,6 +217,7 @@ def run(*args) end aruba.config.before(:command, self, command) + command.start aruba.announcer.announce(:stop_signal, command.pid, stop_signal) if stop_signal diff --git a/lib/aruba/api/core.rb b/lib/aruba/api/core.rb index da74f139b..a24c60fc1 100644 --- a/lib/aruba/api/core.rb +++ b/lib/aruba/api/core.rb @@ -179,8 +179,10 @@ def expand_path(file_name, dir_string = nil) def with_environment(env = {}, &block) old_aruba_env = aruba.environment.to_h + # make sure the old environment is really restored in "ENV" Aruba.platform.with_environment aruba.environment.update(env).to_h, &block ensure + # make sure the old environment is really restored in "aruba.environment" aruba.environment.clear aruba.environment.update old_aruba_env end diff --git a/lib/aruba/config.rb b/lib/aruba/config.rb index caef3e5c1..493fc075c 100644 --- a/lib/aruba/config.rb +++ b/lib/aruba/config.rb @@ -36,7 +36,7 @@ class Configuration < BasicConfiguration option_accessor :io_wait_timeout, :contract => { Num => Num }, :default => 0.1 option_accessor :startup_wait_time, :contract => { Num => Num }, :default => 0 option_accessor :fixtures_directories, :contract => { Array => ArrayOf[String] }, :default => %w(features/fixtures spec/fixtures test/fixtures fixtures) - option_accessor :command_runtime_environment, :contract => { Hash => Hash }, :default => ENV.to_hash.dup + option_accessor :command_runtime_environment, :contract => { Hash => Hash }, :default => ENV.to_hash # rubocop:disable Metrics/LineLength option_accessor(:command_search_paths, :contract => { ArrayOf[String] => ArrayOf[String] }) { |config| [File.join(config.root_directory.value, 'bin')] } # rubocop:enable Metrics/LineLength diff --git a/lib/aruba/cucumber/hooks.rb b/lib/aruba/cucumber/hooks.rb index 8be15d103..7fd739e8c 100644 --- a/lib/aruba/cucumber/hooks.rb +++ b/lib/aruba/cucumber/hooks.rb @@ -10,9 +10,6 @@ end Before do - # this is ENV by default ... - aruba.environment.update aruba.config.command_runtime_environment - # ... so every change needs to be done later prepend_environment_variable 'PATH', aruba.config.command_search_paths.join(':') + ':' set_environment_variable 'HOME', aruba.config.home_directory diff --git a/lib/aruba/platforms/unix_environment_variables.rb b/lib/aruba/platforms/unix_environment_variables.rb index e619af251..4d2047142 100644 --- a/lib/aruba/platforms/unix_environment_variables.rb +++ b/lib/aruba/platforms/unix_environment_variables.rb @@ -4,17 +4,69 @@ module Aruba module Platforms # Abstract environment variables class UnixEnvironmentVariables + # Update environment + class UpdateAction + attr_reader :other_env, :block + + def initialize(other_env, &block) + @other_env = other_env + + if RUBY_VERSION <= '1.9.3' + # rubocop:disable Style/EachWithObject + @other_env = @other_env.to_hash.inject({}) { |a, (k, v)| a[k] = v.to_s; a } + # rubocop:enable Style/EachWithObject + else + @other_env = @other_env.to_h.each_with_object({}) { |(k, v), a| a[k] = v.to_s } + end + + if block_given? + @block = block + else + @block = nil + end + end + + def call(env) + if block + env.update(other_env, &block) + else + env.update(other_env) + end + end + end + + # Remove variables from environment + class RemoveAction + attr_reader :variables + + def initialize(variables) + @variables = Array(variables) + end + + def call(env) + variables.each { |v| env.delete v } + + env + end + end + # We need to use this, because `nil` is a valid value as default UNDEFINED = Object.new.freeze private - attr_reader :env + attr_reader :actions, :env public - def initialize(env = ENV.to_hash) - @env = Marshal.load(Marshal.dump(env)) + def initialize(env = ENV) + @actions = [] + + if RUBY_VERSION < '2.0' + @env = env.to_hash + else + @env = env.to_h + end end # Update environment with other en @@ -24,18 +76,10 @@ def initialize(env = ENV.to_hash) # # @yield # Pass block to env - def update(other_env, &block) - if RUBY_VERSION <= '1.9.3' - # rubocop:disable Style/EachWithObject - other_env = other_env.to_hash.inject({}) { |a, (k, v)| a[k] = v.to_s; a } - # rubocop:enable Style/EachWithObject - else - other_env = other_env.to_h.each_with_object({}) { |(k, v), a| a[k] = v.to_s } - end + def update(other_env) + actions << UpdateAction.new(other_env) - env.update(other_env, &block) - - self + UnixEnvironmentVariables.new(to_h) end # Fetch variable from environment @@ -47,9 +91,9 @@ def update(other_env, &block) # The default value used, if the variable is not defined def fetch(name, default = UNDEFINED) if default == UNDEFINED - env.fetch name.to_s + to_h.fetch name.to_s else - env.fetch name.to_s, default + to_h.fetch name.to_s, default end end @@ -58,7 +102,7 @@ def fetch(name, default = UNDEFINED) # @param [#to_s] name # The name of the variable def key?(name) - env.key? name.to_s + to_h.key? name.to_s end # Get value of variable @@ -66,7 +110,7 @@ def key?(name) # @param [#to_s] name # The name of the variable def [](name) - env[name.to_s] + to_h[name.to_s] end # Set value of variable @@ -77,9 +121,11 @@ def [](name) # @param [#to_s] value # The value of the variable def []=(name, value) - env[name.to_s] = value.to_s + value = value.to_s + + actions << UpdateAction.new(name.to_s => value) - self + value end # Append value to variable @@ -90,10 +136,12 @@ def []=(name, value) # @param [#to_s] value # The value of the variable def append(name, value) - name = name.to_s - env[name] = env[name].to_s + value.to_s + name = name.to_s + value = self[name].to_s + value.to_s + + actions << UpdateAction.new(name => value ) - self + value end # Prepend value to variable @@ -104,10 +152,12 @@ def append(name, value) # @param [#to_s] value # The value of the variable def prepend(name, value) - name = name.to_s - env[name] = value.to_s + env[name].to_s + name = name.to_s + value = value.to_s + self[name].to_s - self + actions << UpdateAction.new(name => value) + + value end # Delete variable @@ -115,9 +165,24 @@ def prepend(name, value) # @param [#to_s] name # The name of the variable def delete(name) - env.delete name.to_s + # Rescue value, before it is deleted + value = to_h[name.to_s] + + actions << RemoveAction.new(name.to_s) + + value + end + + # Pass on checks + def method_missing(name, *args, &block) + super unless to_h.respond_to? name + + to_h.send name, *args, &block + end - self + # Check for respond_to + def respond_to_missing?(name, _private) + to_h.respond_to? name end # Convert to hash @@ -126,17 +191,31 @@ def delete(name) # A new hash from environment def to_h if RUBY_VERSION < '2.0' - Marshal.load(Marshal.dump(env.to_hash)) + Marshal.load(Marshal.dump(prepared_environment.to_hash)) else - Marshal.load(Marshal.dump(env.to_h)) + Marshal.load(Marshal.dump(prepared_environment.to_h)) end end # Reset environment def clear - env.clear + value = to_h - self + actions.clear + + value + end + + private + + def prepared_environment + if RUBY_VERSION <= '1.9.3' + # rubocop:disable Style/EachWithObject + actions.inject(ENV.to_hash.merge(env)) { |a, e| e.call(a) } + # rubocop:enable Style/EachWithObject + else + actions.each_with_object(ENV.to_hash.merge(env)) { |e, a| a = e.call(a) } + end end end end diff --git a/lib/aruba/platforms/unix_platform.rb b/lib/aruba/platforms/unix_platform.rb index 48d94fb00..b19936031 100644 --- a/lib/aruba/platforms/unix_platform.rb +++ b/lib/aruba/platforms/unix_platform.rb @@ -34,7 +34,7 @@ def self.match? end def environment_variables - UnixEnvironmentVariables.new + UnixEnvironmentVariables end def command_string diff --git a/lib/aruba/platforms/windows_environment_variables.rb b/lib/aruba/platforms/windows_environment_variables.rb index c2247c7fe..2855cf309 100644 --- a/lib/aruba/platforms/windows_environment_variables.rb +++ b/lib/aruba/platforms/windows_environment_variables.rb @@ -34,14 +34,14 @@ module Platforms # will always work. class WindowsEnvironmentVariables < UnixEnvironmentVariables def initialize(env = ENV.to_hash) - @env = Marshal.load(Marshal.dump(env)) + @actions = [] if RUBY_VERSION <= '1.9.3' # rubocop:disable Style/EachWithObject - @env = @env.inject({}) { |a, (k,v)| a[k.to_s.upcase] = v; a } + @env = env.inject({}) { |a, (k,v)| a[k.to_s.upcase] = v; a } # rubocop:enable Style/EachWithObject else - @env = @env.each_with_object({}) { |(k,v), a| a[k.to_s.upcase] = v } + @env = env.each_with_object({}) { |(k,v), a| a[k.to_s.upcase] = v } end end diff --git a/lib/aruba/platforms/windows_platform.rb b/lib/aruba/platforms/windows_platform.rb index cf8bc8c1e..f40fbd3af 100644 --- a/lib/aruba/platforms/windows_platform.rb +++ b/lib/aruba/platforms/windows_platform.rb @@ -30,7 +30,7 @@ def command_string # @see UnixPlatform#environment_variables def environment_variables - WindowsEnvironmentVariables.new + WindowsEnvironmentVariables end # @see UnixPlatform#which diff --git a/lib/aruba/rspec.rb b/lib/aruba/rspec.rb index 6b13352bc..2c24adc95 100644 --- a/lib/aruba/rspec.rb +++ b/lib/aruba/rspec.rb @@ -91,14 +91,13 @@ config.before :each do next unless self.class.include? Aruba::Api - aruba.environment.update aruba.config.command_runtime_environment - aruba.environment.prepend 'PATH', aruba.config.command_search_paths.join(':') + ':' + prepend_environment_variable 'PATH', aruba.config.command_search_paths.join(':') + ':' end # Use configured home directory as HOME config.before :each do |example| next unless self.class.include? Aruba::Api - aruba.environment['HOME'] = aruba.config.home_directory + set_environment_variable 'HOME', aruba.config.home_directory end end diff --git a/lib/aruba/runtime.rb b/lib/aruba/runtime.rb index e87216c3e..0b0294fef 100644 --- a/lib/aruba/runtime.rb +++ b/lib/aruba/runtime.rb @@ -40,14 +40,15 @@ class Runtime attr_accessor :config, :environment, :logger, :command_monitor, :announcer, :event_bus def initialize(opts = {}) - @environment = opts.fetch(:environment, Aruba.platform.environment_variables) - @event_bus = ::Event::Bus.new(::Event::NameResolver.new(Aruba::Events)) + @event_bus = ::Event::Bus.new(::Event::NameResolver.new(Aruba::Events)) @announcer = opts.fetch(:announcer, Aruba.platform.announcer.new) - - @config = opts.fetch(:config, ConfigWrapper.new(Aruba.config.make_copy, @event_bus)) + @config = opts.fetch(:config, ConfigWrapper.new(Aruba.config.make_copy, @event_bus)) + @environment = opts.fetch(:environment, Aruba.platform.environment_variables.new) @current_directory = ArubaPath.new(@config.working_directory) @root_directory = ArubaPath.new(@config.root_directory) + @environment.update(@config.command_runtime_environment) + if Aruba::VERSION < '1' @command_monitor = opts.fetch(:command_monitor, Aruba.platform.command_monitor.new(:announcer => @announcer)) else