From e7399f46925fab0facb0c05fc3a371f52e7a46c5 Mon Sep 17 00:00:00 2001 From: seenmyfate Date: Fri, 27 Sep 2013 11:45:55 +0100 Subject: [PATCH] Integration tests with Vagrant and Cucumber This commit removes the existing 'local' integration tests and replaces them with Cucumber features running against VMs. At this stage, some of the assertions are pending due to the limited nature of the response returned when executing commands through Vagrant, but the framework is there as a starting point to build upon. To run the suite: bundle exec cucumber During development, avoid scraping the VM between runs: bundle exec cucumber KEEPING_RUNNING=1 Ultimately I would like to see the `TestApp` helpers along with the Vagrant integration packaged and available for use when developing gems that work with Cap. For now though, this closes #641 --- capistrano.gemspec | 3 + features/deploy.feature | 52 +++++++++++++ features/installation.feature | 16 ++++ features/remote_file_task.feature | 14 ++++ features/step_definitions/assertions.rb | 90 ++++++++++++++++++++++ features/step_definitions/cap_commands.rb | 8 ++ features/step_definitions/setup.rb | 25 ++++++ features/support/env.rb | 12 +++ features/support/remote_command_helpers.rb | 20 +++++ spec/integration/deploy_finalize_spec.rb | 34 -------- spec/integration/deploy_finished_spec.rb | 38 --------- spec/integration/deploy_started_spec.rb | 74 ------------------ spec/integration/deploy_update_spec.rb | 44 ----------- spec/integration/installation_spec.rb | 76 ------------------ spec/integration/remote_file_task_spec.rb | 51 ------------ spec/spec_helper.rb | 1 - spec/support/.gitignore | 1 + spec/support/Vagrantfile | 13 ++++ spec/support/test_app.rb | 49 ++++++++++-- 19 files changed, 297 insertions(+), 324 deletions(-) create mode 100644 features/deploy.feature create mode 100644 features/installation.feature create mode 100644 features/remote_file_task.feature create mode 100644 features/step_definitions/assertions.rb create mode 100644 features/step_definitions/cap_commands.rb create mode 100644 features/step_definitions/setup.rb create mode 100644 features/support/env.rb create mode 100644 features/support/remote_command_helpers.rb delete mode 100644 spec/integration/deploy_finalize_spec.rb delete mode 100644 spec/integration/deploy_finished_spec.rb delete mode 100644 spec/integration/deploy_started_spec.rb delete mode 100644 spec/integration/deploy_update_spec.rb delete mode 100644 spec/integration/installation_spec.rb delete mode 100644 spec/integration/remote_file_task_spec.rb create mode 100644 spec/support/.gitignore create mode 100644 spec/support/Vagrantfile diff --git a/capistrano.gemspec b/capistrano.gemspec index 5a6ad8a4..e95bf980 100644 --- a/capistrano.gemspec +++ b/capistrano.gemspec @@ -28,5 +28,8 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rspec' gem.add_development_dependency 'mocha' + gem.add_development_dependency 'vagrant', '~> 1.0.7' + gem.add_development_dependency 'kuroko' + gem.add_development_dependency 'cucumber' end diff --git a/features/deploy.feature b/features/deploy.feature new file mode 100644 index 00000000..5342b3bc --- /dev/null +++ b/features/deploy.feature @@ -0,0 +1,52 @@ +Feature: Deploy + + Background: + Given a test app with the default configuration + And servers with the roles app and web + + Scenario: Creating the repo + When I run cap "git:check" + Then references in the remote repo are listed + + Scenario: Creating the directory structure + When I run cap "deploy:check:directories" + Then the shared path is created + And the releases path is created + + Scenario: Creating linked directories + When I run cap "deploy:check:linked_dirs" + Then directories in :linked_dirs are created in shared + + Scenario: Creating linked directories for linked files + When I run cap "deploy:check:make_linked_dirs" + Then directories referenced in :linked_files are created in shared + + Scenario: Checking linked files - missing file + Given a required file + But the file does not exist + When I run cap "deploy:check:linked_files" + Then the task will exit + + Scenario: Checking linked files - file exists + Given a required file + And that file exists + When I run cap "deploy:check:linked_files" + Then the task will be successful + + Scenario: Creating a release + When I run cap "git:create_release" as part of a release + Then the repo is cloned + And the release is created + + Scenario: Symlink linked files + When I run cap "deploy:symlink:linked_files" as part of a release + Then file symlinks are created in the new release + + Scenario: Symlink linked dirs + When I run cap "deploy:symlink:linked_dirs" as part of a release + Then directory symlinks are created in the new release + + Scenario: Publishing + When I run cap "deploy:symlink:release" + Then the current directory will be a symlink to the release + diff --git a/features/installation.feature b/features/installation.feature new file mode 100644 index 00000000..20d23296 --- /dev/null +++ b/features/installation.feature @@ -0,0 +1,16 @@ +Feature: Installation + + Background: + Given a test app with the default configuration + + Scenario: With default stages + When I run cap "install" + Then the deploy.rb file is created + And the default stage files are created + And the tasks folder is created + + Scenario: With specified stages + When I run cap "install STAGES=qa,production" + Then the deploy.rb file is created + And the specified stage files are created + And the tasks folder is created diff --git a/features/remote_file_task.feature b/features/remote_file_task.feature new file mode 100644 index 00000000..6c2ac640 --- /dev/null +++ b/features/remote_file_task.feature @@ -0,0 +1,14 @@ +Feature: Remote file task + + Background: + Given a test app with the default configuration + And a custom task to generate a file + And servers with the roles app and web + + Scenario: Where the file does not exist + When I run cap "deploy:check:linked_files" + Then it creates the file with the remote_task prerequisite + + Scenario: Where the file already exists + When I run cap "deploy:check:linked_files" + Then it will not recreate the file diff --git a/features/step_definitions/assertions.rb b/features/step_definitions/assertions.rb new file mode 100644 index 00000000..efa002cd --- /dev/null +++ b/features/step_definitions/assertions.rb @@ -0,0 +1,90 @@ +Then(/^references in the remote repo are listed$/) do +end + +Then(/^the shared path is created$/) do + run_vagrant_command(test_dir_exists(TestApp.shared_path)) +end + +Then(/^the releases path is created$/) do + run_vagrant_command(test_dir_exists(TestApp.releases_path)) +end + +Then(/^directories in :linked_dirs are created in shared$/) do + TestApp.linked_dirs.each do |dir| + run_vagrant_command(test_dir_exists(TestApp.shared_path.join(dir))) + end +end + +Then(/^directories referenced in :linked_files are created in shared$/) do + dirs = TestApp.linked_files.map { |path| TestApp.shared_path.join(path).dirname } + dirs.each do | dir| + run_vagrant_command(test_dir_exists(dir)) + end +end + +Then(/^the task will be successful$/) do +end + + +Then(/^the task will exit$/) do +end + +Then(/^the repo is cloned$/) do + run_vagrant_command(test_dir_exists(TestApp.repo_path)) +end + +Then(/^the release is created$/) do + run_vagrant_command("ls -g #{TestApp.releases_path}") +end + +Then(/^file symlinks are created in the new release$/) do + pending + TestApp.linked_files.each do |file| + run_vagrant_command(test_symlink_exists(TestApp.release_path.join(file))) + end +end + +Then(/^directory symlinks are created in the new release$/) do + pending + TestApp.linked_dirs.each do |dir| + run_vagrant_command(test_symlink_exists(TestApp.release_path.join(dir))) + end +end + +Then(/^the current directory will be a symlink to the release$/) do + run_vagrant_command(test_symlink_exists(TestApp.current_path)) +end + +Then(/^the deploy\.rb file is created$/) do + file = TestApp.test_app_path.join('config/deploy.rb') + expect(File.exists?(file)).to be_true +end + +Then(/^the default stage files are created$/) do + staging = TestApp.test_app_path.join('config/deploy/staging.rb') + production = TestApp.test_app_path.join('config/deploy/production.rb') + expect(File.exists?(staging)).to be_true + expect(File.exists?(production)).to be_true +end + +Then(/^the tasks folder is created$/) do + path = TestApp.test_app_path.join('lib/capistrano/tasks') + expect(Dir.exists?(path)).to be_true +end + +Then(/^the specified stage files are created$/) do + qa = TestApp.test_app_path.join('config/deploy/qa.rb') + production = TestApp.test_app_path.join('config/deploy/production.rb') + expect(File.exists?(qa)).to be_true + expect(File.exists?(production)).to be_true +end + +Then(/^it creates the file with the remote_task prerequisite$/) do + TestApp.linked_files.each do |file| + run_vagrant_command(test_file_exists(TestApp.shared_path.join(file))) + end +end + +Then(/^it will not recreate the file$/) do + # +end diff --git a/features/step_definitions/cap_commands.rb b/features/step_definitions/cap_commands.rb new file mode 100644 index 00000000..f5b5c14d --- /dev/null +++ b/features/step_definitions/cap_commands.rb @@ -0,0 +1,8 @@ +When(/^I run cap "(.*?)"$/) do |task| + TestApp.cap(task) +end + +When(/^I run cap "(.*?)" as part of a release$/) do |task| + TestApp.cap("deploy:new_release_path #{task}") +end + diff --git a/features/step_definitions/setup.rb b/features/step_definitions/setup.rb new file mode 100644 index 00000000..4d16620a --- /dev/null +++ b/features/step_definitions/setup.rb @@ -0,0 +1,25 @@ +Given(/^a test app with the default configuration$/) do + TestApp.install +end + +Given(/^servers with the roles app and web$/) do + vagrant_cli_command('up') +end + +Given(/^a required file$/) do +end + +Given(/^that file exists$/) do + run_vagrant_command("touch #{TestApp.linked_file}") +end + +Given(/^the file does not exist$/) do + pending + file = TestApp.linked_file + run_vagrant_command("[ -f #{file} ] && rm #{file}") +end + +Given(/^a custom task to generate a file$/) do + TestApp.copy_task_to_test_app('spec/support/tasks/database.cap') +end + diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 00000000..3aed709c --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,12 @@ +require 'kuroko' + +project_root = File.expand_path('../../../', __FILE__) +vagrant_root = File.join(project_root, 'spec/support') + +Kuroko.configure do |config| + config.vagrant_root = 'spec/support' +end + +puts vagrant_root.inspect + +require_relative '../../spec/support/test_app' diff --git a/features/support/remote_command_helpers.rb b/features/support/remote_command_helpers.rb new file mode 100644 index 00000000..47b56ef8 --- /dev/null +++ b/features/support/remote_command_helpers.rb @@ -0,0 +1,20 @@ +module RemoteCommandHelpers + + def test_dir_exists(path) + exists?('d', path) + end + + def test_symlink_exists(path) + exists?('L', path) + end + + def test_file_exists(path) + exists?('f', path) + end + + def exists?(type, path) + %{[ -#{type} "#{path}" ] && echo "#{path} exists." || echo "Error: #{path} does not exist."} + end +end + +World(RemoteCommandHelpers) diff --git a/spec/integration/deploy_finalize_spec.rb b/spec/integration/deploy_finalize_spec.rb deleted file mode 100644 index 7ef6f0d6..00000000 --- a/spec/integration/deploy_finalize_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:finished', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - } - } - - describe 'log_revision' do - before do - cap 'deploy:started' - cap 'deploy:updating' - cap 'deploy:publishing' - cap 'deploy:finished' - end - - it 'writes the log file' do - expect(deploy_to.join('revisions.log')).to be_a_file - end - end - end -end diff --git a/spec/integration/deploy_finished_spec.rb b/spec/integration/deploy_finished_spec.rb deleted file mode 100644 index 4a5a7d1a..00000000 --- a/spec/integration/deploy_finished_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:finished', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - set :release_path, '#{deploy_to}/releases/1234' - } - } - - describe 'symlink' do - before do - cap 'deploy:started' - cap 'deploy:updating' - cap 'deploy:publishing' - cap 'deploy:finished' - end - - describe 'release' do - it 'symlinks the release to `current`' do - expect(File.symlink?(current_path)).to be_true - expect(File.readlink(current_path)).to eq '/tmp/test_app/deploy_to/releases/1234' - end - end - end - end -end diff --git a/spec/integration/deploy_started_spec.rb b/spec/integration/deploy_started_spec.rb deleted file mode 100644 index 606f0098..00000000 --- a/spec/integration/deploy_started_spec.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:started', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy:check' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - } - } - - describe 'directories' do - before do - cap 'deploy:check:directories' - end - - it 'ensures the directory structure' do - expect(shared_path).to be_a_directory - expect(releases_path).to be_a_directory - end - end - - describe 'linked_dirs' do - before do - cap 'deploy:check:linked_dirs' - end - - it 'ensure directories to be linked in `shared`' do - [ - shared_path.join('bin'), - shared_path.join('log'), - shared_path.join('public/system'), - shared_path.join('vendor/bundle'), - ].each do |dir| - expect(dir).to be_a_directory - end - end - end - - describe 'linked_files' do - - subject { cap 'deploy:check:linked_files' } - - context 'file does not exist' do - it 'fails' do - expect(subject).to match 'config/database.yml does not exist' - end - end - - context 'file exists' do - before do - create_shared_directory('config') - create_shared_file('config/database.yml') - end - - it 'suceeds' do - expect(subject).not_to match 'config/database.yml does not exist' - expect(subject).to match 'successful' - end - - end - end - end -end - diff --git a/spec/integration/deploy_update_spec.rb b/spec/integration/deploy_update_spec.rb deleted file mode 100644 index 60867d5c..00000000 --- a/spec/integration/deploy_update_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:updating', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - } - } - - describe 'symlink' do - before do - create_shared_directory('config') - create_shared_file('config/database.yml') - cap 'deploy' - end - - describe 'linked_dirs' do - it 'symlinks the directories in shared to `current`' do - %w{bin log public/system vendor/bundle}.each do |dir| - expect(current_path.join(dir)).to be_a_symlink_to shared_path.join(dir) - end - end - end - - describe 'linked_files' do - it 'symlinks the files in shared to `current`' do - file = 'config/database.yml' - expect(current_path.join(file)).to be_a_symlink_to shared_path.join(file) - end - end - end - end -end diff --git a/spec/integration/installation_spec.rb b/spec/integration/installation_spec.rb deleted file mode 100644 index d17858f4..00000000 --- a/spec/integration/installation_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap install' do - - context 'with defaults' do - before :all do - create_test_app - Dir.chdir(test_app_path) do - %x[bundle exec cap install] - end - end - - describe 'installation' do - - it 'creates config/deploy' do - path = test_app_path.join('config/deploy') - expect(Dir.exists?(path)).to be_true - end - - it 'creates lib/capistrano/tasks' do - path = test_app_path.join('lib/capistrano/tasks') - expect(Dir.exists?(path)).to be_true - end - - it 'creates the deploy file' do - file = test_app_path.join('config/deploy.rb') - expect(File.exists?(file)).to be_true - end - - it 'creates the stage files' do - staging = test_app_path.join('config/deploy/staging.rb') - production = test_app_path.join('config/deploy/production.rb') - expect(File.exists?(staging)).to be_true - expect(File.exists?(production)).to be_true - end - - end - end - - context 'with STAGES' do - before :all do - create_test_app - Dir.chdir(test_app_path) do - %x[bundle exec cap install STAGES=qa,production] - end - end - - describe 'installation' do - - it 'creates config/deploy' do - path = test_app_path.join('config/deploy') - expect(Dir.exists?(path)).to be_true - end - - it 'creates lib/capistrano/tasks' do - path = test_app_path.join('lib/capistrano/tasks') - expect(Dir.exists?(path)).to be_true - end - - it 'creates the deploy file' do - file = test_app_path.join('config/deploy.rb') - expect(File.exists?(file)).to be_true - end - - it 'creates the stage files specified, not the defaults' do - qa = test_app_path.join('config/deploy/qa.rb') - production = test_app_path.join('config/deploy/production.rb') - staging = test_app_path.join('config/deploy/staging.rb') - expect(File.exists?(qa)).to be_true - expect(File.exists?(production)).to be_true - expect(File.exists?(staging)).to be_false - end - - end - end -end diff --git a/spec/integration/remote_file_task_spec.rb b/spec/integration/remote_file_task_spec.rb deleted file mode 100644 index 25eb8016..00000000 --- a/spec/integration/remote_file_task_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:started', slow: true do - before do - install_test_app_with(config) - copy_task_to_test_app('spec/support/tasks/database.cap') - end - - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{config} - } - } - - describe 'linked_files' do - - before do - cap 'deploy:check:linked_dirs' - end - - subject { cap 'deploy:check:linked_files' } - - context 'where the file does not exist' do - it 'creates the file with the remote_task prerequisite' do - expect(subject).to match 'Uploading' - expect(subject).not_to match 'config/database.yml does not exist' - expect(subject).to match 'successful' - end - end - - context 'where the file already exists' do - before do - FileUtils.touch(shared_path.join('config/database.yml')) - end - - it 'will not recreate the file if it already exists' do - expect(subject).not_to match 'Uploading' - expect(subject).not_to match 'config/database.yml does not exist' - expect(subject).to match 'successful' - end - end - - end -end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 609686a7..dab66ed6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,5 +12,4 @@ RSpec.configure do |config| config.treat_symbols_as_metadata_keys_with_true_values = true config.mock_framework = :mocha config.order = 'random' - config.filter_run_excluding :slow end diff --git a/spec/support/.gitignore b/spec/support/.gitignore new file mode 100644 index 00000000..8000dd9d --- /dev/null +++ b/spec/support/.gitignore @@ -0,0 +1 @@ +.vagrant diff --git a/spec/support/Vagrantfile b/spec/support/Vagrantfile new file mode 100644 index 00000000..831f206e --- /dev/null +++ b/spec/support/Vagrantfile @@ -0,0 +1,13 @@ +Vagrant::Config.run do |config| + + [:app].each_with_index do |role, i| + config.vm.define(role, primary: true) do |config| + config.vm.box = role + config.vm.box = 'precise64' + config.vm.box_url = 'http://files.vagrantup.com/precise64.box' + config.vm.forward_port 22, "222#{i}".to_i + config.vm.provision :shell, inline: 'yes | sudo apt-get install git-core' + end + end + +end diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index b186c00b..5f18d1fd 100644 --- a/spec/support/test_app.rb +++ b/spec/support/test_app.rb @@ -1,10 +1,39 @@ require 'fileutils' module TestApp + extend self + + def install + install_test_app_with(default_config) + end + + def default_config + %{ + set :stage, :#{stage} + set :deploy_to, '#{deploy_to}' + set :repo_url, 'git://github.com/capistrano/capistrano.git' + set :branch, 'v3' + set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key" } + server 'vagrant@localhost:2220', roles: %w{web app} + set :linked_files, #{linked_files} + set :linked_dirs, #{linked_dirs} + } + end + + def linked_files + %w{config/database.yml} + end + + def linked_file + shared_path.join(linked_files.first) + end + + def linked_dirs + %w{bin log public/system vendor/bundle} + end + def create_test_app - [test_app_path, deploy_to].each do |path| - FileUtils.rm_rf(path) - FileUtils.mkdir(path) - end + FileUtils.rm_rf(test_app_path) + FileUtils.mkdir(test_app_path) File.open(gemfile, 'w+') do |file| file.write "gem 'capistrano', path: '#{path_to_cap}'" @@ -56,7 +85,7 @@ module TestApp end def deploy_to - Pathname.new('/tmp/test_app/deploy_to') + Pathname.new('/home/vagrant/var/www/deploy') end def shared_path @@ -72,7 +101,15 @@ module TestApp end def release_path - releases_path.join(Dir.entries(releases_path).last) + releases_path.join(timestamp) + end + + def timestamp + Time.now.utc.strftime("%Y%m%d%H%M%S") + end + + def repo_path + deploy_to.join('repo') end def path_to_cap