From 4c9ad2611554b1261d4eff17d9da6277d06d3fdf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Nov 2011 21:09:38 -0700 Subject: [PATCH] Acceptance tests starting code is in. Version is an example. --- .gitignore | 1 + Rakefile | 42 ++++++++++++ test/acceptance/base.rb | 50 +++++++++++++++ test/acceptance/helpers/config.rb | 23 +++++++ .../helpers/isolated_environment.rb | 64 +++++++++++++++++++ test/acceptance/helpers/tempdir.rb | 34 ++++++++++ test/acceptance/version_test.rb | 8 +++ vagrant.gemspec | 2 + 8 files changed, 224 insertions(+) create mode 100644 test/acceptance/base.rb create mode 100644 test/acceptance/helpers/config.rb create mode 100644 test/acceptance/helpers/isolated_environment.rb create mode 100644 test/acceptance/helpers/tempdir.rb create mode 100644 test/acceptance/version_test.rb diff --git a/.gitignore b/.gitignore index a7ac018fe..bee88a907 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .DS_Store # Vagrant stuff +acceptance_config.yml Vagrantfile .vagrant diff --git a/Rakefile b/Rakefile index f5f6bdf2e..ed51535c9 100644 --- a/Rakefile +++ b/Rakefile @@ -9,3 +9,45 @@ Rake::TestTask.new do |t| t.libs << "test/unit" t.pattern = 'test/unit/**/*_test.rb' end + +namespace :acceptance do + desc "Generates the configuration for acceptance tests from current source." + task :config do + require 'yaml' + require 'posix-spawn' + + require File.expand_path("../lib/vagrant/version", __FILE__) + require File.expand_path('../test/acceptance/helpers/tempdir', __FILE__) + + # Generate the binstubs for the Vagrant binary + tempdir = Tempdir.new + pid, stdin, stdout, stderr = + POSIX::Spawn.popen4("bundle", "install", "--binstubs", tempdir.path) + pid, status = Process.waitpid2(pid) + if status.exitstatus != 0 + # Bundle install failed... + puts "Bundle install failed! Error:" + puts stderr.read + exit 1 + end + + # Generate the actual configuration + config = { + "vagrant_path" => File.join(tempdir.path, "vagrant"), + "vagrant_version" => Vagrant::VERSION, + "env" => { + "BUNDLE_GEMFILE" => File.expand_path("../Gemfile", __FILE__) + } + } + + File.open("acceptance_config.yml", "w+") do |f| + f.write(YAML.dump(config)) + end + + puts <<-OUTPUT +Acceptance test configuration is now in this directory in +"acceptance_config.yml." Set your ACCEPTANCE_CONFIG environmental +variable to this file and run any of the acceptance tests now. +OUTPUT + end +end diff --git a/test/acceptance/base.rb b/test/acceptance/base.rb new file mode 100644 index 000000000..ffd8308a2 --- /dev/null +++ b/test/acceptance/base.rb @@ -0,0 +1,50 @@ +require "contest" +require "log4r" + +require File.expand_path("../helpers/isolated_environment", __FILE__) +require File.expand_path("../helpers/config.rb", __FILE__) + +# Enable logging if requested +if ENV["ACCEPTANCE_LOGGING"] + logger = Log4r::Logger.new("acceptance") + logger.outputters = Log4r::Outputter.stdout + logger.level = Log4r.const_get(ENV["ACCEPTANCE_LOGGING"].upcase) + logger = nil +end + +# Parse the command line options and load the global configuration. +if !ENV.has_key?("ACCEPTANCE_CONFIG") + $stderr.puts "A configuration file must be passed into the acceptance test." + exit +elsif !File.file?(ENV["ACCEPTANCE_CONFIG"]) + $stderr.puts "The configuration file must exist." + exit +end + +$acceptance_options = Acceptance::Config.new(ENV["ACCEPTANCE_CONFIG"]) + +class AcceptanceTest < Test::Unit::TestCase + # This method is a shortcut to give access to the global configuration + # setup by the acceptance tests. + def config + $acceptance_options + end + + # Executes the given command in the isolated environment. This + # is just a shortcut to IsolatedEnvironment#execute. + # + # @return [Object] + def execute(*args) + @environment.execute(*args) + end + + setup do + # Setup the environment so that we have an isolated area + # to run Vagrant. We do some configuration here as well in order + # to replace "vagrant" with the proper path to Vagrant as well + # as tell the isolated environment about custom environmental + # variables to pass in. + apps = { "vagrant" => config.vagrant_path } + @environment = Acceptance::IsolatedEnvironment.new(apps, config.env) + end +end diff --git a/test/acceptance/helpers/config.rb b/test/acceptance/helpers/config.rb new file mode 100644 index 000000000..21524a7d1 --- /dev/null +++ b/test/acceptance/helpers/config.rb @@ -0,0 +1,23 @@ +require "yaml" + +require "log4r" + +module Acceptance + # This represents a configuration object for acceptance tests. + class Config + attr_reader :vagrant_path + attr_reader :vagrant_version + attr_reader :env + + def initialize(path) + @logger = Log4r::Logger.new("acceptance::config") + @logger.info("Loading configuration from: #{path}") + options = YAML.load_file(path) + @logger.info("Loaded: #{options.inspect}") + + @vagrant_path = options["vagrant_path"] + @vagrant_version = options["vagrant_version"] + @env = options["env"] + end + end +end diff --git a/test/acceptance/helpers/isolated_environment.rb b/test/acceptance/helpers/isolated_environment.rb new file mode 100644 index 000000000..a3a58753e --- /dev/null +++ b/test/acceptance/helpers/isolated_environment.rb @@ -0,0 +1,64 @@ +require "log4r" +require "posix-spawn" + +require File.expand_path("../tempdir", __FILE__) + +module Acceptance + # This class manages an isolated environment for Vagrant to + # run in. It creates a temporary directory to act as the + # working directory as well as sets a custom home directory. + class IsolatedEnvironment + include POSIX::Spawn + + # Initializes an isolated environment. You can pass in some + # options here to configure runing custom applications in place + # of others as well as specifying environmental variables. + # + # @param [Hash] apps A mapping of application name (such as "vagrant") + # to an alternate full path to the binary to run. + # @param [Hash] env Additional environmental variables to inject + # into the execution environments. + def initialize(apps=nil, env=nil) + @logger = Log4r::Logger.new("acceptance::isolated_environment") + + @apps = apps || {} + @env = env || {} + + # Create a temporary directory for our work + @tempdir = Tempdir.new("vagrant") + @logger.info("Initialize isolated environment: #{@tempdir.path}") + end + + # Executes a command in the context of this isolated environment. + # Any command executed will therefore see our temporary directory + # as the home directory. + def execute(command, *argN) + command = @apps[command] if @apps.has_key?(command) + + # Execute in a separate process, wait for it to complete, and + # return the IO streams. + @logger.info("Executing: #{command} #{argN.inspect}") + pid, stdin, stdout, stderr = popen4(@env, command, *argN, :chdir => @tempdir.path) + _pid, status = Process.waitpid2(pid) + @logger.info("Exit status: #{status.exitstatus}") + + return ExecuteProcess.new(status.exitstatus, stdout, stderr) + end + end + + # This class represents a process which has run via the IsolatedEnvironment. + # This is a readonly structure that can be used to inspect the exit status, + # stdout, stderr, etc. from the process which ran. + class ExecuteProcess + attr_reader :exit_status + attr_reader :stdout + attr_reader :stderr + + def initialize(exit_status, stdout, stderr) + @exit_status = exit_status + @stdout = stdout + @stderr = stderr + end + end +end + diff --git a/test/acceptance/helpers/tempdir.rb b/test/acceptance/helpers/tempdir.rb new file mode 100644 index 000000000..21984757f --- /dev/null +++ b/test/acceptance/helpers/tempdir.rb @@ -0,0 +1,34 @@ +require 'fileutils' +require 'tempfile' + +# This class provides an easy way of creating a temporary +# directory and having it removed when the application exits. +# +# TODO: This class doesn't currently delete the temporary +# directory on exit. +class Tempdir + attr_reader :path + + def initialize(basename="vagrant") + @path = nil + + # Loop and attempt to create a temporary directory until + # it succeeds. + while @path.nil? + file = Tempfile.new(basename) + @path = file.path + file.unlink + + begin + Dir.mkdir(@path) + rescue + @path = nil + end + end + end + + # This deletes the temporary directory. + def unlink + FileUtils.rm_rf(@path) + end +end diff --git a/test/acceptance/version_test.rb b/test/acceptance/version_test.rb new file mode 100644 index 000000000..f7f12506a --- /dev/null +++ b/test/acceptance/version_test.rb @@ -0,0 +1,8 @@ +require File.expand_path("../base", __FILE__) + +class VersionTest < AcceptanceTest + should "print the version to stdout" do + result = execute("vagrant", "version") + assert_equal("Vagrant version #{config.vagrant_version}\n", result.stdout.read) + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index 41c648e70..5b278bde3 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -25,8 +25,10 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "contest", ">= 0.1.2" + s.add_development_dependency "log4r", "~> 1.1.9" s.add_development_dependency "minitest", "~> 2.5.1" s.add_development_dependency "mocha" + s.add_development_dependency "posix-spawn", "~> 0.3.6" s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact