1
0
Fork 0
mirror of https://github.com/capistrano/capistrano synced 2023-03-27 23:21:18 -04:00

getting started on cap 2.0

git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@6243 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Jamis Buck 2007-02-26 23:59:33 +00:00
parent 66524897fb
commit 8e33fdc46b
19 changed files with 1442 additions and 493 deletions

View file

@ -416,50 +416,6 @@ module Capistrano
release_path(releases[-2]) if releases[-2]
end
# Invoke a set of tasks in a transaction. If any task fails (raises an
# exception), all tasks executed within the transaction are inspected to
# see if they have an associated on_rollback hook, and if so, that hook
# is called.
def transaction
if task_call_history
yield
else
logger.info "transaction: start"
begin
@task_call_history = []
yield
logger.info "transaction: commit"
rescue Object => e
current = task_call_history.last
logger.important "transaction: rollback", current ? current.name : "transaction start"
task_call_history.reverse.each do |task|
begin
# throw the task back on the stack so that roles are properly
# interpreted in the scope of the task in question.
push_task_call_frame(task.name)
logger.debug "rolling back", task.name
task.rollback.call if task.rollback
rescue Object => e
logger.info "exception while rolling back: #{e.class}, #{e.message}", task.name
ensure
pop_task_call_frame
end
end
raise
ensure
@task_call_history = nil
end
end
end
# Specifies an on_rollback hook for the currently executing task. If this
# or any subsequent task then fails, and a transaction is active, this
# hook will be executed.
def on_rollback(&block)
task_call_frames.last.rollback = block
end
# An instance-level reader for the class' #default_io_proc attribute.
def default_io_proc
self.class.default_io_proc
@ -473,11 +429,6 @@ module Capistrano
execute_on_servers(options) { }
end
def current_task
return nil if task_call_frames.empty?
tasks[task_call_frames.last.name]
end
def metaclass
class << self; self; end
end
@ -492,16 +443,6 @@ module Capistrano
metaclass.send(:define_method, name, &block)
end
def push_task_call_frame(name)
frame = TaskCallFrame.new(name)
task_call_frames.push frame
task_call_history.push frame if task_call_history
end
def pop_task_call_frame
task_call_frames.pop
end
def establish_connections(servers)
@factory = establish_gateway if needs_gateway?
servers = Array(servers)

View file

@ -1,242 +1,25 @@
require 'capistrano/actor'
require 'capistrano/logger'
require 'capistrano/scm/subversion'
require 'capistrano/extensions'
require 'capistrano/configuration/execution'
require 'capistrano/configuration/loading'
require 'capistrano/configuration/namespaces'
require 'capistrano/configuration/roles'
require 'capistrano/configuration/variables'
module Capistrano
# Represents a specific Capistrano configuration. A Configuration instance
# may be used to load multiple recipe files, define and describe tasks,
# define roles, create an actor, and set configuration variables.
# define roles, and set configuration variables.
class Configuration
Role = Struct.new(:host, :options)
DEFAULT_VERSION_DIR_NAME = "releases" #:nodoc:
DEFAULT_CURRENT_DIR_NAME = "current" #:nodoc:
DEFAULT_SHARED_DIR_NAME = "shared" #:nodoc:
# The actor created for this configuration instance.
attr_reader :actor
# The list of Role instances defined for this configuration.
attr_reader :roles
# The logger instance defined for this configuration.
attr_reader :logger
# The load paths used for locating recipe files.
attr_reader :load_paths
# The time (in UTC) at which this configuration was created, used for
# determining the release path.
attr_reader :now
# The has of variables currently known by the configuration
attr_reader :variables
def initialize(actor_class=Actor) #:nodoc:
@roles = Hash.new { |h,k| h[k] = [] }
@actor = actor_class.new(self)
def initialize #:nodoc:
@logger = Logger.new
@load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
@variables = {}
@now = Time.now.utc
# for preserving the original value of Proc-valued variables
set :original_value, Hash.new
set :application, nil
set :repository, nil
set :gateway, nil
set :user, nil
set :password, nil
set :ssh_options, Hash.new
set(:deploy_to) { "/u/apps/#{application}" }
set :version_dir, DEFAULT_VERSION_DIR_NAME
set :current_dir, DEFAULT_CURRENT_DIR_NAME
set :shared_dir, DEFAULT_SHARED_DIR_NAME
set :scm, :subversion
set(:revision) { source.latest_revision }
end
# Set a variable to the given value.
def set(variable, value=nil, &block)
# if the variable is uppercase, then we add it as a constant to the
# actor. This is to allow uppercase "variables" to be set and referenced
# in recipes.
if variable.to_s[0].between?(?A, ?Z)
warn "[DEPRECATION] You setting an upper-cased variable, `#{variable}'. Variables should start with a lower-case letter. Support for upper-cased variables will be removed in version 2."
klass = @actor.metaclass
klass.send(:remove_const, variable) if klass.const_defined?(variable)
klass.const_set(variable, value)
end
value = block if value.nil? && block_given?
@variables[variable] = value
end
alias :[]= :set
# Access a named variable. If the value of the variable responds_to? :call,
# #call will be invoked (without parameters) and the return value cached
# and returned.
def [](variable)
if @variables[variable].respond_to?(:call)
self[:original_value][variable] = @variables[variable]
set variable, @variables[variable].call
end
@variables[variable]
end
# Based on the current value of the <tt>:scm</tt> variable, instantiate and
# return an SCM module representing the desired source control behavior.
def source
@source ||= case scm
when Class then
scm.new(self)
when String, Symbol then
require "capistrano/scm/#{scm.to_s.downcase}"
Capistrano::SCM.const_get(scm.to_s.downcase.capitalize).new(self)
else
raise "invalid scm specification: #{scm.inspect}"
end
end
# Load a configuration file or string into this configuration.
#
# Usage:
#
# load("recipe"):
# Look for and load the contents of 'recipe.rb' into this
# configuration.
#
# load(:file => "recipe"):
# same as above
#
# load(:string => "set :scm, :subversion"):
# Load the given string as a configuration specification.
#
# load { ... }
# Load the block in the context of the configuration.
def load(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
args.each { |arg| load options.merge(:file => arg) }
return unless args.empty?
if block
raise "loading a block requires 0 parameters" unless args.empty?
load(options.merge(:proc => block))
elsif options[:file]
file = options[:file]
unless file[0] == ?/
load_paths.each do |path|
if File.file?(File.join(path, file))
file = File.join(path, file)
break
elsif File.file?(File.join(path, file) + ".rb")
file = File.join(path, file + ".rb")
break
end
end
end
load :string => File.read(file), :name => options[:name] || file
elsif options[:string]
instance_eval(options[:string], options[:name] || "<eval>")
elsif options[:proc]
instance_eval(&options[:proc])
else
raise ArgumentError, "don't know how to load #{options.inspect}"
end
end
# Define a new role and its associated servers. You must specify at least
# one host for each role. Also, you can specify additional information
# (in the form of a Hash) which can be used to more uniquely specify the
# subset of servers specified by this specific role definition.
#
# Usage:
#
# role :db, "db1.example.com", "db2.example.com"
# role :db, "master.example.com", :primary => true
# role :app, "app1.example.com", "app2.example.com"
def role(which, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
raise ArgumentError, "must give at least one host" if args.empty?
args.each { |host| roles[which] << Role.new(host, options) }
end
# Describe the next task to be defined. The given text will be attached to
# the next task that is defined and used as its description.
def desc(text)
@next_description = text
end
# Define a new task. If a description is active (see #desc), it is added to
# the options under the <tt>:desc</tt> key. This method ultimately
# delegates to Actor#define_task.
def task(name, options={}, &block)
raise ArgumentError, "expected a block" unless block
if @next_description
options = options.merge(:desc => @next_description)
@next_description = nil
end
actor.define_task(name, options, &block)
end
# Require another file. This is identical to the standard require method,
# with the exception that it sets the reciever as the "current" configuration
# so that third-party task bundles can include themselves relative to
# that configuration.
def require(*args) #:nodoc:
original, Capistrano.configuration = Capistrano.configuration, self
super
ensure
# restore the original, so that require's can be nested
Capistrano.configuration = original
end
# Return the path into which releases should be deployed.
def releases_path
File.join(deploy_to, version_dir)
end
# Return the path identifying the +current+ symlink, used to identify the
# current release.
def current_path
File.join(deploy_to, current_dir)
end
# Return the path into which shared files should be stored.
def shared_path
File.join(deploy_to, shared_dir)
end
# Return the full path to the named release. If a release is not specified,
# +now+ is used (the time at which the configuration was created).
def release_path(release=now.strftime("%Y%m%d%H%M%S"))
File.join(releases_path, release)
end
def respond_to?(sym) #:nodoc:
@variables.has_key?(sym) || super
end
def method_missing(sym, *args, &block) #:nodoc:
if args.length == 0 && block.nil? && @variables.has_key?(sym)
self[sym]
else
super
end
end
# The includes must come at the bottom, since they may redefine methods
# defined in the base class.
include Execution, Loading, Namespaces, Roles, Variables
end
end

View file

@ -0,0 +1,123 @@
module Capistrano
class Configuration
module Execution
def self.included(base)
base.send :alias_method, :initialize_without_execution, :initialize
base.send :alias_method, :initialize, :initialize_with_execution
end
# The call stack of the tasks. The currently executing task may inspect
# this to see who its caller was. The current task is always the last
# element of this stack.
attr_reader :task_call_frames
# The stack of tasks that have registered rollback handlers within the
# current transaction. If this is nil, then there is no transaction
# that is currently active.
attr_reader :rollback_requests
# A struct for representing a single instance of an invoked task.
TaskCallFrame = Struct.new(:task, :rollback)
def initialize_with_execution #:nodoc:
initialize_without_execution
@task_call_frames = []
end
private :initialize_with_execution
# Returns true if there is a transaction currently active.
def transaction?
!rollback_requests.nil?
end
# Invoke a set of tasks in a transaction. If any task fails (raises an
# exception), all tasks executed within the transaction are inspected to
# see if they have an associated on_rollback hook, and if so, that hook
# is called.
def transaction
raise ArgumentError, "expected a block" unless block_given?
raise ScriptError, "transaction must be called from within a task" if task_call_frames.empty?
return yield if transaction?
logger.info "transaction: start"
begin
@rollback_requests = []
yield
logger.info "transaction: commit"
rescue Object => e
rollback!
raise
ensure
@rollback_requests = nil
end
end
# Specifies an on_rollback hook for the currently executing task. If this
# or any subsequent task then fails, and a transaction is active, this
# hook will be executed.
def on_rollback(&block)
task_call_frames.last.rollback = block
rollback_requests << task_call_frames.last
end
# Returns the TaskDefinition object for the currently executing task.
# It returns nil if there is no task being executed.
def current_task
return nil if task_call_frames.empty?
task_call_frames.last.task
end
# Executes the task with the given name, including the before and after
# hooks.
def execute_task(name, namespace, fail_silently=false)
name = name.to_sym
unless task = namespace.tasks[name]
return if fail_silently
fqn = " in `#{namespace.fully_qualified_name}'" if namespace.parent
raise NoMethodError, "no such task `#{name}'#{fqn}"
end
execute_task("before_#{name}", namespace, true)
logger.debug "executing `#{task.fully_qualified_name}'"
begin
push_task_call_frame(task)
result = instance_eval(&task.body)
ensure
pop_task_call_frame
end
execute_task("after_#{name}", namespace, true)
result
end
protected
def rollback!
# throw the task back on the stack so that roles are properly
# interpreted in the scope of the task in question.
rollback_requests.reverse.each do |frame|
begin
push_task_call_frame(frame.task)
logger.important "rolling back", frame.task.fully_qualified_name
frame.rollback.call
rescue Object => e
logger.info "exception while rolling back: #{e.class}, #{e.message}", frame.task.fully_qualified_name
ensure
pop_task_call_frame
end
end
end
def push_task_call_frame(task)
frame = TaskCallFrame.new(task)
task_call_frames.push frame
end
def pop_task_call_frame
task_call_frames.pop
end
end
end
end

View file

@ -0,0 +1,112 @@
module Capistrano
class Configuration
module Loading
def self.included(base)
base.send :alias_method, :initialize_without_loading, :initialize
base.send :alias_method, :initialize, :initialize_with_loading
base.extend ClassMethods
end
module ClassMethods
# Used by third-party task bundles to identify the capistrano
# configuration that is loading them. Its return value is not reliable
# in other contexts. If +require_config+ is not false, an exception
# will be raised if the current configuration is not set.
def instance(require_config=false)
config = Thread.current[:capistrano_configuration]
if require_config && config.nil?
raise LoadError, "Please require this file from within a Capistrano recipe"
end
config
end
# Used internally by Capistrano to specify the current configuration
# before loading a third-party task bundle.
def instance=(config)
Thread.current[:capistrano_configuration] = config
end
end
# The load paths used for locating recipe files.
attr_reader :load_paths
def initialize_with_loading #:nodoc:
initialize_without_loading
@load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "../recipes"))]
end
private :initialize_with_loading
# Load a configuration file or string into this configuration.
#
# Usage:
#
# load("recipe"):
# Look for and load the contents of 'recipe.rb' into this
# configuration.
#
# load(:file => "recipe"):
# same as above
#
# load(:string => "set :scm, :subversion"):
# Load the given string as a configuration specification.
#
# load { ... }
# Load the block in the context of the configuration.
def load(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
if block
raise ArgumentError, "loading a block requires 0 arguments" unless options.empty? && args.empty?
load(:proc => block)
elsif args.any?
args.each { |arg| load options.merge(:file => arg) }
elsif options[:file]
load_from_file(options[:file], options[:name])
elsif options[:string]
instance_eval(options[:string], options[:name] || "<eval>")
elsif options[:proc]
instance_eval(&options[:proc])
else
raise ArgumentError, "don't know how to load #{options.inspect}"
end
end
# Require another file. This is identical to the standard require method,
# with the exception that it sets the reciever as the "current" configuration
# so that third-party task bundles can include themselves relative to
# that configuration.
def require(*args) #:nodoc:
original, self.class.instance = self.class.instance, self
super
ensure
# restore the original, so that require's can be nested
self.class.instance = original
end
private
# Load a recipe from the named file. If +name+ is given, the file will
# be reported using that name.
def load_from_file(file, name=nil)
file = find_file_in_load_path(file) unless file[0] == ?/
load :string => File.read(file), :name => name || file
end
def find_file_in_load_path(file)
load_paths.each do |path|
["", ".rb"].each do |ext|
name = File.join(path, "#{file}#{ext}")
return name if File.file?(name)
end
end
raise LoadError, "no such file to load -- #{file}"
end
end
end
end

View file

@ -0,0 +1,126 @@
require 'capistrano/task_definition'
module Capistrano
class Configuration
module Namespaces
def self.included(base)
base.send :alias_method, :initialize_without_namespaces, :initialize
base.send :alias_method, :initialize, :initialize_with_namespaces
end
# The name of this namespace. Defaults to +nil+ for the top-level
# namespace.
attr_reader :name
# The parent namespace of this namespace. Returns +nil+ for the top-level
# namespace.
attr_reader :parent
# The hash of tasks defined for this namespace.
attr_reader :tasks
# The hash of namespaces defined for this namespace.
attr_reader :namespaces
def initialize_with_namespaces(*args) #:nodoc:
@name = @parent = nil
initialize_without_namespaces(*args)
@tasks = {}
@namespaces = {}
@next_description = nil
end
private :initialize_with_namespaces
# Returns the fully-qualified name of this namespace, or nil if the
# namespace is at the top-level.
def fully_qualified_name
return nil if name.nil?
[parent.fully_qualified_name, name].compact.join(":")
end
# Describe the next task to be defined. The given text will be attached to
# the next task that is defined and used as its description.
def desc(text)
@next_description = text
end
# Open a namespace in which to define new tasks. If the namespace was
# defined previously, it will be reopened, otherwise a new namespace
# will be created for the given name.
def namespace(name, &block)
name = name.to_sym
raise ArgumentError, "expected a block" unless block_given?
namespace_already_defined = namespaces.key?(name)
if all_methods.include?(name.to_s) && !namespace_already_defined
thing = tasks.key?(name) ? "task" : "method"
raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
end
namespaces[name] ||= Namespace.new(name, self)
namespaces[name].instance_eval(&block)
# make sure any open description gets terminated
namespaces[name].desc(nil)
if !namespace_already_defined
metaclass = class << self; self; end
metaclass.send(:define_method, name) { namespaces[name] }
end
end
# Describe a new task. If a description is active (see #desc), it is added
# to the options under the <tt>:desc</tt> key. The new task is added to
# the namespace.
def task(name, options={}, &block)
name = name.to_sym
raise ArgumentError, "expected a block" unless block_given?
task_already_defined = tasks.key?(name)
if all_methods.include?(name.to_s) && !task_already_defined
thing = namespaces.key?(name) ? "namespace" : "method"
raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
end
tasks[name] = TaskDefinition.new(name, self, {:desc => @next_description}.merge(options), &block)
@next_description = nil
if !task_already_defined
metaclass = class << self; self; end
metaclass.send(:define_method, name) { execute_task(name, self) }
end
end
private
def all_methods
public_methods.concat(protected_methods).concat(private_methods)
end
class Namespace
def initialize(name, parent)
@parent = parent
@name = name
end
def role(*args)
raise NotImplementedError, "roles cannot be defined in a namespace"
end
def respond_to?(sym)
super || parent.respond_to?(sym)
end
def method_missing(sym, *args, &block)
if parent.respond_to?(sym)
parent.send(sym, *args, &block)
else
super
end
end
include Capistrano::Configuration::Namespaces
end
end
end
end

View file

@ -0,0 +1,52 @@
require 'capistrano/server_definition'
module Capistrano
class Configuration
module Roles
def self.included(base)
base.send :alias_method, :initialize_without_roles, :initialize
base.send :alias_method, :initialize, :initialize_with_roles
end
# The hash of roles defined for this configuration. Each entry in the
# hash points to an array of server definitions that belong in that
# role.
attr_reader :roles
def initialize_with_roles #:nodoc:
initialize_without_roles
@roles = Hash.new { |h,k| h[k] = [] }
end
# Define a new role and its associated servers. You must specify at least
# one host for each role. Also, you can specify additional information
# (in the form of a Hash) which can be used to more uniquely specify the
# subset of servers specified by this specific role definition.
#
# Usage:
#
# role :db, "db1.example.com", "db2.example.com"
# role :db, "master.example.com", :primary => true
# role :app, "app1.example.com", "app2.example.com"
#
# You can also encode the username and port number for each host in the
# server string, if needed:
#
# role :web, "www@web1.example.com"
# role :file, "files.example.com:4144"
# role :db, "admin@db3.example.com:1234"
#
# Lastly, username and port number may be passed as options, if that is
# preferred; note that the options apply to all servers defined in
# that call to "role":
#
# role :web, "web2", "web3", :user => "www", :port => 2345
def role(which, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
raise ArgumentError, "must give at least one host" if args.empty?
which = which.to_sym
args.each { |host| roles[which] << ServerDefinition.new(host, options) }
end
end
end
end

View file

@ -0,0 +1,108 @@
module Capistrano
class Configuration
module Variables
def self.included(base)
%w(initialize respond_to? method_missing).each do |m|
base_name = m[/^\w+/]
punct = m[/\W+$/]
base.send :alias_method, "#{base_name}_without_variables#{punct}", m
base.send :alias_method, m, "#{base_name}_with_variables#{punct}"
end
end
# The hash of variables that have been defined in this configuration
# instance.
attr_reader :variables
# Set a variable to the given value.
def set(variable, *args, &block)
if variable.to_s !~ /^[_a-z]/
raise ArgumentError, "invalid variable `#{variable}' (variables must begin with an underscore, or a lower-case letter)"
end
if !block_given? && args.empty? || block_given? && !args.empty?
raise ArgumentError, "you must specify exactly one of either a value or a block"
end
if args.length > 1
raise ArgumentError, "wrong number of arguments (#{args.length} for 1)"
end
value = args.empty? ? block : args.first
@variables[variable.to_sym] = value
end
alias :[]= :set
# Removes any trace of the given variable.
def unset(variable)
sym = variable.to_sym
@original_procs.delete(sym)
@variables.delete(sym)
end
# Returns true if the variable has been defined, and false otherwise.
def exists?(variable)
@variables.key?(variable.to_sym)
end
# If the variable was originally a proc value, it will be reset to it's
# original proc value. Otherwise, this method does nothing. It returns
# true if the variable was actually reset.
def reset!(variable)
sym = variable.to_sym
if @original_procs.key?(sym)
@variables[sym] = @original_procs.delete(sym)
true
else
false
end
end
# Access a named variable. If the value of the variable responds_to? :call,
# #call will be invoked (without parameters) and the return value cached
# and returned.
def fetch(variable, *args)
if !args.empty? && block_given?
raise ArgumentError, "you must specify either a default value or a block, but not both"
end
sym = variable.to_sym
if !@variables.key?(sym)
return args.first unless args.empty?
return yield(variable) if block_given?
raise IndexError, "`#{variable}' not found"
end
if @variables[sym].respond_to?(:call)
@original_procs[sym] = @variables[sym]
@variables[sym] = @variables[sym].call
end
@variables[sym]
end
def [](variable)
fetch(variable, nil)
end
def initialize_with_variables #:nodoc:
initialize_without_variables
@variables = {}
@original_procs = {}
end
private :initialize_with_variables
def respond_to_with_variables?(sym) #:nodoc:
@variables.has_key?(sym) || respond_to_without_variables?(sym)
end
def method_missing_with_variables(sym, *args, &block) #:nodoc:
if args.length == 0 && block.nil? && @variables.has_key?(sym)
self[sym]
else
method_missing_without_variables(sym, *args, &block)
end
end
end
end
end

View file

@ -0,0 +1,30 @@
module Capistrano
class ServerDefinition
attr_reader :host
attr_reader :user
attr_reader :port
attr_reader :options
def initialize(string, options={})
@user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3]
@options = options.dup
@user = @options.delete(:user) || @user
@port = @options.delete(:port) || @port
@port = @port.to_i if @port
end
# Redefined, so that Array#uniq will work to remove duplicate server
# definitions, based solely on their host names.
def eql?(server)
host == server.host
end
# Redefined, so that Array#uniq will work to remove duplicate server
# definitions, based solely on their host names.
def hash
host.hash
end
end
end

View file

@ -0,0 +1,67 @@
require 'capistrano/server_definition'
module Capistrano
# Represents the definition of a single task.
class TaskDefinition
attr_reader :name, :namespace, :options, :body
def initialize(name, namespace, options={}, &block)
@name, @namespace, @options = name, namespace, options
@body = block or raise ArgumentError, "a task requires a block"
@servers = nil
end
# Returns the task's fully-qualified name, including the namespace
def fully_qualified_name
@fully_qualified_name ||= [namespace.fully_qualified_name, name].compact.join(":")
end
# Returns the list of server definitions (_not_ connections to servers)
# that are the target of this task.
def servers(reevaluate=false)
@servers = nil if reevaluate
@servers ||=
if hosts = find_hosts
hosts.map { |host| ServerDefinition.new(host) }
else
apply_except(apply_only(find_servers_by_role)).uniq
end
end
private
def find_servers_by_role
roles = namespace.roles
role_names = environment_values(:roles, true) || @options[:roles] || roles.keys
Array(role_names).inject([]) do |list, name|
name = name.to_sym
raise ArgumentError, "task `#{fully_qualified_name}' references non-existant role `#{name}'" unless roles.key?(name)
list.concat(roles[name])
end
end
def find_hosts
environment_values(:hosts) || @options[:hosts]
end
def environment_values(key, use_symbols = false)
if variable = ENV[key.to_s.upcase]
values = variable.split(",")
use_symbols ? values.collect { |e| e.to_sym } : values
end
end
def apply_only(roles)
only = @options[:only] || {}
roles.select do |role|
only.all? { |key, value| role.options[key] == value }
end
end
def apply_except(roles)
except = @options[:except] || {}
roles.reject do |role|
except.any? { |key, value| role.options[key] == value }
end
end
end
end

View file

@ -11,16 +11,14 @@ module Capistrano
# If +require_config+ is not false, an exception will be raised if the current
# configuration is not set.
def self.configuration(require_config=false)
config = Thread.current[:capistrano_configuration]
if require_config && config.nil?
raise "Please require this file from within a Capistrano recipe"
end
config
warn "[DEPRECATION] please use Capistrano::Configuration.instance instead of Capistrano.configuration. (You may be using a Capistrano plugin that is using this deprecated syntax.)"
Capistrano::Configuration.instance(require_config)
end
# Used internally by Capistrano to specify the current configuration before
# loading a third-party task bundle.
def self.configuration=(config)
Thread.current[:capistrano_configuration] = config
warn "[DEPRECATION] please us Capistrano::Configuration.instance= instead of Capistrano.configuration=."
Capistrano::Configuration.instance = config
end
end

View file

@ -0,0 +1,149 @@
$:.unshift File.dirname(__FILE__) + "/../../lib"
require 'test/unit'
require 'mocha'
require 'capistrano/configuration/execution'
require 'capistrano/task_definition'
class ConfigurationNamespacesDSLTest < Test::Unit::TestCase
class MockConfig
include Mocha::AutoVerify
attr_reader :tasks, :namespaces, :fully_qualified_name, :parent
attr_reader :state
attr_accessor :logger
def initialize(options={})
@tasks = {}
@namespaces = {}
@state = {}
@fully_qualified_name = options[:fqn]
@parent = options[:parent]
@logger = stub(:debug => nil, :info => nil, :important => nil)
end
include Capistrano::Configuration::Execution
end
def setup
@config = MockConfig.new
end
def test_initialize_should_initialize_collections
assert_nil @config.rollback_requests
assert @config.task_call_frames.empty?
end
def test_execute_task_with_unknown_task_should_raise_error
assert_raises(NoMethodError) do
@config.execute_task(:bogus, @config)
end
end
def test_execute_task_with_unknown_task_and_fail_silently_should_fail_silently
assert_nothing_raised do
@config.execute_task(:bogus, @config, true)
end
end
def test_execute_task_should_populate_call_stack
new_task @config, :testing
assert_nothing_raised { @config.execute_task :testing, @config }
assert_equal %w(testing), @config.state[:testing][:stack]
assert_nil @config.state[:testing][:history]
assert @config.task_call_frames.empty?
end
def test_nested_execute_task_should_add_to_call_stack
new_task @config, :testing
new_task(@config, :outer) { execute_task :testing, self }
assert_nothing_raised { @config.execute_task :outer, @config }
assert_equal %w(outer testing), @config.state[:testing][:stack]
assert_nil @config.state[:testing][:history]
assert @config.task_call_frames.empty?
end
def test_execute_task_should_execute_before_hook_if_defined
new_task @config, :testing
new_task @config, :before_testing
@config.execute_task :testing, @config
assert_equal %w(before_testing testing), @config.state[:trail]
end
def test_execute_task_should_execute_after_hook_if_defined
new_task @config, :testing
new_task @config, :after_testing
@config.execute_task :testing, @config
assert_equal %w(testing after_testing), @config.state[:trail]
end
def test_transaction_outside_of_task_should_raise_exception
assert_raises(ScriptError) { @config.transaction {} }
end
def test_transaction_without_block_should_raise_argument_error
new_task(@config, :testing) { transaction }
assert_raises(ArgumentError) { @config.execute_task :testing, @config }
end
def test_transaction_should_initialize_transaction_history
@config.state[:inspector] = stack_inspector
new_task(@config, :testing) { transaction { instance_eval(&state[:inspector]) } }
@config.execute_task :testing, @config
assert_equal [], @config.state[:testing][:history]
end
def test_transaction_from_within_transaction_should_not_start_new_transaction
new_task(@config, :third, &stack_inspector)
new_task(@config, :second) { transaction { execute_task(:third, self) } }
new_task(@config, :first) { transaction { execute_task(:second, self) } }
# kind of fragile...not sure how else to check that transaction was only
# really run twice...but if the transaction was REALLY run, logger.info
# will be called once when it starts, and once when it finishes.
@config.logger = mock()
@config.logger.stubs(:debug)
@config.logger.expects(:info).times(2)
@config.execute_task :first, @config
end
def test_exception_raised_in_transaction_should_call_all_registered_rollback_handlers_in_reverse_order
new_task(@config, :aaa) { on_rollback { (state[:rollback] ||= []) << :aaa } }
new_task(@config, :bbb) { on_rollback { (state[:rollback] ||= []) << :bbb } }
new_task(@config, :ccc) {}
new_task(@config, :ddd) { on_rollback { (state[:rollback] ||= []) << :ddd }; execute_task(:bbb, self); execute_task(:ccc, self) }
new_task(@config, :eee) { transaction { execute_task(:ddd, self); execute_task(:aaa, self); raise "boom" } }
assert_raises(RuntimeError) do
@config.execute_task :eee, @config
end
assert_equal [:aaa, :bbb, :ddd], @config.state[:rollback]
assert_nil @config.rollback_requests
assert @config.task_call_frames.empty?
end
def test_exception_during_rollback_should_simply_be_logged_and_ignored
new_task(@config, :aaa) { on_rollback { state[:aaa] = true; raise LoadError, "ouch" }; execute_task(:bbb, self) }
new_task(@config, :bbb) { raise MadError, "boom" }
new_task(@config, :ccc) { transaction { execute_task(:aaa, self) } }
assert_raises(NameError) do
@config.execute_task :ccc, @config
end
assert @config.state[:aaa]
end
private
def stack_inspector
Proc.new do
(state[:trail] ||= []) << current_task.fully_qualified_name
data = state[current_task.name] = {}
data[:stack] = task_call_frames.map { |frame| frame.task.fully_qualified_name }
data[:history] = rollback_requests && rollback_requests.map { |frame| frame.task.fully_qualified_name }
end
end
def new_task(namespace, name, options={}, &block)
block ||= stack_inspector
namespace.tasks[name] = Capistrano::TaskDefinition.new(name, namespace, &block)
end
end

View file

@ -0,0 +1,116 @@
$:.unshift File.dirname(__FILE__) + "/../../lib"
require 'test/unit'
require 'mocha'
require 'capistrano/configuration/loading'
class ConfigurationLoadingTest < Test::Unit::TestCase
class MockConfig
include Capistrano::Configuration::Loading
attr_accessor :ping
def ping!(value)
@ping = value
end
end
def setup
@config = MockConfig.new
end
def teardown
MockConfig.instance = nil
$".delete "#{File.dirname(__FILE__)}/../fixtures/custom.rb"
end
def test_initialize_sets_load_paths
assert @config.load_paths.include?(".")
assert @config.load_paths.detect { |v| v =~ /capistrano\/recipes$/ }
end
def test_load_with_options_and_block_should_raise_argument_error
assert_raises(ArgumentError) do
@config.load(:string => "foo") { something }
end
end
def test_load_with_arguments_and_block_should_raise_argument_error
assert_raises(ArgumentError) do
@config.load("foo") { something }
end
end
def test_load_from_string_should_eval_in_config_scope
@config.load :string => "ping! :here"
assert_equal :here, @config.ping
end
def test_load_from_file_shoudld_respect_load_path
File.stubs(:file?).returns(false)
File.stubs(:file?).with("custom/path/for/file.rb").returns(true)
File.stubs(:read).with("custom/path/for/file.rb").returns("ping! :here")
@config.load_paths << "custom/path/for"
@config.load :file => "file.rb"
assert_equal :here, @config.ping
end
def test_load_from_file_should_respect_load_path_and_appends_rb
File.stubs(:file?).returns(false)
File.stubs(:file?).with("custom/path/for/file.rb").returns(true)
File.stubs(:read).with("custom/path/for/file.rb").returns("ping! :here")
@config.load_paths << "custom/path/for"
@config.load :file => "file"
assert_equal :here, @config.ping
end
def test_load_from_file_should_raise_load_error_if_file_cannot_be_found
File.stubs(:file?).returns(false)
assert_raises(LoadError) do
@config.load :file => "file"
end
end
def test_load_from_proc_should_eval_proc_in_config_scope
@config.load :proc => Proc.new { ping! :here }
assert_equal :here, @config.ping
end
def test_load_with_block_should_treat_block_as_proc_parameter
@config.load { ping! :here }
assert_equal :here, @config.ping
end
def test_load_with_unrecognized_option_should_raise_argument_error
assert_raises(ArgumentError) do
@config.load :url => "http://www.load-this.test"
end
end
def test_load_with_arguments_should_treat_arguments_as_files
File.stubs(:file?).returns(false)
File.stubs(:file?).with("./first.rb").returns(true)
File.stubs(:file?).with("./second.rb").returns(true)
File.stubs(:read).with("./first.rb").returns("ping! 'this'")
File.stubs(:read).with("./second.rb").returns("ping << 'that'")
assert_nothing_raised { @config.load "first", "second" }
assert_equal "thisthat", @config.ping
end
def test_require_from_config_should_load_file_in_config_scope
assert_nothing_raised do
@config.require "#{File.dirname(__FILE__)}/../fixtures/custom"
end
assert_equal :custom, @config.ping
end
def test_require_without_config_should_raise_load_error
assert_raises(LoadError) do
require "#{File.dirname(__FILE__)}/../fixtures/custom"
end
end
end

View file

@ -0,0 +1,151 @@
$:.unshift File.dirname(__FILE__) + "/../../lib"
require 'test/unit'
require 'capistrano/configuration/namespaces'
class ConfigurationNamespacesDSLTest < Test::Unit::TestCase
class MockConfig
include Capistrano::Configuration::Namespaces
end
def setup
@config = MockConfig.new
end
def test_initialize_should_initialize_collections
assert @config.tasks.empty?
assert @config.namespaces.empty?
end
def test_unqualified_task_should_define_task_at_top_namespace
assert !@config.tasks.key?(:testing)
@config.task(:testing) { puts "something" }
assert @config.tasks.key?(:testing)
end
def test_qualification_should_define_task_within_namespace
@config.namespace(:testing) do
task(:nested) { puts "nested" }
end
assert !@config.tasks.key?(:nested)
assert @config.namespaces.key?(:testing)
assert @config.namespaces[:testing].tasks.key?(:nested)
end
def test_namespace_within_namespace_should_define_task_within_nested_namespace
@config.namespace :outer do
namespace :inner do
task :nested do
puts "nested"
end
end
end
assert !@config.tasks.key?(:nested)
assert @config.namespaces.key?(:outer)
assert @config.namespaces[:outer].namespaces.key?(:inner)
assert @config.namespaces[:outer].namespaces[:inner].tasks.key?(:nested)
end
def test_pending_desc_should_disappear_when_enclosing_namespace_terminates
@config.namespace :outer do
desc "Something to say"
end
@config.namespace :outer do
task :testing do
puts "testing"
end
end
assert_nil @config.namespaces[:outer].tasks[:testing].options[:desc]
end
def test_pending_desc_should_apply_only_to_immediately_subsequent_task
@config.desc "A description"
@config.task(:testing) { puts "foo" }
@config.task(:another) { puts "bar" }
assert_equal "A description", @config.tasks[:testing].options[:desc]
assert_nil @config.tasks[:another].options[:desc]
end
def test_defining_task_without_block_should_raise_error
assert_raises(ArgumentError) do
@config.task(:testing)
end
end
def test_defining_task_that_shadows_existing_method_should_raise_error
assert_raises(ArgumentError) do
@config.task(:sprintf) { puts "foo" }
end
end
def test_defining_task_that_shadows_existing_namespace_should_raise_error
@config.namespace(:outer) {}
assert_raises(ArgumentError) do
@config.task(:outer) { puts "foo" }
end
end
def test_defining_namespace_that_shadows_existing_method_should_raise_error
assert_raises(ArgumentError) do
@config.namespace(:sprintf) {}
end
end
def test_defining_namespace_that_shadows_existing_task_should_raise_error
@config.task(:testing) { puts "foo" }
assert_raises(ArgumentError) do
@config.namespace(:testing) {}
end
end
def test_defining_task_that_shadows_existing_task_should_not_raise_error
@config.task(:original) { puts "foo" }
assert_nothing_raised do
@config.task(:original) { puts "bar" }
end
end
def test_role_inside_namespace_should_raise_error
assert_raises(NotImplementedError) do
@config.namespace(:outer) do
role :app, "hello"
end
end
end
def test_name_for_top_level_should_be_nil
assert_nil @config.name
end
def test_parent_for_top_level_should_be_nil
assert_nil @config.parent
end
def test_fqn_for_top_level_should_be_nil
assert_nil @config.fully_qualified_name
end
def test_fqn_for_namespace_should_be_the_name_of_the_namespace
@config.namespace(:outer) {}
assert_equal "outer", @config.namespaces[:outer].fully_qualified_name
end
def test_parent_for_namespace_should_be_the_top_level
@config.namespace(:outer) {}
assert_equal @config, @config.namespaces[:outer].parent
end
def test_fqn_for_nested_namespace_should_be_color_delimited
@config.namespace(:outer) { namespace(:inner) {} }
assert_equal "outer:inner", @config.namespaces[:outer].namespaces[:inner].fully_qualified_name
end
def test_parent_for_nested_namespace_should_be_the_nesting_namespace
@config.namespace(:outer) { namespace(:inner) {} }
assert_equal @config.namespaces[:outer], @config.namespaces[:outer].namespaces[:inner].parent
end
end

View file

@ -0,0 +1,43 @@
$:.unshift File.dirname(__FILE__) + "/../../lib"
require 'test/unit'
require 'capistrano/configuration/roles'
class ConfigurationRolesTest < Test::Unit::TestCase
class MockConfig
include Capistrano::Configuration::Roles
end
def setup
@config = MockConfig.new
end
def test_initialize_should_initialize_roles_collection
assert @config.roles.empty?
end
def test_role_should_require_at_least_one_server
assert_raises ArgumentError do
@config.role :app
end
end
def test_role_with_one_argument_should_add_to_roles_collection
@config.role :app, "app1.capistrano.test"
assert_equal [:app], @config.roles.keys
assert_equal %w(app1.capistrano.test), @config.roles[:app].map { |s| s.host }
end
def test_role_with_multiple_arguments_should_add_each_to_roles_collection
@config.role :app, "app1.capistrano.test", "app2.capistrano.test"
assert_equal [:app], @config.roles.keys
assert_equal %w(app1.capistrano.test app2.capistrano.test), @config.roles[:app].map { |s| s.host }
end
def test_role_with_options_should_apply_options_to_each_argument
@config.role :app, "app1.capistrano.test", "app2.capistrano.test", :extra => :value
@config.roles[:app].each do |server|
assert_equal({:extra => :value}, server.options)
end
end
end

View file

@ -0,0 +1,174 @@
$:.unshift File.dirname(__FILE__) + "/../../lib"
require 'test/unit'
require 'capistrano/configuration/variables'
class ConfigurationVariablesTest < Test::Unit::TestCase
class MockConfig
include Capistrano::Configuration::Variables
end
def setup
@config = MockConfig.new
end
def test_initialize_should_initialize_variables_hash
assert @config.variables.empty?
end
def test_set_should_add_variable_to_hash
@config.set :sample, :value
assert_equal :value, @config.variables[:sample]
end
def test_set_should_convert_variable_name_to_symbol
@config.set "sample", :value
assert_equal :value, @config.variables[:sample]
end
def test_set_should_be_aliased_to_square_brackets
@config[:sample] = :value
assert_equal :value, @config.variables[:sample]
end
def test_variables_should_be_accessible_as_read_accessors
@config[:sample] = :value
assert_equal :value, @config.sample
end
def test_method_missing_should_raise_error_if_no_variable_matches
assert_raises(NoMethodError) do
@config.sample
end
end
def test_respond_to_should_look_for_variables
assert !@config.respond_to?(:sample)
@config[:sample] = :value
assert @config.respond_to?(:sample)
end
def test_set_should_require_value
assert_raises(ArgumentError) do
@config.set(:sample)
end
end
def test_set_should_allow_value_to_be_omitted_if_block_is_given
assert_nothing_raised do
@config.set(:sample) { :value }
end
assert_instance_of Proc, @config.variables[:sample]
end
def test_set_should_not_allow_multiple_values
assert_raises(ArgumentError) do
@config.set(:sample, :value, :another)
end
end
def test_set_should_not_allow_both_a_value_and_a_block
assert_raises(ArgumentError) do
@config.set(:sample, :value) { :block }
end
end
def test_set_should_not_allow_capitalized_variables
assert_raises(ArgumentError) do
@config.set :Sample, :value
end
end
def test_unset_should_remove_variable_from_hash
@config.set :sample, :value
assert @config.variables.key?(:sample)
@config.unset :sample
assert !@config.variables.key?(:sample)
end
def test_unset_should_clear_memory_of_original_proc
@config.set(:sample) { :value }
@config.fetch(:sample)
@config.unset(:sample)
assert_equal false, @config.reset!(:sample)
end
def test_exists_should_report_existance_of_variable_in_hash
assert !@config.exists?(:sample)
@config[:sample] = :value
assert @config.exists?(:sample)
end
def test_reset_should_do_nothing_if_variable_does_not_exist
assert_equal false, @config.reset!(:sample)
assert !@config.variables.key?(:sample)
end
def test_reset_should_do_nothing_if_variable_is_not_a_proc
@config.set(:sample, :value)
assert_equal false, @config.reset!(:sample)
assert_equal :value, @config.variables[:sample]
end
def test_reset_should_do_nothing_if_proc_variable_has_not_been_dereferenced
@config.set(:sample) { :value }
assert_equal false, @config.reset!(:sample)
assert_instance_of Proc, @config.variables[:sample]
end
def test_reset_should_restore_variable_to_original_proc_value
@config.set(:sample) { :value }
assert_instance_of Proc, @config.variables[:sample]
@config.fetch(:sample)
assert_instance_of Symbol, @config.variables[:sample]
assert @config.reset!(:sample)
assert_instance_of Proc, @config.variables[:sample]
end
def test_fetch_should_return_stored_non_proc_value
@config.set(:sample, :value)
assert_equal :value, @config.fetch(:sample)
end
def test_fetch_should_raise_index_error_if_variable_does_not_exist
assert_raises(IndexError) do
@config.fetch(:sample)
end
end
def test_fetch_should_return_default_if_variable_does_not_exist_and_default_is_given
assert_nothing_raised do
assert_equal :default_value, @config.fetch(:sample, :default_value)
end
end
def test_fetch_should_invoke_block_if_variable_does_not_exist_and_block_is_given
assert_nothing_raised do
assert_equal :default_value, @config.fetch(:sample) { :default_value }
end
end
def test_fetch_should_raise_argument_error_if_both_default_and_block_are_given
assert_raises(ArgumentError) do
@config.fetch(:sample, :default1) { :default2 }
end
end
def test_fetch_should_dereference_proc_values
@config.set(:sample) { :value }
assert_instance_of Proc, @config.variables[:sample]
assert_equal :value, @config.fetch(:sample)
assert_instance_of Symbol, @config.variables[:sample]
end
def test_square_brackets_should_alias_fetch
@config.set(:sample, :value)
assert_equal :value, @config[:sample]
end
def test_square_brackets_should_return_nil_for_non_existant_variable
assert_nothing_raised do
assert_nil @config[:sample]
end
end
end

View file

@ -2,137 +2,10 @@ $:.unshift File.dirname(__FILE__) + "/../lib"
require 'test/unit'
require 'capistrano/configuration'
require 'flexmock'
class ConfigurationTest < Test::Unit::TestCase
class MockActor
attr_reader :tasks
def initialize(config)
end
def define_task(*args, &block)
(@tasks ||= []).push [args, block].flatten
end
end
class MockSCM
attr_reader :configuration
def initialize(config)
@configuration = config
end
end
def setup
@config = Capistrano::Configuration.new(MockActor)
@config.set :scm, MockSCM
end
def test_version_dir_default
assert "releases", @config.version_dir
end
def test_current_dir_default
assert "current", @config.current_dir
end
def test_shared_dir_default
assert "shared", @config.shared_dir
end
def test_set_repository
@config.set :repository, "/foo/bar/baz"
assert_equal "/foo/bar/baz", @config.repository
end
def test_set_user
@config.set :user, "flippy"
assert_equal "flippy", @config.user
end
def test_define_single_role
@config.role :app, "somewhere.example.com"
assert_equal 1, @config.roles[:app].length
assert_equal "somewhere.example.com", @config.roles[:app].first.host
assert_equal Hash.new, @config.roles[:app].first.options
end
def test_define_single_role_with_options
@config.role :app, "somewhere.example.com", :primary => true
assert_equal 1, @config.roles[:app].length
assert_equal "somewhere.example.com", @config.roles[:app].first.host
assert_equal({:primary => true}, @config.roles[:app].first.options)
end
def test_define_multi_role
@config.role :app, "somewhere.example.com", "else.example.com"
assert_equal 2, @config.roles[:app].length
assert_equal "somewhere.example.com", @config.roles[:app].first.host
assert_equal "else.example.com", @config.roles[:app].last.host
assert_equal({}, @config.roles[:app].first.options)
assert_equal({}, @config.roles[:app].last.options)
end
def test_define_multi_role_with_options
@config.role :app, "somewhere.example.com", "else.example.com", :primary => true
assert_equal 2, @config.roles[:app].length
assert_equal "somewhere.example.com", @config.roles[:app].first.host
assert_equal "else.example.com", @config.roles[:app].last.host
assert_equal({:primary => true}, @config.roles[:app].first.options)
assert_equal({:primary => true}, @config.roles[:app].last.options)
end
def test_load_string_unnamed
@config.load :string => "set :repository, __FILE__"
assert_equal "<eval>", @config.repository
end
def test_load_string_named
@config.load :string => "set :repository, __FILE__", :name => "test.rb"
assert_equal "test.rb", @config.repository
end
def test_load
file = File.dirname(__FILE__) + "/fixtures/config.rb"
@config.load file
assert_equal "1/2/foo", @config.repository
assert_equal "./#{file}.example.com", @config.gateway
assert_equal 1, @config.roles[:web].length
end
def test_load_explicit_name
file = File.dirname(__FILE__) + "/fixtures/config.rb"
@config.load file, :name => "config"
assert_equal "1/2/foo", @config.repository
assert_equal "config.example.com", @config.gateway
assert_equal 1, @config.roles[:web].length
end
def test_load_file_implied_name
file = File.dirname(__FILE__) + "/fixtures/config.rb"
@config.load :file => file
assert_equal "1/2/foo", @config.repository
assert_equal "./#{file}.example.com", @config.gateway
assert_equal 1, @config.roles[:web].length
end
def test_load_file_explicit_name
file = File.dirname(__FILE__) + "/fixtures/config.rb"
@config.load :file => file, :name => "config"
assert_equal "1/2/foo", @config.repository
assert_equal "config.example.com", @config.gateway
assert_equal 1, @config.roles[:web].length
end
def test_load_proc_explicit
@config.load :proc => Proc.new { set :gateway, "nifty.zoo.test" }
assert_equal "nifty.zoo.test", @config.gateway
end
def test_load_proc_implicit
@config.load { set :gateway, "nifty.zoo.test" }
assert_equal "nifty.zoo.test", @config.gateway
@config = Capistrano::Configuration.new
end
def test_task_without_options
@ -153,81 +26,10 @@ class ConfigurationTest < Test::Unit::TestCase
assert_equal block, @config.actor.tasks[0][2]
end
def test_source
@config.set :repository, "/foo/bar/baz"
assert_equal "/foo/bar/baz", @config.source.configuration.repository
end
def test_releases_path_default
@config.set :deploy_to, "/start/of/path"
assert_equal "/start/of/path/releases", @config.releases_path
end
def test_releases_path_custom
@config.set :deploy_to, "/start/of/path"
@config.set :version_dir, "right/here"
assert_equal "/start/of/path/right/here", @config.releases_path
end
def test_current_path_default
@config.set :deploy_to, "/start/of/path"
assert_equal "/start/of/path/current", @config.current_path
end
def test_current_path_custom
@config.set :deploy_to, "/start/of/path"
@config.set :current_dir, "right/here"
assert_equal "/start/of/path/right/here", @config.current_path
end
def test_shared_path_default
@config.set :deploy_to, "/start/of/path"
assert_equal "/start/of/path/shared", @config.shared_path
end
def test_shared_path_custom
@config.set :deploy_to, "/start/of/path"
@config.set :shared_dir, "right/here"
assert_equal "/start/of/path/right/here", @config.shared_path
end
def test_release_path_implicit
@config.set :deploy_to, "/start/of/path"
assert_equal "/start/of/path/releases/#{@config.now.strftime("%Y%m%d%H%M%S")}", @config.release_path
end
def test_release_path_explicit
@config.set :deploy_to, "/start/of/path"
assert_equal "/start/of/path/releases/silly", @config.release_path("silly")
end
def test_task_description
block = Proc.new { }
@config.desc "A sample task"
@config.task :hello, &block
assert_equal "A sample task", @config.actor.tasks[0][1][:desc]
end
def test_set_scm_to_darcs
@config.set :scm, :darcs
assert_equal "Capistrano::SCM::Darcs", @config.source.class.name
end
def test_set_scm_to_subversion
@config.set :scm, :subversion
assert_equal "Capistrano::SCM::Subversion", @config.source.class.name
end
def test_get_proc_variable_sets_original_value_hash
@config.set(:proc) { "foo" }
assert_nil @config[:original_value][:proc]
assert_equal "foo", @config[:proc]
assert_not_nil @config[:original_value][:proc]
assert @config[:original_value][:proc].respond_to?(:call)
end
def test_require
@config.require "#{File.dirname(__FILE__)}/fixtures/custom"
assert_equal "foo", @config.gateway
end
end

View file

@ -1,3 +1,3 @@
Capistrano.configuration(:must_exist).load do
set :gateway, "foo"
ConfigurationLoadingTest::MockConfig.instance(:must_exist).load do
ping! :custom
end

View file

@ -0,0 +1,67 @@
$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
require 'capistrano/server_definition'
class ServerDefinitionTest < Test::Unit::TestCase
def test_new_without_credentials_or_port_should_set_values_to_defaults
server = Capistrano::ServerDefinition.new("www.capistrano.test")
assert_equal "www.capistrano.test", server.host
assert_nil server.user
assert_nil server.port
end
def test_new_with_encoded_user_should_extract_user_and_use_default_port
server = Capistrano::ServerDefinition.new("jamis@www.capistrano.test")
assert_equal "www.capistrano.test", server.host
assert_equal "jamis", server.user
assert_nil server.port
end
def test_new_with_encoded_port_should_extract_port_and_use_default_user
server = Capistrano::ServerDefinition.new("www.capistrano.test:8080")
assert_equal "www.capistrano.test", server.host
assert_nil server.user
assert_equal 8080, server.port
end
def test_new_with_encoded_user_and_port_should_extract_user_and_port
server = Capistrano::ServerDefinition.new("jamis@www.capistrano.test:8080")
assert_equal "www.capistrano.test", server.host
assert_equal "jamis", server.user
assert_equal 8080, server.port
end
def test_new_with_user_as_option_should_use_given_user
server = Capistrano::ServerDefinition.new("www.capistrano.test", :user => "jamis")
assert_equal "www.capistrano.test", server.host
assert_equal "jamis", server.user
assert_nil server.port
end
def test_new_with_port_as_option_should_use_given_user
server = Capistrano::ServerDefinition.new("www.capistrano.test", :port => 8080)
assert_equal "www.capistrano.test", server.host
assert_nil server.user
assert_equal 8080, server.port
end
def test_new_with_option_should_override_encoded_value_and_remove_from_options
server = Capistrano::ServerDefinition.new("jamis@www.capistrano.test:8080", :user => "david", :port => 8081)
assert_equal "www.capistrano.test", server.host
assert_equal "david", server.user
assert_equal 8081, server.port
assert server.options.empty?
end
def test_new_with_option_should_dup_option_hash
options = {}
server = Capistrano::ServerDefinition.new("www.capistrano.test", options)
assert_not_equal options.object_id, server.options.object_id
end
def test_new_with_options_should_keep_options
server = Capistrano::ServerDefinition.new("www.capistrano.test", :primary => true)
assert_equal true, server.options[:primary]
end
end

View file

@ -0,0 +1,107 @@
$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
require 'mocha'
require 'capistrano/task_definition'
require 'capistrano/server_definition'
class TaskDefinitionTest < Test::Unit::TestCase
def setup
@namespace = namespace do |s|
role(s, :app, "app1", :primary => true)
role(s, :app, "app2", "app3")
role(s, :web, "web1", "web2")
role(s, :report, "app2", :no_deploy => true)
role(s, :file, "file", :no_deploy => true)
end
end
def test_task_without_roles_should_apply_to_all_defined_hosts
task = new_task(:testing, @namespace)
assert_equal %w(app1 app2 app3 web1 web2 file).sort, task.servers.map { |s| s.host }.sort
end
def test_task_with_explicit_role_list_should_apply_only_to_those_roles
task = new_task(:testing, @namespace, :roles => %w(app web))
assert_equal %w(app1 app2 app3 web1 web2).sort, task.servers.map { |s| s.host }.sort
end
def test_task_with_single_role_should_apply_only_to_that_role
task = new_task(:testing, @namespace, :roles => :web)
assert_equal %w(web1 web2).sort, task.servers.map { |s| s.host }.sort
end
def test_task_with_hosts_option_should_apply_only_to_those_hosts
task = new_task(:testing, @namespace, :hosts => %w(foo bar))
assert_equal %w(foo bar).sort, task.servers.map { |s| s.host }.sort
end
def test_task_with_single_hosts_option_should_apply_only_to_that_host
task = new_task(:testing, @namespace, :hosts => "foo")
assert_equal %w(foo).sort, task.servers.map { |s| s.host }.sort
end
def test_task_with_roles_as_environment_variable_should_apply_only_to_that_role
ENV['ROLES'] = "app,file"
task = new_task(:testing, @namespace)
assert_equal %w(app1 app2 app3 file).sort, task.servers.map { |s| s.host }.sort
ensure
ENV['ROLES'] = nil
end
def test_task_with_hosts_as_environment_variable_should_apply_only_to_those_hosts
ENV['HOSTS'] = "foo,bar"
task = new_task(:testing, @namespace)
assert_equal %w(foo bar).sort, task.servers.map { |s| s.host }.sort
ensure
ENV['HOSTS'] = nil
end
def test_task_with_only_should_apply_only_to_matching_tasks
task = new_task(:testing, @namespace, :roles => :app, :only => { :primary => true })
assert_equal %w(app1), task.servers.map { |s| s.host }
end
def test_task_with_except_should_apply_only_to_matching_tasks
task = new_task(:testing, @namespace, :except => { :no_deploy => true })
assert_equal %w(app1 app2 app3 web1 web2).sort, task.servers.map { |s| s.host }.sort
end
def test_fqn_at_top_level_should_be_task_name
task = new_task(:testing, @namespace)
assert_equal "testing", task.fully_qualified_name
end
def test_fqn_in_namespace_should_include_namespace_fqn
ns = namespace("outer:inner")
task = new_task(:testing, ns)
assert_equal "outer:inner:testing", task.fully_qualified_name
end
def test_task_should_require_block
assert_raises(ArgumentError) do
Capistrano::TaskDefinition.new(:testing, @namespace)
end
end
private
def namespace(fqn=nil)
space = stub(:roles => {}, :fully_qualified_name => fqn)
yield(space) if block_given?
space
end
def role(space, name, *args)
opts = args.last.is_a?(Hash) ? args.pop : {}
space.roles[name] ||= []
space.roles[name].concat(args.map { |h| Capistrano::ServerDefinition.new(h, opts) })
end
def new_task(name, namespace, options={}, &block)
block ||= Proc.new {}
task = Capistrano::TaskDefinition.new(name, namespace, options, &block)
assert_equal block, task.body
return task
end
end