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:
parent
66524897fb
commit
8e33fdc46b
19 changed files with 1442 additions and 493 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
123
lib/capistrano/configuration/execution.rb
Normal file
123
lib/capistrano/configuration/execution.rb
Normal 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
|
112
lib/capistrano/configuration/loading.rb
Normal file
112
lib/capistrano/configuration/loading.rb
Normal 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
|
126
lib/capistrano/configuration/namespaces.rb
Normal file
126
lib/capistrano/configuration/namespaces.rb
Normal 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
|
52
lib/capistrano/configuration/roles.rb
Normal file
52
lib/capistrano/configuration/roles.rb
Normal 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
|
108
lib/capistrano/configuration/variables.rb
Normal file
108
lib/capistrano/configuration/variables.rb
Normal 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
|
30
lib/capistrano/server_definition.rb
Normal file
30
lib/capistrano/server_definition.rb
Normal 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
|
67
lib/capistrano/task_definition.rb
Normal file
67
lib/capistrano/task_definition.rb
Normal 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
|
|
@ -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
|
||||
|
|
149
test/configuration/execution_test.rb
Normal file
149
test/configuration/execution_test.rb
Normal 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
|
116
test/configuration/loading_test.rb
Normal file
116
test/configuration/loading_test.rb
Normal 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
|
151
test/configuration/namespace_dsl_test.rb
Normal file
151
test/configuration/namespace_dsl_test.rb
Normal 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
|
43
test/configuration/roles_test.rb
Normal file
43
test/configuration/roles_test.rb
Normal 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
|
174
test/configuration/variables_test.rb
Normal file
174
test/configuration/variables_test.rb
Normal 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
|
|
@ -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
|
||||
|
|
4
test/fixtures/custom.rb
vendored
4
test/fixtures/custom.rb
vendored
|
@ -1,3 +1,3 @@
|
|||
Capistrano.configuration(:must_exist).load do
|
||||
set :gateway, "foo"
|
||||
ConfigurationLoadingTest::MockConfig.instance(:must_exist).load do
|
||||
ping! :custom
|
||||
end
|
||||
|
|
67
test/server_definition_test.rb
Normal file
67
test/server_definition_test.rb
Normal 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
|
107
test/task_definition_test.rb
Normal file
107
test/task_definition_test.rb
Normal 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
|
Loading…
Add table
Reference in a new issue