diff --git a/.nav b/.nav new file mode 100644 index 000000000..ae1a4d1a9 --- /dev/null +++ b/.nav @@ -0,0 +1,12 @@ +README.md +# before_cmd_hooks.feature +# command_environment_variables.feature +# configuration-rspec.feature +# custom_ruby_process.feature +# exit_statuses.feature +# file_system_commands.feature +# flushing.feature +# interactive.feature +# no_clobber.feature +# output.feature +# utf-8.feature diff --git a/.rspec b/.rspec index 4e1e0d2f7..09127182c 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,2 @@ --color +--order random diff --git a/.rubocop.yml b/.rubocop.yml index 054fe9eb3..9fe843981 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,6 +14,12 @@ Metrics/ModuleLength: Metrics/AbcSize: Enabled: false +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + Lint/AmbiguousRegexpLiteral: Enabled: false diff --git a/.simplecov b/.simplecov index 2e4653de5..718a406e2 100644 --- a/.simplecov +++ b/.simplecov @@ -4,6 +4,8 @@ SimpleCov.start unless ENV.key? 'ARUBA_NO_COVERAGE' SimpleCov.configure do # ignore this file add_filter '.simplecov' + add_filter 'spec' + add_filter 'features' # Rake tasks aren't tested with rspec add_filter 'Rakefile' diff --git a/.travis.yml b/.travis.yml index 10ef97a7c..b9f3ae346 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,11 +16,6 @@ rvm: matrix: allow_failures: - rvm: rbx -notifications: - email: - - cukes-devs@googlegroups.com - irc: - - irc.freenode.org#cucumber env: global: secure: l8uznA5K4K9mZ1krmP3lTMD8WcJ32qGxFOR3jubKHcOBSLB4xSzU2aIqjyJdO+rLzebkwamhJc8pGSIWOUDQYvFiX7splK+uEkbBJ5huAhXtLF4Qgl86bCWbEXYzN7rvn0DQfpJAovyFMNRMnfo70XhwqWzFsaYa7Z0YbqYsJE4= diff --git a/Gemfile b/Gemfile index e0799cad4..5e4704288 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,8 @@ group :development, :test do gem 'rubocop', '~> 0.31.0' gem 'license_finder', '~> 2.0.4' + + gem 'relish' end platforms :rbx do diff --git a/README.md b/README.md index 73b29a3b9..093715e3c 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ You can hook into Aruba's lifecycle just before it runs a command: ```ruby Aruba.configure do |config| - config.before_cmd do |cmd| + config.before :cmd do |cmd| puts "About to run '#{cmd}'" end end @@ -216,7 +216,7 @@ or setting a hook like this example: ```ruby Aruba.configure do |config| - config.before_cmd do |cmd| + config.before :cmd do |cmd| set_env('JRUBY_OPTS', "-X-C #{ENV['JRUBY_OPTS']}") # disable JIT since these processes are so short lived set_env('JAVA_OPTS', "-d32 #{ENV['JAVA_OPTS']}") # force jRuby to use client JVM for faster startup times end diff --git a/aruba.gemspec b/aruba.gemspec index 08005323b..c81be6f07 100644 --- a/aruba.gemspec +++ b/aruba.gemspec @@ -13,6 +13,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'cucumber', '>= 1.1.1' s.add_runtime_dependency 'childprocess', '>= 0.3.6' s.add_runtime_dependency 'rspec-expectations', '>= 2.7.0' + s.add_runtime_dependency 'contracts', '~> 0.9' s.add_development_dependency 'bundler', '~> 1.10.2' diff --git a/cucumber.yml b/cucumber.yml index 8136b1024..689225452 100644 --- a/cucumber.yml +++ b/cucumber.yml @@ -1,7 +1,7 @@ <% java_version = (RUBY_DESCRIPTION.match(/.*?on.*?(1\.[\d]\..*? )/))[1] if defined?(JRUBY_VERSION) -std_opts = "--color --exclude features/fixtures" +std_opts = "--color --exclude features/fixtures --require features" java_default_opts = "--tags ~@wip-jruby-java-1.6" if defined?(JRUBY_VERSION) && (java_version < '1.7.0') java_wip_opts = "--tags @wip-jruby-java-1.6:3" if defined?(JRUBY_VERSION) && (java_version < '1.7.0') %> diff --git a/features/commands/exit_statuses.feature b/features/commands/exit_statuses.feature new file mode 100644 index 000000000..3c3ed0c56 --- /dev/null +++ b/features/commands/exit_statuses.feature @@ -0,0 +1,136 @@ +Feature: Check exit status of commands + + Use the `the exit status should be \d`-step to check the exit status of the + last command which was executed. + + Background: + Given I use a fixture named "cli-app" + + Scenario: Test for exit status of 0 + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + exit 0 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Exit status + Scenario: Run command + When I run `cli` + Then the exit status should be 0 + """ + When I run `cucumber` + Then the features should all pass + + Scenario: Test for exit status 1 + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + exit 1 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + When I run `cli` + Then the exit status should be 1 + """ + When I run `cucumber` + Then the features should all pass + + Scenario: Test for non-zero exit status + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + exit 1 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + When I run `cli` + Then the exit status should not be 0 + """ + When I run `cucumber` + Then the features should all pass + + Scenario: Successfully run something + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + exit 0 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + When I successfully run `cli` + """ + When I run `cucumber` + Then the features should all pass + + Scenario: Fail to run something successfully + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + exit 1 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + When I successfully run `cli` + """ + When I run `cucumber` + Then the features should not all pass + + Scenario: Overwrite the default exit timeout via step + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + sleep 1 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + Given the default aruba timeout is 2 seconds + When I successfully run `cli` + """ + When I run `cucumber` + Then the features should all pass + + Scenario: Successfully run something longer then the default time + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + sleep 1 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + Given the default aruba timeout is 0 seconds + When I successfully run `cli` for up to 2 seconds + """ + When I run `cucumber` + Then the features should all pass + + Scenario: Unsuccessfully run something that takes too long + Given a file named "bin/cli" with: + """ + #!/usr/bin/env sh + sleep 2 + """ + And a file named "features/exit_status.feature" with: + """ + Feature: Failing program + Scenario: Run command + Given the default aruba timeout is 0 seconds + When I successfully run `cli` for up to 1 seconds + """ + When I run `cucumber` + Then the features should not all pass with: + """ + process still alive after 1 seconds + """ diff --git a/features/configuration/current_directory.feature b/features/configuration/current_directory.feature new file mode 100644 index 000000000..3cd0b6d83 --- /dev/null +++ b/features/configuration/current_directory.feature @@ -0,0 +1,32 @@ +Feature: Configure current directory of aruba + + As a developer + I want to configure the current directory of aruba + In order to have a test directory for each used spec runner - e.g. cucumber or rspec + + Background: + Given I use the fixture "cli-app" + And the default feature-test + + Scenario: Default value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + puts %(The default value is "%w(#{config.current_directory.join(" ")})") + end + """ + When I successfully run `cucumber` + Then the output should contain: + """ + The default value is "%w(tmp aruba)" + """ + + Scenario: Modify value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + config.current_directory = %w(tmp cucumber) + end + """ + When I successfully run `cucumber` + Then a directory named "tmp/cucumber" should exist diff --git a/features/configuration/exit_timeout.feature b/features/configuration/exit_timeout.feature new file mode 100644 index 000000000..fdfae465b --- /dev/null +++ b/features/configuration/exit_timeout.feature @@ -0,0 +1,49 @@ +Feature: Configure timeout for command execution + + As a developer + I want to configure the timeout when executing a command + In order to support some longer running commands + + Background: + Given I use the fixture "cli-app" + And the default feature-test + + Scenario: Default value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + puts %(The default value is "#{config.exit_timeout}") + end + """ + When I successfully run `cucumber` + Then the output should contain: + """ + The default value is "3" + """ + + Scenario: Modify value + Given a file named "bin/cli" with: + """ + sleep 1 + """ + And a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + config.exit_timeout = 2 + end + """ + Then I successfully run `cucumber` + + Scenario: Fails if takes longer + Given a file named "bin/cli" with: + """ + sleep 5 + """ + And a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + config.exit_timeout = 1 + end + """ + Then I run `cucumber` + And the exit status should be 1 diff --git a/features/configuration/fixtures_directories.feature b/features/configuration/fixtures_directories.feature new file mode 100644 index 000000000..097ca49d6 --- /dev/null +++ b/features/configuration/fixtures_directories.feature @@ -0,0 +1,31 @@ +Feature: Configure directory where to look for fixtures + + As a developer + I want to configure the directory where aruba looks for fixtures + In order to use them in my tests + + Background: + Given I use the fixture "cli-app" + And the default feature-test + + Scenario: Default value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + puts %(The default value is "%w(#{config.fixtures_directories.join(" ")})") + end + """ + When I successfully run `cucumber` + Then the output should contain: + """ + The default value is "%w(features/fixtures spec/fixtures test/fixtures)" + """ + + Scenario: Modify value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + config.fixtures_directories = %w(spec/fixtures) + end + """ + When I successfully run `cucumber` diff --git a/features/configuration/fixtures_directory.feature b/features/configuration/fixtures_directory.feature new file mode 100644 index 000000000..c28fb8cfe --- /dev/null +++ b/features/configuration/fixtures_directory.feature @@ -0,0 +1,5 @@ +Feature: Change fixtures directory search paths of aruba + + As a developer + I want to use a different fixtures directory search path order directory for aruba + In order to look for fixtures in features/fixtures for cucumber and in spec/fixtures for rspec first diff --git a/features/configuration/fixtures_path_prefix.feature b/features/configuration/fixtures_path_prefix.feature new file mode 100644 index 000000000..e72bb4055 --- /dev/null +++ b/features/configuration/fixtures_path_prefix.feature @@ -0,0 +1,24 @@ +Feature: Use fixtures path prefix of aruba + + As a developer + I want to use the fixtures path prefix in aruba + In some API-method for using the fixtures path + + Background: + Given I use the fixture "cli-app" + And the default feature-test + + Scenario: Default value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + puts "The prefix is \"#{config.fixtures_path_prefix}\"." + end + """ + When I successfully run `cucumber` + Then the output should contain: + """ + The prefix is "%". + """ + + diff --git a/features/configuration/io_timeout.feature b/features/configuration/io_timeout.feature new file mode 100644 index 000000000..34110140e --- /dev/null +++ b/features/configuration/io_timeout.feature @@ -0,0 +1,31 @@ +Feature: Configure timeout for io of commands + + As a developer + I want to configure the timeout waiting for io of a command + In order to support some longer running commands + + Background: + Given I use the fixture "cli-app" + And the default feature-test + + Scenario: Default value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + puts %(The default value is "#{config.io_wait_timeout}") + end + """ + When I successfully run `cucumber` + Then the output should contain: + """ + The default value is "0.1" + """ + + Scenario: Modify value + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + config.io_wait_timeout = 2 + end + """ + Then I successfully run `cucumber` diff --git a/features/configuration/root_directory.feature b/features/configuration/root_directory.feature new file mode 100644 index 000000000..9e3002f19 --- /dev/null +++ b/features/configuration/root_directory.feature @@ -0,0 +1,24 @@ +Feature: Use root directory of aruba + + As a developer + I want to use the root directory of aruba + In order to use it elsewhere + + Background: + Given I use the fixture "cli-app" + And the default feature-test + + Scenario: Default configuration + Given a file named "features/support/aruba.rb" with: + """ + Aruba.configure do |config| + puts config.root_directory + end + """ + When I successfully run `cucumber` + Then the output should contain: + """ + tmp/aruba + """ + + diff --git a/features/exit_statuses.feature b/features/exit_statuses.feature deleted file mode 100644 index a5ecaf9f5..000000000 --- a/features/exit_statuses.feature +++ /dev/null @@ -1,30 +0,0 @@ -Feature: exit statuses - - In order to specify expected exit statuses - As a developer using Cucumber - I want to use the "the exit status should be" step - - Scenario: exit status of 0 - When I run `true` - Then the exit status should be 0 - - Scenario: non-zero exit status - When I run `false` - Then the exit status should be 1 - And the exit status should not be 0 - - Scenario: Successfully run something - When I successfully run `true` - - Scenario: Successfully run something for a long time - Given the default aruba timeout is 0 seconds - When I successfully run `sleep 1` for up to 2 seconds - - Scenario: Unsuccessfully run something that takes too long - Given the default aruba timeout is 0 seconds - When I do aruba I successfully run `sleep 1` - Then aruba should fail with "process still alive after 0 seconds" - - Scenario: Unsuccessfully run something - When I do aruba I successfully run `false` - Then aruba should fail with "" diff --git a/features/fixtures/cli-app/.gitignore b/features/fixtures/cli-app/.gitignore new file mode 100644 index 000000000..0cb6eeb06 --- /dev/null +++ b/features/fixtures/cli-app/.gitignore @@ -0,0 +1,9 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/features/fixtures/cli-app/.rspec b/features/fixtures/cli-app/.rspec new file mode 100644 index 000000000..8c18f1abd --- /dev/null +++ b/features/fixtures/cli-app/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/features/fixtures/cli-app/README.md b/features/fixtures/cli-app/README.md new file mode 100644 index 000000000..343aa79ae --- /dev/null +++ b/features/fixtures/cli-app/README.md @@ -0,0 +1,34 @@ +# Simple Cli App + +This is a simple test cli app + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'cli-app' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install cli-app + +## Usage + +Place files in `lib/cli/app/`. They are loaded automatically. If you need a +specific load order, use `require` in your files. + +### CLI + +``` +cli +``` + +### Library + +You can use `script/console` to load your library. diff --git a/features/fixtures/cli-app/Rakefile b/features/fixtures/cli-app/Rakefile new file mode 100644 index 000000000..29955274e --- /dev/null +++ b/features/fixtures/cli-app/Rakefile @@ -0,0 +1 @@ +require "bundler/gem_tasks" diff --git a/features/fixtures/cli-app/bin/cli b/features/fixtures/cli-app/bin/cli new file mode 100755 index 000000000..4a49462ab --- /dev/null +++ b/features/fixtures/cli-app/bin/cli @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +$LOAD_PATH << File.expand_path('../../lib', __FILE__) +require 'cli/app' + +exit 0 diff --git a/features/fixtures/cli-app/cli-app.gemspec b/features/fixtures/cli-app/cli-app.gemspec new file mode 100644 index 000000000..6481ca4af --- /dev/null +++ b/features/fixtures/cli-app/cli-app.gemspec @@ -0,0 +1,26 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'cli/app/version' + +Gem::Specification.new do |spec| + spec.name = 'cli-app' + spec.version = Cli::App::VERSION + spec.authors = ['Aruba Developers'] + spec.email = 'cukes@googlegroups.com' + + spec.summary = 'Summary' + spec.description = 'Description' + spec.homepage = 'http://example.com' + + # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or + # delete this section to allow pushing this gem to any host. + + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_development_dependency 'bundler', '~> 1.9' + spec.add_development_dependency 'rake', '~> 10.0' +end diff --git a/features/fixtures/cli-app/features/support/env.rb b/features/fixtures/cli-app/features/support/env.rb new file mode 100644 index 000000000..fb0a661b9 --- /dev/null +++ b/features/fixtures/cli-app/features/support/env.rb @@ -0,0 +1 @@ +require 'aruba/cucumber' diff --git a/features/fixtures/cli-app/lib/cli/app.rb b/features/fixtures/cli-app/lib/cli/app.rb new file mode 100644 index 000000000..cdd1fb979 --- /dev/null +++ b/features/fixtures/cli-app/lib/cli/app.rb @@ -0,0 +1,9 @@ +require 'cli/app/version' + +Dir.glob(File.expand_path('../**/*.rb', __FILE__)).each { |f| require_relative f } + +module Cli + module App + # Your code goes here... + end +end diff --git a/features/fixtures/cli-app/lib/cli/app/version.rb b/features/fixtures/cli-app/lib/cli/app/version.rb new file mode 100644 index 000000000..07b8ed6a8 --- /dev/null +++ b/features/fixtures/cli-app/lib/cli/app/version.rb @@ -0,0 +1,5 @@ +module Cli + module App + VERSION = "0.1.0" + end +end diff --git a/features/fixtures/cli-app/script/console b/features/fixtures/cli-app/script/console new file mode 100755 index 000000000..1a4888ae7 --- /dev/null +++ b/features/fixtures/cli-app/script/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "cli/app" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start diff --git a/features/fixtures/cli-app/spec/cli/app_spec.rb b/features/fixtures/cli-app/spec/cli/app_spec.rb new file mode 100644 index 000000000..ce27d676c --- /dev/null +++ b/features/fixtures/cli-app/spec/cli/app_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Cli::App do + it 'has a version number' do + expect(Cli::App::VERSION).not_to be nil + end + + it 'does something useful' do + expect(false).to eq(true) + end +end diff --git a/features/fixtures/cli-app/spec/spec_helper.rb b/features/fixtures/cli-app/spec/spec_helper.rb new file mode 100644 index 000000000..32b76cdb2 --- /dev/null +++ b/features/fixtures/cli-app/spec/spec_helper.rb @@ -0,0 +1,2 @@ +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +require 'cli/app' diff --git a/features/before_cmd_hooks.feature b/features/hooks/before_cmd.feature similarity index 94% rename from features/before_cmd_hooks.feature rename to features/hooks/before_cmd.feature index 2369886c3..baea5a593 100644 --- a/features/before_cmd_hooks.feature +++ b/features/hooks/before_cmd.feature @@ -12,7 +12,7 @@ Feature: before_cmd hooks require 'aruba/api' Aruba.configure do |config| - config.before_cmd do |cmd| + config.before :cmd do |cmd| puts "about to run `#{cmd}`" end end @@ -36,7 +36,7 @@ Feature: before_cmd hooks require 'aruba/api' Aruba.configure do |config| - config.before_cmd do |cmd| + config.before :cmd do |cmd| puts "I can see @your_context=#{@your_context}" end end diff --git a/features/step_definitions/aruba_dev_steps.rb b/features/step_definitions/aruba_dev_steps.rb index f5d888049..7734523d6 100644 --- a/features/step_definitions/aruba_dev_steps.rb +++ b/features/step_definitions/aruba_dev_steps.rb @@ -1,4 +1,6 @@ When /^I do aruba (.*)$/ do |aruba_step| + @aruba_exception = StandardError.new + begin step(aruba_step) rescue Exception => e @@ -26,3 +28,39 @@ Then /^the output should be (\d+) bytes long$/ do |length| expect(all_output.length).to eq length.to_i end + +Then /^the feature(?:s)? should( not)?(?: all)? pass$/ do |negated| + if negated + step 'the output should contain " failed)"' + step 'the exit status should be 1' + else + step 'the output should not contain " failed)"' + step 'the exit status should be 0' + end +end + +Then /^the feature(?:s)? should( not)?(?: all)? pass with:$/ do |negated, string| + if negated + step 'the output should contain " failed)"' + step 'the exit status should be 1' + else + step 'the output should not contain " failed)"' + step 'the exit status should be 0' + end + + step 'the output should contain:', string if string +end + +Given(/^the default feature-test$/) do + step( + 'a file named "features/default.feature" with:', + <<-EOS.strip_heredoc + Feature: Default Feature + + This is the default feature + + Scenario: Run command + Given I successfully run `cli` + EOS + ) +end diff --git a/features/support/aruba.rb b/features/support/aruba.rb new file mode 100644 index 000000000..611d68a80 --- /dev/null +++ b/features/support/aruba.rb @@ -0,0 +1,5 @@ +require 'aruba/cucumber' + +Aruba.configure do |config| + config.exit_timeout = 5 +end diff --git a/lib/aruba.rb b/lib/aruba.rb index fda321cb1..896dcf6c0 100644 --- a/lib/aruba.rb +++ b/lib/aruba.rb @@ -1,4 +1,37 @@ +require 'contracts' +require 'rspec/expectations' + +require 'fileutils' +require 'rbconfig' +require 'ostruct' +require 'pathname' + +require 'aruba/extensions/object/deep_dup' +require 'aruba/extensions/string/strip' + +require 'aruba/errors' +require 'aruba/hooks' + +require 'aruba/config_wrapper' +require 'aruba/basic_configuration' +require 'aruba/config' +require 'aruba/announcer' +require 'aruba/command_monitor' +require 'aruba/runtime' + +require 'aruba/event' +require 'aruba/event_queue' + +require 'aruba/utils' require 'aruba/processes/spawn_process' +require 'aruba/processes/in_process' +require 'aruba/processes/debug_process' + +require 'aruba/aruba_command' + +require 'aruba/jruby' + +require 'aruba/api/core' module Aruba class << self diff --git a/lib/aruba/announcer.rb b/lib/aruba/announcer.rb index 3dc5f5ba9..4ebe64fae 100644 --- a/lib/aruba/announcer.rb +++ b/lib/aruba/announcer.rb @@ -56,7 +56,7 @@ def mode?(m) public - def initialize(_, options) + def initialize(_ = nil, options = {}) @announcers = [] @announcers << PutsAnnouncer.new @announcers << KernelPutsAnnouncer.new diff --git a/lib/aruba/api.rb b/lib/aruba/api.rb index 99450c640..ff6502fff 100644 --- a/lib/aruba/api.rb +++ b/lib/aruba/api.rb @@ -1,14 +1,4 @@ -require 'fileutils' -require 'rbconfig' -require 'rspec/expectations' require 'aruba' -require 'aruba/config' -require 'aruba/errors' -require 'aruba/process_monitor' -require 'aruba/utils' -require 'aruba/announcer' -require 'ostruct' -require 'pathname' Dir.glob( File.join( File.expand_path( '../matchers' , __FILE__ ) , '*.rb' ) ).each { |rb| require rb } @@ -16,6 +6,7 @@ module Aruba module Api include RSpec::Matchers include Aruba::Utils + include Aruba::Api::Core # Expand file name # @@ -54,7 +45,7 @@ def expand_path(file_name, dir_string = nil) fail ArgumentError, message if file_name.nil? || file_name.empty? # rubocop:enable Style/RaiseArgs - if FIXTURES_PATH_PREFIX == file_name[0] + if aruba.config.fixtures_path_prefix == file_name[0] File.join fixtures_directory, file_name[1..-1] else in_current_directory { File.expand_path file_name, dir_string } @@ -69,15 +60,6 @@ def absolute_path(*args) in_current_directory { File.expand_path File.join(*args) } end - # Execute block in current directory - # - # @yield - # The block which should be run in current directory - def in_current_directory(&block) - _mkdir(current_directory) - Dir.chdir(current_directory, &block) - end - # Check if file or directory exist # # @param [String] file_or_directory @@ -152,26 +134,6 @@ def directory(path) Dir.new(expand_path(path)) end - # @deprecated - # @private - def in_current_dir(*args, &block) - warn('The use of "in_current_dir" is deprecated. Use "in_current_directory" instead') - in_current_directory(*args, &block) - end - - # Clean the current directory - def clean_current_directory - _rm_rf(current_directory) - _mkdir(current_directory) - end - - # @deprecated - # @private - def clean_current_dir(*args, &block) - warn('The use of "clean_current_dir" is deprecated. Use "clean_current_directory" instead') - clean_current_directory(*args, &block) - end - # Return content of directory # # @return [Array] @@ -197,39 +159,6 @@ def read(name) File.readlines(expand_path(name)) end - # Get access to current dir - # - # @return - # Current directory - def current_directory - File.join(*dirs) - end - - # @deprecated - # @private - def current_dir(*args, &block) - warn('The use of "current_dir" is deprecated. Use "current_directory" instead') - current_directory(*args, &block) - end - - # Switch to directory - # - # @param [String] dir - # The directory - def cd(dir) - dirs << dir - raise "#{current_directory} is not a directory." unless File.directory?(current_directory) - end - - # The path to the directory which should contain all your test data - # You might want to overwrite this method to place your data else where. - # - # @return [Array] - # The directory path: Each subdirectory is a member of an array - def dirs - @dirs ||= %w(tmp aruba) - end - # Create a file with given content # # The method does not check if file already exists. If the file name is a @@ -297,7 +226,7 @@ def copy(*source, destination) raise ArgumentError, %(The following source "#{s}" does not exist.) unless exist? s end - raise ArgumentError, "Using a fixture as destination (#{destination}) is not supported" if destination.start_with? FIXTURES_PATH_PREFIX + raise ArgumentError, "Using a fixture as destination (#{destination}) is not supported" if destination.start_with? aruba.config.fixtures_path_prefix raise ArgumentError, "Multiples sources can only be copied to a directory" if source.count > 1 && exist?(destination) && !directory?(destination) source_paths = source.map { |f| expand_path(f) } @@ -526,7 +455,7 @@ def remove_dir(*args) def check_file_presence(paths, expect_presence = true) warn('The use of "check_file_presence" is deprecated. Use "expect().to be_existing_file or expect(all_paths).to match_path_pattern() instead" ') - stop_processes! + stop_commands Array(paths).each do |path| if path.kind_of? Regexp @@ -571,7 +500,7 @@ def pipe_in_file(file_name) # check_file_size(paths_and_sizes) # def check_file_size(paths_and_sizes) - stop_processes! + stop_commands paths_and_sizes.each do |path, size| path = expand_path(path) @@ -588,7 +517,7 @@ def check_file_size(paths_and_sizes) # @yield # Pass the content of the given file to this block def with_file_content(file, &block) - stop_processes! + stop_commands file = expand_path(file) content = IO.read(file) @@ -612,7 +541,7 @@ def with_file_content(file, &block) # @param [true, false] expect_match # Must the content be in the file or not def check_file_content(file, content, expect_match = true) - stop_processes! + stop_commands match_content = if(Regexp === content) @@ -664,7 +593,7 @@ def check_binary_file_content(file, reference_file, expect_match = true) # @param [true, false] expect_presence # Should the directory be there or should the directory not be there def check_directory_presence(paths, expect_presence) - stop_processes! + stop_commands paths.each do |path| path = expand_path(path) @@ -681,7 +610,7 @@ def check_directory_presence(paths, expect_presence) def prep_for_fs_check(&block) warn('The use of "prep_for_fs_check" is deprecated. It will be removed soon.') - process_monitor.stop_processes! + aruba.command_monitor.stop_commands in_current_directory{ block.call } end @@ -717,7 +646,7 @@ def unescape(string) # @param [String] cmd # The command def output_from(cmd) - process_monitor.output_from(cmd) + aruba.command_monitor.output_from(cmd) end # Fetch stdout from command @@ -725,7 +654,7 @@ def output_from(cmd) # @param [String] cmd # The command def stdout_from(cmd) - process_monitor.stdout_from(cmd) + aruba.command_monitor.stdout_from(cmd) end # Fetch stderr from command @@ -733,31 +662,31 @@ def stdout_from(cmd) # @param [String] cmd # The command def stderr_from(cmd) - process_monitor.stderr_from(cmd) + aruba.command_monitor.stderr_from(cmd) end - # Get stdout of all processes + # Get stdout of all commands # # @return [String] - # The stdout of all process which have run before + # The stdout of all command which have run before def all_stdout - process_monitor.all_stdout + aruba.command_monitor.all_stdout end - # Get stderr of all processes + # Get stderr of all commands # # @return [String] - # The stderr of all process which have run before + # The stderr of all command which have run before def all_stderr - process_monitor.all_stderr + aruba.command_monitor.all_stderr end - # Get stderr and stdout of all processes + # Get stderr and stdout of all commands # # @return [String] - # The stderr and stdout of all process which have run before + # The stderr and stdout of all command which have run before def all_output - process_monitor.all_output + aruba.command_monitor.all_output end # Full compare arg1 and arg2 @@ -856,7 +785,7 @@ def assert_exit_status_and_output(expect_to_pass, expected_output, expect_exact_ end end - # Check exit status of process + # Check exit status of command # # @return [TrueClass, FalseClass] # If arg1 is true, return true if command was successful @@ -885,43 +814,74 @@ def append_output_to(message) "#{message} Output:\n\n#{all_output}\n" end - def process_monitor - @process_monitor ||= ProcessMonitor.new(announcer) - end - # @private def processes - process_monitor.send(:processes) + warn('The use of "processes" is deprecated. It will be removed soon. Please use "aruba.command_monitor.commands" instead.') + + aruba.command_monitor.commands end # @private def stop_processes! - process_monitor.stop_processes! + warn('The use of "stop_processes!" is deprecated. It will be removed soon. Please use "#stop_commands" instead.') + + stop_commands end - # Terminate all running processes + # Stop all commands + def stop_commands + aruba.command_monitor.stop_commands + end + + # Terminate all running commands def terminate_processes! - process_monitor.terminate_processes! + warn('The use of "terminate_processes!" is deprecated. Please use "#terminate_commands" instead.') + + terminate_commands end - # @private + # Terminate all running commands + def terminate_commands + aruba.command_monitor.terminate_commands + end + + # Return the last command which was started + # + # @return [Command] + # The command def last_command - processes.last[1] + aruba.command_monitor.last_command end # @private - def register_process(*args) - process_monitor.register_process(*args) + # @deprecated + def register_process(name, command) + warn('The use of "register_processs" is deprecated. It will be removed soon. There\'s no need to register a command anymore. To start a command use "aruba.command_monitor.start_command()". This starts and registers the command for you.') + + aruba.command_monitor.send(:register_command, command) end # @private + # @deprecated def get_process(wanted) - process_monitor.get_process(wanted) + warn('The use of "get_process" is deprecated. It will be removed soon. To find a command use "aruba.command_monitor.find()". But be aware that this uses the commandline of the process.') + aruba.command_monitor.find(wanted) end # @private - def only_processes - process_monitor.only_processes + # @deprecated + def only_process + warn('The use of "only_processes" is deprecated. It will be removed soon. To get access to all commands use "#all_commands".') + + all_commands + end + + # Make all commands available + # + # @return [Array] + # Array of commands + def all_commands + aruba.command_monitor.commands end # Run given command and stop it if timeout is reached @@ -933,7 +893,7 @@ def only_processes # If the timeout is reached the command will be killed # # @yield [SpawnProcess] - # Run block with process + # Run block with command def run(cmd, timeout = nil) timeout ||= exit_timeout @commands ||= [] @@ -941,54 +901,35 @@ def run(cmd, timeout = nil) cmd = detect_ruby(cmd) - Aruba.config.hooks.execute(:before_cmd, self, cmd) + aruba.config.hooks.execute(:before_cmd, self, cmd) + aruba.event_queue.notify :switched_working_directory, Dir.pwd - announcer.announce(:directory, Dir.pwd) - announcer.announce(:command, cmd) + command = aruba.command_monitor.start_command(cmd, timeout, io_wait, expand_path('.')) - process = Aruba.process.new(cmd, timeout, io_wait, expand_path('.')) - process_monitor.register_process(cmd, process) - process.run! - - block_given? ? yield(process) : process + block_given? ? yield(command) : command end - DEFAULT_TIMEOUT_SECONDS = 3 - # Default exit timeout for running commands with aruba # # Overwrite this method if you want a different timeout or set # `@aruba_timeout_seconds`. def exit_timeout - @aruba_timeout_seconds || DEFAULT_TIMEOUT_SECONDS + aruba.config.exit_timeout end - DEFAULT_IO_WAIT_SECONDS = 0.1 - # Default io wait timeout # # Overwrite this method if you want a different timeout or set # `@aruba_io_wait_seconds def io_wait - @aruba_io_wait_seconds || DEFAULT_IO_WAIT_SECONDS + aruba.config.io_wait_timeout end - DEFAULT_ROOT_DIRECTORY = Dir.getwd - # The root directory of aruba def root_directory - @aruba_root_directory || DEFAULT_ROOT_DIRECTORY + aruba.config.root_directory end - # Path prefix for fixtures - FIXTURES_PATH_PREFIX = ?% - - DEFAULT_FIXTURES_DIRECTORIES = %w( - features/fixtures - spec/fixtures - test/fixtures - ) - # The path to the directory which contains fixtures # You might want to overwrite this method to place your data else where. # @@ -996,7 +937,7 @@ def root_directory # The directory to where your fixtures are stored def fixtures_directory unless @fixtures_directory - candidates = DEFAULT_FIXTURES_DIRECTORIES.map { |dir| File.join(root_directory, dir) } + candidates = aruba.config.fixtures_directories.map { |dir| File.join(root_directory, dir) } @fixtures_directory = candidates.find { |dir| File.directory? dir } raise "No fixtures directories are found" unless @fixtures_directory end @@ -1018,10 +959,10 @@ def fixtures_directory # @param [Integer] timeout # Timeout for execution def run_simple(cmd, fail_on_error = true, timeout = nil) - command = run(cmd, timeout) do |process| - process_monitor.stop_process(process) + command = run(cmd, timeout) do |c| + aruba.command_monitor.stop_command(c) - process + c end @timed_out = command.exit_status.nil? @@ -1131,12 +1072,13 @@ def unset_bundler_env_vars # @param [String] value # The value of the environment variable. Needs to be a string. def set_env(key, value) - announcer.announce(:environment, key, value) + aruba.event_queue.notify :changed_environment, { key: value } + original_env[key] = ENV.delete(key) unless original_env.key? key ENV[key] = value end - # Restore original process environment + # Restore original command environment def restore_env original_env.each do |key, value| if value @@ -1188,15 +1130,19 @@ def announcer private def last_exit_status - process_monitor.last_exit_status + aruba.command_monitor.last_command.exit_status end - def stop_process(process) - process_monitor.stop_process(process) + def stop_process(command) + warn('The use of "stop_process" is deprecated. It will be removed soon. Please use `aruba.command_monitor.find(command).stop`.') + + aruba.command_monitor.find(command).stop end - def terminate_process(process) - process_monitor.terminate_process(process) + def terminate_process(command) + warn('The use of "terminate_process" is deprecated. It will be removed soon. Please use `aruba.command_monitor.find(command).terminate`.') + + aruba.command_monitor.find(command).terminate end end end diff --git a/lib/aruba/api/core.rb b/lib/aruba/api/core.rb new file mode 100644 index 000000000..5e7d18b92 --- /dev/null +++ b/lib/aruba/api/core.rb @@ -0,0 +1,72 @@ +module Aruba + module Api + module Core + # Aruba Runtime + def aruba + @_aruba_runtime ||= Runtime.new + end + + # Execute block in current directory + # + # @yield + # The block which should be run in current directory + def in_current_directory(&block) + _mkdir(current_directory) + Dir.chdir(current_directory, &block) + end + + # @deprecated + # @private + def in_current_dir(*args, &block) + warn('The use of "in_current_dir" is deprecated. Use "in_current_directory" instead') + in_current_directory(*args, &block) + end + + # Clean the current directory + def clean_current_directory + _rm_rf(current_directory) + _mkdir(current_directory) + end + + # @deprecated + # @private + def clean_current_dir(*args, &block) + warn('The use of "clean_current_dir" is deprecated. Use "clean_current_directory" instead') + clean_current_directory(*args, &block) + end + + # Get access to current dir + # + # @return + # Current directory + def current_directory + File.join(*dirs) + end + + # @deprecated + # @private + def current_dir(*args, &block) + warn('The use of "current_dir" is deprecated. Use "current_directory" instead') + current_directory(*args, &block) + end + + # Switch to directory + # + # @param [String] dir + # The directory + def cd(dir) + dirs << dir + raise "#{current_directory} is not a directory." unless File.directory?(current_directory) + end + + # The path to the directory which should contain all your test data + # You might want to overwrite this method to place your data else where. + # + # @return [Array] + # The directory path: Each subdirectory is a member of an array + def dirs + @dirs ||= Aruba.config.current_directory + end + end + end +end diff --git a/lib/aruba/aruba_command.rb b/lib/aruba/aruba_command.rb new file mode 100644 index 000000000..2a268b727 --- /dev/null +++ b/lib/aruba/aruba_command.rb @@ -0,0 +1,36 @@ +module Aruba + class ArubaCommand < SimpleDelegator + attr_reader :event_queue + private :event_queue + + def initialize(obj, event_queue) + @event_queue = event_queue + + super(obj) + end + + def stop(*) + event_queue.notify :command_stopped, self.commandline + + super + + event_queue.notify :command_stdout, self.stdout + event_queue.notify :command_stderr, self.stderr + end + + def terminate(*) + event_queue.notify :command_stopped, self.commandline + + super + + event_queue.notify :command_stdout, self.stdout + event_queue.notify :command_stderr, self.stderr + end + + def start + event_queue.notify :command_started, self.commandline + super + end + alias_method :run!, :start + end +end diff --git a/lib/aruba/basic_configuration.rb b/lib/aruba/basic_configuration.rb new file mode 100644 index 000000000..164af449c --- /dev/null +++ b/lib/aruba/basic_configuration.rb @@ -0,0 +1,142 @@ +module Aruba + # Basic configuration for ProxyPacRb + class BasicConfiguration + include Contracts + + # A configuration option + class Option + attr_accessor :name + attr_writer :value + + def initialize(name:, value:) + @name = name + @value = value + end + + def value + @value.deep_dup + end + + def deep_dup + obj = self.dup + obj.value = value + + obj + end + + def ==(other) + name == other.name && value == other.value + end + end + + class << self + def known_options + @known_options ||= {} + end + + def option_reader(name, contract: nil, default: nil) + fail ArgumentError, 'contract is required' if contract.nil? + fail ArgumentError, 'Either use block or default value' if block_given? && default + + Contract contract + add_option(name, block_given? ? yield(ConfigWrapper.new(known_options)) : default) + + define_method(name) { find_option(name).value } + + self + end + + def option_accessor(name, contract: nil, default: nil) + fail ArgumentError, 'contract is required' if contract.nil? + fail ArgumentError, 'Either use block or default value' if block_given? && default + fail ArgumentError, 'Either use block or default value' if !block_given? && default.nil? && default.empty? + + # Add writer + add_option(name, block_given? ? yield(ConfigWrapper.new(known_options)) : default) + + Contract contract + define_method("#{name}=") { |v| find_option(name).value = v } + + # Add reader + option_reader name, contract: { None => contract.values.first } + end + + private + + def add_option(name, value = nil) + return if known_options.key?(name) + + known_options[name] = Option.new(name: name, value: value) + + self + end + end + + protected + + attr_accessor :local_options + attr_writer :hooks + + public + + attr_reader :hooks + + def initialize + initialize_configuration + end + + # @yield [Configuration] + # + # Yields self + def configure + yield self if block_given? + end + + # Reset configuration + def reset + initialize_configuration + end + + def deep_dup + obj = self.dup + obj.local_options = local_options.deep_dup + obj.hooks = hooks.deep_dup + + obj + end + + def before(name, &block) + hooks.append('before_' + name.to_s, block) + end + + def after(name, &block) + hooks.append('after_' + name.to_s, block) + end + + def option?(name) + local_options.any? { |_, v| v.name == name } + end + + def ==(other) + local_options.values.map(&:value) == other.local_options.values.map(&:value) + end + + # Set if name is option + def set_if_option(name, *args) + public_send("#{name}=".to_sym, *args) if option? name + end + + private + + def initialize_configuration + @local_options = self.class.known_options.deep_dup + @hooks = Hooks.new + end + + def find_option(name) + fail NotImplementedError, %(Unknown option "#{name}") unless option? name + + local_options[name] + end + end +end diff --git a/lib/aruba/command_monitor.rb b/lib/aruba/command_monitor.rb new file mode 100644 index 000000000..1eeeb1cfd --- /dev/null +++ b/lib/aruba/command_monitor.rb @@ -0,0 +1,159 @@ +module Aruba + class CommandMonitor + private + + attr_reader :event_queue + + public + + attr_reader :commands + + def initialize(event_queue: nil) + fail ArgumentError, 'event_queue is required' if event_queue.nil? + + @commands = [] + @event_queue = event_queue + end + + def last_exit_status + return @last_exit_status if @last_exit_status + stop_commands + @last_exit_status + end + + # The last started command + def last_command + commands.last + end + + # Start given command + # + # @param [String] cmd + # The commandline of the command + # @param [Numeric] timeout + # The time to wait for the command to stop + def start_command(cmd, timeout, io_wait, working_directory) + command = ArubaCommand.new(Aruba.process.new(cmd, timeout, io_wait, working_directory), event_queue) + register_command(command) + command.start + + command + end + + # Stop given command + # + # @param [String] cmd + # The commandline of the command + def stop_command(cmd) + find(cmd) { |c| c.stop } + end + + # Terminate given command + # + # @param [String] cmd + # The commandline of the command + def terminate_command(cmd) + find(cmd) { |c| c.terminate } + end + + # Find command + # + # @yield [Command] + # This yields the found command + def find(cmd) + cmd = cmd.commandline if cmd.is_a? ArubaCommand + command = commands.find { |c| c.commandline == cmd } + + fail ArgumentError, "No command named '#{cmd}' has been started" if command.nil? + + yield(command) + end + + # Stop all known commands + def stop_commands + commands.each do |c| + c.stop + end + + self + end + + # Terminate all running commands + def terminate_commands + commands.each do |c| + c.terminate + end + + self + end + + # Fetch output (stdout, stderr) from command + # + # @param [String] cmd + # The command + def output_from(cmd) + cmd = Utils.detect_ruby(cmd) + find(cmd).output + end + + # Fetch stdout from command + # + # @param [String] cmd + # The command + def stdout_from(cmd) + cmd = Utils.detect_ruby(cmd) + find(cmd).stdout + end + + # Fetch stderr from command + # + # @param [String] cmd + # The command + def stderr_from(cmd) + cmd = Utils.detect_ruby(cmd) + find(cmd).stderr + end + + # Get stdout of all commands + # + # @return [String] + # The stdout of all command which have run before + def all_stdout + stop_commands + commands.each_with_object("") { |ps, out| out << ps.stdout } + end + + # Get stderr of all commands + # + # @return [String] + # The stderr of all command which have run before + def all_stderr + stop_commands + commands.each_with_object("") { |ps, out| out << ps.stderr } + end + + # Get stderr and stdout of all commands + # + # @return [String] + # The stderr and stdout of all command which have run before + def all_output + all_stdout << all_stderr + end + + # Clear list of known commands + def clear + stop_commands + commands.clear + + self + end + + private + + # Register + def register_command(cmd) + commands << cmd + end + + end +end diff --git a/lib/aruba/config.rb b/lib/aruba/config.rb index 1c106d08e..3a24f7ad3 100644 --- a/lib/aruba/config.rb +++ b/lib/aruba/config.rb @@ -1,43 +1,39 @@ module Aruba - class Config - attr_reader :hooks - - def initialize - @hooks = Hooks.new - end - - # Register a hook to be called before Aruba runs a command - def before_cmd(&block) - @hooks.append(:before_cmd, block) - end + # Aruba Configuration + class Configuration < BasicConfiguration + option_reader :root_directory, contract: { None => String }, default: Dir.getwd + option_accessor :current_directory, contract: { Array => Array }, default: %w(tmp aruba) + option_reader :fixtures_path_prefix, contract: { None => String }, default: ?% + option_accessor :exit_timeout, contract: { Num => Num }, default: 3 + option_accessor :io_wait_timeout, contract: { Num => Num }, default: 0.1 + option_accessor :fixtures_directories, contract: { Array => ArrayOf[String] }, default: %w(features/fixtures spec/fixtures test/fixtures) end +end - #nodoc - class Hooks - def initialize - @store = Hash.new do |hash, key| - hash[key] = [] - end - end +# Main Module +module Aruba + @config = Configuration.new - def append(label, block) - @store[label] << block - end + class << self + attr_reader :config + + def configure(&block) + @config.configure(&block) - def execute(label, context, *args) - @store[label].each do |block| - context.instance_exec(*args, &block) - end + self end end +end - class << self - attr_accessor :config - - def configure - yield config +module Aruba + # Old Config + # + # @private + class Config < Configuration + def initialize(*args) + warn('The use of "Aruba::Config" is deprecated. Use "Aruba::Configuration" instead.') + + super end end - - self.config = Config.new end diff --git a/lib/aruba/config_wrapper.rb b/lib/aruba/config_wrapper.rb new file mode 100644 index 000000000..43c7d3c67 --- /dev/null +++ b/lib/aruba/config_wrapper.rb @@ -0,0 +1,17 @@ +module Aruba + class ConfigWrapper + attr_reader :config + private :config + + def initialize(config) + @config = config.dup + end + + def method_missing(name, *args) + fail ArgumentError, 'Options take no argument' if args.count > 0 + fail UnknownOptionError, %(Option "#{name}" is unknown. Please use only earlier defined options) unless config.key? name + + config[name] + end + end +end diff --git a/lib/aruba/cucumber.rb b/lib/aruba/cucumber.rb index 85716e09b..465c81f47 100644 --- a/lib/aruba/cucumber.rb +++ b/lib/aruba/cucumber.rb @@ -1,3 +1,4 @@ +require 'aruba' require 'aruba/api' require 'aruba/cucumber/hooks' require 'aruba/reporting' @@ -5,17 +6,17 @@ World(Aruba::Api) Given /the default aruba timeout is (\d+) seconds/ do |seconds| - @aruba_timeout_seconds = seconds.to_i + aruba.config.exit_timeout = seconds.to_i end Given /I use (?:a|the) fixture(?: named)? "([^"]*)"/ do |name| - copy File.join(Aruba::Api::FIXTURES_PATH_PREFIX, name), name + copy File.join(aruba.config.fixtures_path_prefix, name), name cd name end Given /The default aruba timeout is (\d+) seconds/ do |seconds| warn(%{\e[35m The /^The default aruba timeout is (\d+) seconds/ step definition is deprecated. Please use the one with `the` and not `The` at the beginning.\e[0m}) - @aruba_timeout_seconds = seconds.to_i + aruba.config.exit_timeout = seconds.to_i end Given /^I'm using a clean gemset "([^"]*)"$/ do |gemset| @@ -379,7 +380,3 @@ Then /^the mode of filesystem object "([^"]*)" should (not )?match "([^"]*)"$/ do |file, expect_match, mode| check_filesystem_permissions(mode, file, !expect_match) end - -Before '@mocked_home_directory' do - set_env 'HOME', expand_path('.') -end diff --git a/lib/aruba/cucumber/hooks.rb b/lib/aruba/cucumber/hooks.rb index 98845ca21..50dbac633 100644 --- a/lib/aruba/cucumber/hooks.rb +++ b/lib/aruba/cucumber/hooks.rb @@ -60,3 +60,7 @@ restore_env process_monitor.clear end + +Before '@mocked_home_directory' do + set_env 'HOME', expand_path('.') +end diff --git a/lib/aruba/errors.rb b/lib/aruba/errors.rb index 310428ef1..ba53adec5 100644 --- a/lib/aruba/errors.rb +++ b/lib/aruba/errors.rb @@ -7,4 +7,7 @@ class UserError < StandardError; end # Raised on launch error class LaunchError < Error; end + + # Raised if one tries to use an unknown configuration option + class UnknownOptionError < ArgumentError; end end diff --git a/lib/aruba/event.rb b/lib/aruba/event.rb new file mode 100644 index 000000000..cafd34b96 --- /dev/null +++ b/lib/aruba/event.rb @@ -0,0 +1,10 @@ +module Aruba + class Event + attr_reader :name, :message + + def initialize(name, message) + @name = name + @message = message + end + end +end diff --git a/lib/aruba/event_queue.rb b/lib/aruba/event_queue.rb new file mode 100644 index 000000000..6c4c94ea0 --- /dev/null +++ b/lib/aruba/event_queue.rb @@ -0,0 +1,19 @@ +module Aruba + class EventQueue + def initialize + @queues = Hash.new { |h, k| h[k] = [] } + end + + def register(event, listener) + fail ArgumentError, 'A valid listener needs to implement "#notify' unless listener.respond_to? :notify + + @queues[event.to_sym] << listener + + self + end + + def notify(event, message) + @queues[event].each { |l| l.notify Event.new(event, message) } + end + end +end diff --git a/lib/aruba/extensions/object/deep_dup.rb b/lib/aruba/extensions/object/deep_dup.rb new file mode 100644 index 000000000..820c5602d --- /dev/null +++ b/lib/aruba/extensions/object/deep_dup.rb @@ -0,0 +1,46 @@ +require 'aruba/extensions/object/duplicable' + +class Object + # Returns a deep copy of object if it's duplicable. If it's + # not duplicable, returns +self+. + # + # object = Object.new + # dup = object.deep_dup + # dup.instance_variable_set(:@a, 1) + # + # object.instance_variable_defined?(:@a) # => false + # dup.instance_variable_defined?(:@a) # => true + def deep_dup + duplicable? ? dup : self + end +end + +class Array + # Returns a deep copy of array. + # + # array = [1, [2, 3]] + # dup = array.deep_dup + # dup[1][2] = 4 + # + # array[1][2] # => nil + # dup[1][2] # => 4 + def deep_dup + map(&:deep_dup) + end +end + +class Hash + # Returns a deep copy of hash. + # + # hash = { a: { b: 'b' } } + # dup = hash.deep_dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] # => nil + # dup[:a][:c] # => "c" + def deep_dup + each_with_object(dup) do |(key, value), hash| + hash[key.deep_dup] = value.deep_dup + end + end +end diff --git a/lib/aruba/extensions/object/duplicable.rb b/lib/aruba/extensions/object/duplicable.rb new file mode 100644 index 000000000..c6919f72d --- /dev/null +++ b/lib/aruba/extensions/object/duplicable.rb @@ -0,0 +1,105 @@ +#-- +# Most objects are cloneable, but not all. For example you can't dup +nil+: +# +# nil.dup # => TypeError: can't dup NilClass +# +# Classes may signal their instances are not duplicable removing +dup+/+clone+ +# or raising exceptions from them. So, to dup an arbitrary object you normally +# use an optimistic approach and are ready to catch an exception, say: +# +# arbitrary_object.dup rescue object +# +# Rails dups objects in a few critical spots where they are not that arbitrary. +# That rescue is very expensive (like 40 times slower than a predicate), and it +# is often triggered. +# +# That's why we hardcode the following cases and check duplicable? instead of +# using that rescue idiom. +#++ +class Object + # Can you safely dup this object? + # + # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects; + # true otherwise. + def duplicable? + true + end +end + +class NilClass + # +nil+ is not duplicable: + # + # nil.duplicable? # => false + # nil.dup # => TypeError: can't dup NilClass + def duplicable? + false + end +end + +class FalseClass + # +false+ is not duplicable: + # + # false.duplicable? # => false + # false.dup # => TypeError: can't dup FalseClass + def duplicable? + false + end +end + +class TrueClass + # +true+ is not duplicable: + # + # true.duplicable? # => false + # true.dup # => TypeError: can't dup TrueClass + def duplicable? + false + end +end + +class Symbol + # Symbols are not duplicable: + # + # :my_symbol.duplicable? # => false + # :my_symbol.dup # => TypeError: can't dup Symbol + def duplicable? + false + end +end + +class Numeric + # Numbers are not duplicable: + # + # 3.duplicable? # => false + # 3.dup # => TypeError: can't dup Fixnum + def duplicable? + false + end +end + +require 'bigdecimal' +class BigDecimal + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. + begin + BigDecimal.new('4.56').dup + + def duplicable? + true + end + # rubocop:disable Lint/HandleExceptions + rescue TypeError + # rubocop:enable Lint/HandleExceptions + # can't dup, so use superclass implementation + end +end + +class Method + # Methods are not duplicable: + # + # method(:puts).duplicable? # => false + # method(:puts).dup # => TypeError: allocator undefined for Method + def duplicable? + false + end +end diff --git a/lib/aruba/extensions/object/try.rb b/lib/aruba/extensions/object/try.rb new file mode 100644 index 000000000..23d15cd31 --- /dev/null +++ b/lib/aruba/extensions/object/try.rb @@ -0,0 +1,99 @@ +class Object + # Invokes the public method whose name goes as first argument just like + # +public_send+ does, except that if the receiver does not respond to it the + # call returns +nil+ rather than raising an exception. + # + # This method is defined to be able to write + # + # @person.try(:name) + # + # instead of + # + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) #=> nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil + # + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: + # + # nil.try(:to_i) # => nil, rather than 0 + # + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # in case of argument mismatch. + # + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: + # + # @person.try do |p| + # ... + # end + # + # You can also call try with a block without accepting an argument, and the block + # will be instance_eval'ed instead: + # + # @person.try { upcase.truncate(50) } + # + # Please also note that +try+ is defined on +Object+. Therefore, it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. For example, using +try+ with + # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on + # the delegator itself. + def try(*a, &b) + try!(*a, &b) if a.empty? || respond_to?(a.first) + end + + # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and + # does not implement the tried method. + def try!(*a, &b) + if a.empty? && block_given? + if b.arity.zero? + instance_eval(&b) + else + yield self + end + else + public_send(*a, &b) + end + end +end + +class NilClass + # Calling +try+ on +nil+ always returns +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. + # + # nil.try(:name) # => nil + # + # Without +try+ + # @person && @person.children.any? && @person.children.first.name + # + # With +try+ + # @person.try(:children).try(:first).try(:name) + def try(*args) + nil + end + + def try!(*args) + nil + end +end diff --git a/lib/aruba/extensions/string/strip.rb b/lib/aruba/extensions/string/strip.rb new file mode 100644 index 000000000..2264fc788 --- /dev/null +++ b/lib/aruba/extensions/string/strip.rb @@ -0,0 +1,26 @@ +require 'aruba/extensions/object/try' + +class String + # Strips indentation in heredocs. + # + # For example in + # + # if options[:usage] + # puts <<-USAGE.strip_heredoc + # This command does such and such. + # + # Supported options are: + # -h This message + # ... + # USAGE + # end + # + # the user would see the usage message aligned against the left margin. + # + # Technically, it looks for the least indented line in the whole string, and removes + # that amount of leading whitespace. + def strip_heredoc + indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 + gsub(/^[ \t]{#{indent}}/, '') + end +end diff --git a/lib/aruba/hooks.rb b/lib/aruba/hooks.rb new file mode 100644 index 000000000..7fca8a9b6 --- /dev/null +++ b/lib/aruba/hooks.rb @@ -0,0 +1,20 @@ +module Aruba + # Aruba Hooks + class Hooks + def initialize + @store = Hash.new do |hash, key| + hash[key] = [] + end + end + + def append(label, block) + @store[label.to_sym] << block + end + + def execute(label, context, *args) + @store[label.to_sym].each do |block| + context.instance_exec(*args, &block) + end + end + end +end diff --git a/lib/aruba/jruby.rb b/lib/aruba/jruby.rb index c8038855b..0113bbdda 100644 --- a/lib/aruba/jruby.rb +++ b/lib/aruba/jruby.rb @@ -1,9 +1,12 @@ # ideas taken from: http://blog.headius.com/2010/03/jruby-startup-time-tips.html Aruba.configure do |config| - config.before_cmd do + config.before :cmd do + next unless RUBY_PLATFORM == 'java' + # disable JIT since these processes are so short lived set_env('JRUBY_OPTS', "-X-C #{ENV['JRUBY_OPTS']}") + # force jRuby to use client JVM for faster startup times set_env('JAVA_OPTS', "-d32 #{ENV['JAVA_OPTS']}") if RbConfig::CONFIG['host_os'] =~ /solaris|sunos/i end -end if RUBY_PLATFORM == 'java' +end diff --git a/lib/aruba/matchers/command.rb b/lib/aruba/matchers/command.rb index 9dbdcf3f3..b4ac8af4e 100644 --- a/lib/aruba/matchers/command.rb +++ b/lib/aruba/matchers/command.rb @@ -19,7 +19,8 @@ RSpec::Matchers.define :have_exit_status do |expected| match do |actual| @old_actual = actual - @old_actual.stop(announcer) unless @old_actual.stopped? + + @old_actual.stop unless @old_actual.stopped? @actual = actual.exit_status next false unless @old_actual.respond_to? :exit_status diff --git a/lib/aruba/process_monitor.rb b/lib/aruba/process_monitor.rb deleted file mode 100644 index dda069e63..000000000 --- a/lib/aruba/process_monitor.rb +++ /dev/null @@ -1,113 +0,0 @@ -module Aruba - class ProcessMonitor - private - - attr_reader :processes, :announcer - - public - - def initialize(announcer) - @processes = [] - @announcer = announcer - end - - def last_exit_status - return @last_exit_status if @last_exit_status - stop_processes! - @last_exit_status - end - - def stop_process(process) - @last_exit_status = process.stop(announcer) - end - - def terminate_process!(process) - process.terminate - end - - def stop_processes! - processes.each do |_, process| - stop_process(process) - end - end - - # Terminate all running processes - def terminate_processes - processes.each do |_, process| - terminate_process(process) - stop_process(process) - end - end - - def register_process(name, process) - processes << [name, process] - end - - def get_process(wanted) - matching_processes = processes.reverse.find{ |name, _| name == wanted } - raise ArgumentError.new("No process named '#{wanted}' has been started") unless matching_processes - matching_processes.last - end - - def only_processes - processes.collect{ |_, process| process } - end - - # Fetch output (stdout, stderr) from command - # - # @param [String] cmd - # The command - def output_from(cmd) - cmd = Utils.detect_ruby(cmd) - get_process(cmd).output - end - - # Fetch stdout from command - # - # @param [String] cmd - # The command - def stdout_from(cmd) - cmd = Utils.detect_ruby(cmd) - get_process(cmd).stdout - end - - # Fetch stderr from command - # - # @param [String] cmd - # The command - def stderr_from(cmd) - cmd = Utils.detect_ruby(cmd) - get_process(cmd).stderr - end - - # Get stdout of all processes - # - # @return [String] - # The stdout of all process which have run before - def all_stdout - stop_processes! - only_processes.each_with_object("") { |ps, out| out << ps.stdout } - end - - # Get stderr of all processes - # - # @return [String] - # The stderr of all process which have run before - def all_stderr - stop_processes! - only_processes.each_with_object("") { |ps, out| out << ps.stderr } - end - - # Get stderr and stdout of all processes - # - # @return [String] - # The stderr and stdout of all process which have run before - def all_output - all_stdout << all_stderr - end - - def clear - processes.clear - end - end -end diff --git a/lib/aruba/processes/spawn_process.rb b/lib/aruba/processes/spawn_process.rb index 674a506c3..6e035d93c 100644 --- a/lib/aruba/processes/spawn_process.rb +++ b/lib/aruba/processes/spawn_process.rb @@ -6,6 +6,7 @@ module Aruba module Processes + # rubocop:disable Metrics/ClassLength class SpawnProcess < BasicProcess attr_reader :exit_status @@ -23,19 +24,22 @@ class SpawnProcess < BasicProcess # @params [String] working_directory # The directory where the command will be executed def initialize(cmd, exit_timeout, io_wait, working_directory) - @exit_timeout = exit_timeout - @io_wait = io_wait - @cmd = cmd - @process = nil - @exit_status = nil - @output_cache = nil - @error_cache = nil + @exit_timeout = exit_timeout + @io_wait = io_wait + @cmd = cmd + @process = nil + @exit_status = nil + @output_cache = nil + @error_cache = nil super end # Return command line + # + # @return [String] + # The command line of process def commandline @cmd end @@ -44,7 +48,8 @@ def commandline # # @yield [SpawnProcess] # Run code for process which was started - def run! + # rubocop:disable Metrics/MethodLength + def start @process = ChildProcess.build(*Shellwords.split(@cmd)) @out = Tempfile.new("aruba-out") @err = Tempfile.new("aruba-err") @@ -67,41 +72,89 @@ def run! after_run yield self if block_given? + + self end + alias_method :run!, :start + # rubocop:enable Metrics/MethodLength + # Make stdin avaiable def stdin @process.io.stdin end - def stdout - wait_for_io do + # Return stdout + # + # @param [TrueClass, FalseClass] wait + # Wait(true), Do not wait(false) for io before output it + # + # @return [String] + # The string of stdout + def stdout(wait: true) + return @output_cache if @output_cache # output channel was closed before due to command stop + + if wait + wait_for_io do + @process.io.stdout.flush + read(@out) + end + else @process.io.stdout.flush read(@out) - end || @output_cache + end end - def stderr - wait_for_io do + # Return stderr + # + # @param [TrueClass, FalseClass] wait + # Wait(true), Do not wait(false) for io before output it + # + # @return [String] + # The string of stderr + def stderr(wait: true) + return @error_cache if @error_cache # output channel was closed before due to command stop + + if wait + wait_for_io do + @process.io.stderr.flush + read(@err) + end + else @process.io.stderr.flush read(@err) - end || @error_cache + end end + # @private + # @deprecated def read_stdout warn('The use of "#read_stdout" is deprecated. Use "#stdout" instead.') stdout end + # Write to stdin + # + # @param [String] input + # The input string def write(input) @process.io.stdin.write(input) @process.io.stdin.flush + + self end + # Close io of process + # + # @param [String, Symbol] name + # The name of the io def close_io(name) @process.io.public_send(name.to_sym).close + + self end - def stop(reader) + # Stop process + def stop @stopped = true return @exit_status unless @process @@ -114,19 +167,20 @@ def stop(reader) close_and_cache_out close_and_cache_err - if reader - reader.stdout stdout - reader.stderr stderr - end - - @exit_status + self end + # Terminate process + # + # This does not wait for process until @exit_timeout is passed but stop + # it at once. def terminate return unless @process @process.stop - stop nil + stop + + self end private @@ -144,16 +198,21 @@ def read(io) end def close_and_cache_out - @output_cache = read @out + @output_cache = read(@out) @out.close @out = nil + + self end def close_and_cache_err - @error_cache = read @err + @error_cache = read(@err) @err.close @err = nil + + self end end + # rubocop:enable Metrics/ClassLength end end diff --git a/lib/aruba/rspec.rb b/lib/aruba/rspec.rb index 39de0b6a1..4858f0ca3 100644 --- a/lib/aruba/rspec.rb +++ b/lib/aruba/rspec.rb @@ -1,4 +1,6 @@ require 'rspec/core' + +require 'aruba' require 'aruba/api' require 'aruba/reporting' @@ -6,12 +8,18 @@ config.include Aruba::Api, type: :aruba config.before :each do - next unless self.class.include?(Aruba::Api) + next unless self.class.include? Aruba::Api restore_env clean_current_directory end + config.before :each do |example| + next unless self.class.include? Aruba::Api + + example.metadata.each { |k, v| aruba.config.set_if_option(k, v) } + end + # config.before do # next unless self.class.include?(Aruba::Api) diff --git a/lib/aruba/runtime.rb b/lib/aruba/runtime.rb new file mode 100644 index 000000000..81107a34a --- /dev/null +++ b/lib/aruba/runtime.rb @@ -0,0 +1,24 @@ +module Aruba + class Runtime + attr_reader :config, :command_monitor, :announcer, :event_queue + + def initialize + @config = Aruba.config.deep_dup + @event_queue = Aruba::EventQueue.new + @announcer = Aruba::Announcer.new + @command_monitor = Aruba::CommandMonitor.new(event_queue: @event_queue) + + # register_events + end + + private + + def register_events + event_queue.register :command_started, announcer + event_queue.register :command_stopped, announcer + event_queue.register :add_environment_variable, announcer + event_queue.register :switched_working_directory, announcer +# event_queue.register :switched_working_directory, command_monitor + end + end +end diff --git a/script/bootstrap b/script/bootstrap index 52096627c..7bbcaf0bc 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -13,10 +13,8 @@ echo -ne "$info_msg Checking if ruby installed? " which 'ruby' >/dev/null 2>error.log || ( echo -e "$error_msg\n\nCould not find \`ruby\`. Please install ruby or add it to PATH"; output_error_log; exit 1 ) echo OK -echo -en "$info_msg rubygem \"bundler\" " -gem install bundler >/dev/null 2>error.log || ( echo -e "$error_msg\n\nAn error occurred during installation of bundler. Run \`gem install bundler\` yourself."; output_error_log; exit 1 ) -echo OK +echo "$info_msg rubygem \"bundler\" " +gem install bundler -echo -en "$info_msg \"bundle install\" " -bundle install $* >/dev/null 2>error.log || ( echo -e "$error_msg\n\nAn error occurred during bundle install. Run \`bundle install\` yourself."; output_error_log; exit 1 ) -echo OK +echo "$info_msg \"bundle install\" " +bundle install diff --git a/spec/aruba/api/commands_spec.rb b/spec/aruba/api/commands_spec.rb new file mode 100644 index 000000000..e75ca1bbe --- /dev/null +++ b/spec/aruba/api/commands_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +RSpec.describe 'Commands' do + include_context 'uses aruba API' + + describe "#get_process" do + before(:each){ @aruba.run_simple 'true' } + + after(:each){ @aruba.stop_commands } + + context 'when existing process' do + it { expect(@aruba.get_process('true')).not_to be nil } + end + + context 'when non-existing process' do + it { expect{ @aruba.get_process('false') }.to raise_error(ArgumentError, "No command named 'false' has been started") } + end + end + + describe "#run_simple" do + after(:each){ @aruba.stop_commands } + + context 'when simple command' do + it { expect { @aruba.run_simple "true" }.not_to raise_error } + end + + context 'when long running command' do + it { expect { @aruba.run_simple 'bash -c "sleep 7"' }.not_to raise_error } + end + end + + describe "#run" do + context 'when interactive comand' do + before(:each){ @aruba.run 'cat' } + + after(:each){ @aruba.stop_commands } + + context 'when input is given' do + before :each do + @aruba.type "Hello" + @aruba.type "" + end + + it { expect(@aruba.all_output).to eq "Hello\n" } + end + + context 'when input is closed' do + before :each do + @aruba.type "Hello" + @aruba.close_input + end + + it { expect(@aruba.all_output).to eq "Hello\n" } + it { expect(@aruba.type).to raise_error } + end + + context 'when data is piped into it' do + before :each do + @aruba.write_file(@file_name, "Hello\nWorld!") + @aruba.pipe_in_file(@file_name) + @aruba.close_input + end + + it { expect(@aruba.all_output).to eq "Hello\nWorld!" } + end + end + end +end diff --git a/spec/aruba/api/runtime_spec.rb b/spec/aruba/api/runtime_spec.rb new file mode 100644 index 000000000..0417f1f61 --- /dev/null +++ b/spec/aruba/api/runtime_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +RSpec.describe 'aruba' do + describe '#config' do + subject(:config) { aruba.config } + + context 'when initialized' do + it { is_expected.to eq Aruba.config } + end + + context 'when changed earlier' do + context 'values when init' do + let(:value) { 20 } + before(:each) { aruba.config.io_wait_timeout = value } + + it { expect(config.io_wait_timeout).to eq value } + end + + context 'default value' do + let(:value) { 0.1 } # Aruba.config.io_wait_timeout + + it { expect(config.io_wait_timeout).to eq value } + end + end + end +end diff --git a/spec/aruba/api_spec.rb b/spec/aruba/api_spec.rb index 9667ddc96..2411a604b 100644 --- a/spec/aruba/api_spec.rb +++ b/spec/aruba/api_spec.rb @@ -1064,7 +1064,7 @@ def actuctual_permissions describe 'tags' do describe '@announce_stdout' do - after(:each){@aruba.stop_processes!} + after(:each){@aruba.stop_commands} context 'enabled' do before :each do @aruba.send(:announcer).activate(:stdout) @@ -1095,7 +1095,7 @@ def actuctual_permissions describe "#assert_not_matching_output" do before(:each){ @aruba.run_simple("echo foo", false) } - after(:each){ @aruba.stop_processes! } + after(:each){ @aruba.stop_commands } it "passes when the output doesn't match a regexp" do @aruba.assert_not_matching_output "bar", @aruba.all_output @@ -1107,47 +1107,8 @@ def actuctual_permissions end end - describe "#run_interactive" do - before(:each){@aruba.run_interactive "cat"} - after(:each){@aruba.stop_processes!} - it "respond to input" do - @aruba.type "Hello" - @aruba.type "" - expect(@aruba.all_output).to eq "Hello\n" - end - - it "respond to close_input" do - @aruba.type "Hello" - @aruba.close_input - expect(@aruba.all_output).to eq "Hello\n" - end - - it "pipes data" do - @aruba.write_file(@file_name, "Hello\nWorld!") - @aruba.pipe_in_file(@file_name) - @aruba.close_input - expect(@aruba.all_output).to eq "Hello\nWorld!" - end - end - - describe "#run_simple" do - before(:each){@aruba.run_simple "true"} - after(:each){@aruba.stop_processes!} - describe "get_process" do - it "returns a process" do - expect(@aruba.get_process("true")).not_to be(nil) - end - - it "raises a descriptive exception" do - expect do - expect(@aruba.get_process("false")).not_to be(nil) - end.to raise_error(ArgumentError, "No process named 'false' has been started") - end - end - end - describe 'fixtures' do - let(:aruba) do + let(:api) do klass = Class.new do include Aruba::Api @@ -1162,33 +1123,33 @@ def root_directory describe '#fixtures_directory' do context 'when no fixtures directories exist' do it "should raise exception" do - expect { aruba.fixtures_directory }.to raise_error + expect { api.fixtures_directory }.to raise_error end end context 'when "/features/fixtures"-directory exist' do - before(:each) { aruba.create_directory('features/fixtures') } + before(:each) { api.create_directory('features/fixtures') } - it { expect(aruba.fixtures_directory).to eq expand_path('features/fixtures') } + it { expect(api.fixtures_directory).to eq expand_path('features/fixtures') } end context 'when "/spec/fixtures"-directory exist' do - before(:each) { aruba.create_directory('spec/fixtures') } + before(:each) { api.create_directory('spec/fixtures') } - it { expect(aruba.fixtures_directory).to eq expand_path('spec/fixtures') } + it { expect(api.fixtures_directory).to eq expand_path('spec/fixtures') } end context 'when "/test/fixtures"-directory exist' do - before(:each) { aruba.create_directory('test/fixtures') } + before(:each) { api.create_directory('test/fixtures') } - it { expect(aruba.fixtures_directory).to eq expand_path('test/fixtures') } + it { expect(api.fixtures_directory).to eq expand_path('test/fixtures') } end end end describe "#set_env" do after(:each) do - @aruba.stop_processes! + @aruba.stop_commands @aruba.restore_env end it "set environment variable" do @@ -1205,7 +1166,7 @@ def root_directory end describe "#restore_env" do - after(:each){@aruba.stop_processes!} + after(:each){@aruba.stop_commands} it "restores environment variable" do @aruba.set_env 'LONG_LONG_ENV_VARIABLE', 'true' @aruba.restore_env diff --git a/spec/aruba/basic_configuration_spec.rb b/spec/aruba/basic_configuration_spec.rb new file mode 100644 index 000000000..aba828e88 --- /dev/null +++ b/spec/aruba/basic_configuration_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe Aruba::BasicConfiguration do + it_behaves_like 'a basic configuration' +end diff --git a/spec/aruba/config_wrapper_spec.rb b/spec/aruba/config_wrapper_spec.rb new file mode 100644 index 000000000..2e1c65764 --- /dev/null +++ b/spec/aruba/config_wrapper_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +RSpec.describe Aruba::ConfigWrapper do + subject(:wrapper) { described_class.new(config) } + + let(:config) { {} } + + context 'when option is defined' do + before :each do + config[:opt] = true + end + + context 'when valid' do + it { expect(wrapper.opt).to be true } + end + + context 'when one tries to pass arguments to option' do + it { expect{ wrapper.opt('arg') }.to raise_error ArgumentError, 'Options take no argument' } + end + end + + context 'when option is not defined' do + it { expect{ wrapper.opt }.to raise_error ArgumentError, 'Option "opt" is unknown. Please use only earlier defined options' } + end +end diff --git a/spec/aruba/configuration_spec.rb b/spec/aruba/configuration_spec.rb new file mode 100644 index 000000000..a6ee67b1d --- /dev/null +++ b/spec/aruba/configuration_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe Aruba::Configuration do + it_behaves_like 'a basic configuration' +end diff --git a/spec/aruba/event_queue_spec.rb b/spec/aruba/event_queue_spec.rb new file mode 100644 index 000000000..09bcc4fa1 --- /dev/null +++ b/spec/aruba/event_queue_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +RSpec.describe EventQueue do + subject(:queue) { described_class.new } + + let(:listener) { double('Listener') } + + context 'when valid listener' do + before :each do + expect(listener).to receive(:notify) + end + + before :each do + queue.register :event1, listener + end + + it { queue.notify :event1, 'Hello World' } + end +end diff --git a/spec/aruba/jruby_spec.rb b/spec/aruba/jruby_spec.rb index 7edf69ceb..f7bbf499a 100644 --- a/spec/aruba/jruby_spec.rb +++ b/spec/aruba/jruby_spec.rb @@ -1,39 +1,55 @@ require 'spec_helper' -require 'aruba/config' require 'aruba/api' describe "Aruba JRuby Startup Helper" do before(:all) do @fake_env = ENV.clone end - before(:each) do - Aruba.config = Aruba::Config.new + before :each do + Aruba.configure(&:reset) + + # Define before_cmd-hook + load 'aruba/jruby.rb' + end + + before(:each) do @fake_env['JRUBY_OPTS'] = "--1.9" @fake_env['JAVA_OPTS'] = "-Xdebug" stub_const('ENV', @fake_env) end - it 'configuration does not load when RUBY_PLATFORM is not java' do - stub_const('RUBY_PLATFORM', 'x86_64-chocolate') + context 'when some mri ruby' do + before :each do + stub_const('RUBY_PLATFORM', 'x86_64-chocolate') + end - load 'aruba/jruby.rb' - Aruba.config.hooks.execute :before_cmd, self - expect(ENV['JRUBY_OPTS']).to eq "--1.9" - expect(ENV['JAVA_OPTS']).to eq "-Xdebug" + before :each do + Aruba.config.hooks.execute :before_cmd, self + end + + it { expect(ENV['JRUBY_OPTS']).to eq '--1.9' } + it { expect(ENV['JAVA_OPTS']).to eq '-Xdebug' } end - it 'configuration loads for java and merges existing environment variables' do - stub_const('RUBY_PLATFORM', 'java') + context 'when jruby ruby' do + before :each do + stub_const('RUBY_PLATFORM', 'java') + end - rb_config = double('rb_config') - allow(rb_config).to receive(:[]).and_return('solaris') - stub_const 'RbConfig::CONFIG', rb_config + before :each do + rb_config = double('rb_config') + allow(rb_config).to receive(:[]).and_return('solaris') - load 'aruba/jruby.rb' - Aruba.config.hooks.execute :before_cmd, self - expect(ENV['JRUBY_OPTS']).to eq "-X-C --1.9" - expect(ENV['JAVA_OPTS']).to eq "-d32 -Xdebug" + stub_const 'RbConfig::CONFIG', rb_config + end + + before :each do + Aruba.config.hooks.execute :before_cmd, self + end + + it { expect(ENV['JRUBY_OPTS']).to eq '-X-C --1.9' } + it { expect(ENV['JAVA_OPTS']).to eq '-d32 -Xdebug' } end end diff --git a/spec/aruba/matchers/command_spec.rb b/spec/aruba/matchers/command_spec.rb index 78400e50e..013b19b07 100644 --- a/spec/aruba/matchers/command_spec.rb +++ b/spec/aruba/matchers/command_spec.rb @@ -3,14 +3,6 @@ RSpec.describe 'Command Matchers' do include_context 'uses aruba API' - def expand_path(*args) - @aruba.expand_path(*args) - end - - def announcer(*args) - @aruba.send(:announcer, *args) - end - describe '#to_have_exit_status' do let(:cmd) { 'true' } diff --git a/spec/aruba/rspec_spec.rb b/spec/aruba/rspec_spec.rb new file mode 100644 index 000000000..1a07f7ee0 --- /dev/null +++ b/spec/aruba/rspec_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +RSpec.describe 'RSpec Integration', type: :aruba do + describe 'Configuration' do + subject(:config) { aruba.config } + + context 'when io_wait_timeout is 0.5', io_wait_timeout: 0.5 do + it { expect(config.io_wait_timeout).to be 0.5 } + end + + context 'when io_wait_timeout is 0.1' do + it { expect(config.io_wait_timeout).to be 0.1 } + end + end +end diff --git a/spec/aruba/spawn_process_spec.rb b/spec/aruba/spawn_process_spec.rb index 37465e262..741e79399 100644 --- a/spec/aruba/spawn_process_spec.rb +++ b/spec/aruba/spawn_process_spec.rb @@ -9,7 +9,7 @@ let(:working_directory) { Dir.getwd } describe "#stdout" do - before(:each) { process.run! } + before(:each) { process.start } context 'when invoked once' do it { expect(process.stdout).to eq "yo\n" } @@ -23,7 +23,7 @@ describe "#stderr" do let(:command) { 'features/fixtures/spawn_process/stderr.sh yo' } - before(:each) { process.run! } + before(:each) { process.start } context 'when invoked once' do it { expect(process.stderr).to eq "yo\n" } @@ -35,29 +35,27 @@ end describe "#stop" do - let(:reader) { double('reader') } + context 'when started' do + before(:each) { process.start } + before(:each) { process.stop } - before(:each) { process.run! } - - before :each do - allow(reader).to receive(:stderr) - expect(reader).to receive(:stdout).with("yo\n") + it { expect(process).to be_stopped } end - context 'when stopped successfully' do - it { process.stop(reader) } + context 'when long running process' do + end end - describe "#run!" do + describe "#start" do context "when process run succeeds" do - it { expect { process.run! }.not_to raise_error } + it { expect { process.start }.not_to raise_error } end context "when process run fails" do let(:command) { 'does_not_exists' } - it { expect {process.run!}.to raise_error Aruba::LaunchError } + it { expect {process.start}.to raise_error Aruba::LaunchError } end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 82936be81..14a56354f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,6 +10,9 @@ require 'bundler' Bundler.require +# Activate RSpec Integration +require 'aruba/rspec' + # Loading support files Dir.glob(::File.expand_path('../support/*.rb', __FILE__)).each { |f| require_relative f } Dir.glob(::File.expand_path('../support/**/*.rb', __FILE__)).each { |f| require_relative f } diff --git a/spec/support/configs/pry.rb b/spec/support/configs/pry.rb new file mode 100644 index 000000000..bab273e1d --- /dev/null +++ b/spec/support/configs/pry.rb @@ -0,0 +1 @@ +ENV['PRYRC'] = File.expand_path('~/.pryrc') diff --git a/spec/support/matchers/option.rb b/spec/support/matchers/option.rb new file mode 100644 index 000000000..babaaa96f --- /dev/null +++ b/spec/support/matchers/option.rb @@ -0,0 +1,31 @@ +RSpec::Matchers.define :be_valid_option do |_| + match do |actual| + subject.option?(actual) + end + + failure_message do |actual| + format("expected that \"%s\" is a valid option", actual) + end + + failure_message_when_negated do |actual| + format("expected that \"%s\" is not a valid option", actual) + end +end + +RSpec::Matchers.define :have_option_value do |expected| + match do |actual| + @old_actual = actual + @actual = subject.public_send(actual.to_sym) + values_match? expected, @actual + end + + diffable + + failure_message do |_actual| + format(%(expected that option "%s" has value "%s"), @old_actual, expected) + end + + failure_message_when_negated do |_actual| + format(%(expected that option "%s" does not have value "%s"), @old_actual, expected) + end +end diff --git a/spec/support/shared_contexts/aruba.rb b/spec/support/shared_contexts/aruba.rb index 79d28117d..4ef1ad3f5 100644 --- a/spec/support/shared_contexts/aruba.rb +++ b/spec/support/shared_contexts/aruba.rb @@ -13,6 +13,14 @@ def create_test_files(files, data = 'a') end end + def expand_path(*args) + @aruba.expand_path(*args) + end + + def announcer(*args) + @aruba.send(:announcer, *args) + end + before(:each) do klass = Class.new do include Aruba::Api diff --git a/spec/support/shared_examples/configuration.rb b/spec/support/shared_examples/configuration.rb new file mode 100644 index 000000000..e28943bfc --- /dev/null +++ b/spec/support/shared_examples/configuration.rb @@ -0,0 +1,116 @@ +RSpec.shared_examples 'a basic configuration' do + subject(:config) do + Class.new(described_class) do + option_accessor :use_test, contract: { Contracts::Bool => Contracts::Bool }, default: false + end.new + end + + it { expect(config).not_to be_nil } + + describe '.option_reader' do + let(:config_klass) { Class.new(described_class) } + + subject(:config) { config_klass.new } + + before :each do + config_klass.option_reader :new_opt, contract: { Contracts::Num => Contracts::Num }, default: 1 + end + + context 'when value is read' do + it { expect(config.new_opt).to eq 1 } + end + + context 'when one tries to write a value' do + it { expect{ config.new_opt = 1}.to raise_error NoMethodError } + end + + context 'when block is defined' do + before :each do + config_klass.option_reader :new_opt2, contract: { Contracts::Num => Contracts::Num } do |c| + c.new_opt.value + 1 + end + end + + it { expect(config.new_opt2).to eq 2 } + end + + context 'when block and default value is defined' do + it do + expect do + config_klass.option_accessor :new_opt2, contract: { Contracts::Num => Contracts::Num }, default: 2 do |c| + c.new_opt.value + 1 + end + end.to raise_error ArgumentError, 'Either use block or default value' + end + end + end + + describe '.option_accessor' do + let(:config_klass) { Class.new(described_class) } + + subject(:config) { config_klass.new } + + before :each do + config_klass.option_accessor :new_opt, contract: { Contracts::Num => Contracts::Num }, default: 1 + end + + context 'when default is used' do + it { expect(config.new_opt).to eq 1 } + end + + context 'when is modified' do + before(:each) { config.new_opt = 2 } + + it { expect(config.new_opt).to eq 2 } + end + + context 'when block is defined' do + before :each do + config_klass.option_accessor :new_opt2, contract: { Contracts::Num => Contracts::Num } do |c| + c.new_opt.value + 1 + end + end + + it { expect(config.new_opt2).to eq 2 } + end + + context 'when block and default value is defined' do + it do + expect do + config_klass.option_accessor :new_opt2, contract: { Contracts::Num => Contracts::Num }, default: 2 do |c| + c.new_opt1 + 1 + end + end.to raise_error ArgumentError, 'Either use block or default value' + end + end + end + + describe 'option?' do + let(:name) { :use_test } + + context 'when valid option' do + it { expect(name).to be_valid_option } + end + + context 'when invalid_option' do + let(:name) { :blub } + it { expect(name).not_to be_valid_option } + end + end + + describe 'set_if_option' do + let(:name) { :use_test } + let(:value) { true } + + context 'when valid option' do + before(:each) { config.set_if_option(name, value) } + it { expect(name).to have_option_value true } + end + + context 'when invalid_option' do + let(:name) { :blub } + + it { expect { config.set_if_option(name, value) }.not_to raise_error } + end + end +end