mirror of
https://github.com/capistrano/capistrano
synced 2023-03-27 23:21:18 -04:00
Add doctor:variables, :environment, and :gems
This adds various "doctor" tasks that can be used for troubleshooting. To see all the doctor output, run e.g. `cap production doctor`. This will print a report like this: ``` Environment Ruby ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin14] Rubygems 2.6.2 Bundler 1.11.2 Command cap production doctor Gems capistrano 3.4.0 airbrussh 1.0.1 rake 10.5.0 (update available) sshkit 1.9.0 capistrano-bundler 1.1.4 capistrano-rails 1.1.6 Variables :application "myapp" :assets_prefix "assets" :assets_roles [:web] :branch "master" ... etc. ``` To obtain the variables information in particular, code has been added to audit the setting and fetching of variables. Variables set by Capistrano itself and its plugins are whitelisted, but others are "untrusted". If a variable is untrusted and it seems like it is never used, then `doctor:variables` will print a warning (include source location) for that variable name, like this: ``` :copy_strategy is not a recognized Capistrano setting (config/deploy.rb:14) ``` Finally, the RubyGems API is used to check the remote gem repository to see if any newer versions of Capistrano gems are available (this is gracefully skipped if there is no network connection). Any outdated gems will be indicated in the `doctor:gems` output.
This commit is contained in:
parent
66f7bae681
commit
b5e4aa6d5e
17 changed files with 640 additions and 77 deletions
|
@ -31,6 +31,7 @@ and how to configure it, visit the
|
|||
|
||||
### New features:
|
||||
|
||||
* Added a `doctor` task that outputs helpful troubleshooting information. Try it like this: `cap production doctor`. (@mattbrictson)
|
||||
* Added a `dry_run?` helper method
|
||||
* `remove` DSL method for removing values like from arrays like `linked_dirs`
|
||||
* `append` DSL method for pushing values like `linked_dirs`
|
||||
|
|
|
@ -25,9 +25,15 @@ As much the Capistrano community tries to write good, well-tested code, bugs sti
|
|||
|
||||
**In case you’ve run across an already-known issue, check the FAQs first on the [official Capistrano site](http://capistranorb.com).**
|
||||
|
||||
When opening a bug report, please include the following:
|
||||
When opening a bug report, please include the output of the `cap <stage> doctor` task, e.g.:
|
||||
|
||||
* Versions of Ruby, Capistrano, and any plugins you’re using
|
||||
```
|
||||
cap production doctor
|
||||
```
|
||||
|
||||
Also include in your report:
|
||||
|
||||
* Versions of Ruby, Capistrano, and any plugins you’re using (if `doctor` didn't already do this for you)
|
||||
* A description of the troubleshooting steps you’ve taken
|
||||
* Logs and backtraces
|
||||
* Sections of your `deploy.rb` that may be relevant
|
||||
|
|
11
features/doctor.feature
Normal file
11
features/doctor.feature
Normal file
|
@ -0,0 +1,11 @@
|
|||
Feature: Doctor
|
||||
|
||||
Background:
|
||||
Given a test app with the default configuration
|
||||
|
||||
Scenario: Running the doctor task
|
||||
When I run cap "doctor"
|
||||
Then the task is successful
|
||||
And contains "Environment" in the output
|
||||
And contains "Gems" in the output
|
||||
And contains "Variables" in the output
|
|
@ -3,22 +3,19 @@
|
|||
---
|
||||
|
||||
#### Steps to reproduce
|
||||
|
||||
1. Lorem.
|
||||
2. Ipsum..
|
||||
3. Dolor...
|
||||
|
||||
#### Expected behaviour
|
||||
|
||||
Tell us what should happen
|
||||
|
||||
#### Actual behaviour
|
||||
|
||||
Tell us what happens instead
|
||||
|
||||
#### Your configuration
|
||||
|
||||
**Your Operating system (`$ uname -a` if on Linux/Mac)**:
|
||||
|
||||
**Your Ruby Version (`$ ruby -v`):**
|
||||
|
||||
**Your Capistrano version (`$ cap --version`):**
|
||||
|
||||
**Your Capistrano Plugins (`$ bundle list | grep capistrano-`): **
|
||||
Paste Capistrano's `doctor` output here (`cap <stage> doctor`):
|
||||
|
|
|
@ -3,15 +3,12 @@ require_relative "configuration/question"
|
|||
require_relative "configuration/plugin_installer"
|
||||
require_relative "configuration/server"
|
||||
require_relative "configuration/servers"
|
||||
require_relative "configuration/variables"
|
||||
|
||||
module Capistrano
|
||||
class ValidationError < Exception; end
|
||||
|
||||
class Configuration
|
||||
def initialize(config=nil)
|
||||
@config ||= config
|
||||
end
|
||||
|
||||
def self.env
|
||||
@env ||= new
|
||||
end
|
||||
|
@ -20,22 +17,22 @@ module Capistrano
|
|||
@env = new
|
||||
end
|
||||
|
||||
extend Forwardable
|
||||
attr_reader :variables
|
||||
def_delegators :variables,
|
||||
:set, :fetch, :fetch_for, :delete, :keys, :validate
|
||||
|
||||
def initialize(values={})
|
||||
@variables = Variables.new(values)
|
||||
end
|
||||
|
||||
def ask(key, default=nil, options={})
|
||||
question = Question.new(key, default, options)
|
||||
set(key, question)
|
||||
end
|
||||
|
||||
def set(key, value=nil, &block)
|
||||
invoke_validations(key, value, &block)
|
||||
config[key] = block || value
|
||||
|
||||
puts "Config variable set: #{key.inspect} => #{config[key].inspect}" if fetch(:print_config_variables, false)
|
||||
|
||||
config[key]
|
||||
end
|
||||
|
||||
def set_if_empty(key, value=nil, &block)
|
||||
set(key, value, &block) unless config.key? key
|
||||
set(key, value, &block) unless keys.include?(key)
|
||||
end
|
||||
|
||||
def append(key, *values)
|
||||
|
@ -46,16 +43,6 @@ module Capistrano
|
|||
set(key, Array(fetch(key)) - values)
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
config.delete(key)
|
||||
end
|
||||
|
||||
def fetch(key, default=nil, &block)
|
||||
value = fetch_for(key, default, &block)
|
||||
value = set(key, value.call) while callable_without_parameters?(value)
|
||||
value
|
||||
end
|
||||
|
||||
def any?(key)
|
||||
value = fetch(key)
|
||||
if value && value.respond_to?(:any?)
|
||||
|
@ -65,16 +52,6 @@ module Capistrano
|
|||
end
|
||||
end
|
||||
|
||||
def validate(key, &validator)
|
||||
vs = (validators[key] || [])
|
||||
vs << validator
|
||||
validators[key] = vs
|
||||
end
|
||||
|
||||
def keys
|
||||
config.keys
|
||||
end
|
||||
|
||||
def is_question?(key)
|
||||
value = fetch_for(key, nil)
|
||||
!value.nil? && value.is_a?(Question)
|
||||
|
@ -166,42 +143,10 @@ module Capistrano
|
|||
@servers ||= Servers.new
|
||||
end
|
||||
|
||||
def config
|
||||
@config ||= {}
|
||||
end
|
||||
|
||||
def validators
|
||||
@validators ||= {}
|
||||
end
|
||||
|
||||
def installer
|
||||
@installer ||= PluginInstaller.new
|
||||
end
|
||||
|
||||
def fetch_for(key, default, &block)
|
||||
if block_given?
|
||||
config.fetch(key, &block)
|
||||
else
|
||||
config.fetch(key, default)
|
||||
end
|
||||
end
|
||||
|
||||
def callable_without_parameters?(x)
|
||||
x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity == 0)
|
||||
end
|
||||
|
||||
def invoke_validations(key, value, &block)
|
||||
unless value.nil? || block.nil?
|
||||
raise Capistrano::ValidationError, "Value and block both passed to Configuration#set"
|
||||
end
|
||||
|
||||
return unless validators.key? key
|
||||
|
||||
validators[key].each do |validator|
|
||||
validator.call(key, block || value)
|
||||
end
|
||||
end
|
||||
|
||||
def configure_sshkit_output(sshkit)
|
||||
format_args = [fetch(:format)]
|
||||
format_args.push(fetch(:format_options)) if any?(:format_options)
|
||||
|
|
136
lib/capistrano/configuration/variables.rb
Normal file
136
lib/capistrano/configuration/variables.rb
Normal file
|
@ -0,0 +1,136 @@
|
|||
module Capistrano
|
||||
class Configuration
|
||||
# Holds the variables assigned at Capistrano runtime via `set` and retrieved
|
||||
# with `fetch`. Does internal bookkeeping to help identify user mistakes
|
||||
# like spelling errors or unused variables that may lead to unexpected
|
||||
# behavior. Also allows validation rules to be registered with `validate`.
|
||||
class Variables
|
||||
CAPISTRANO_LOCATION = File.expand_path("../..", __FILE__).freeze
|
||||
IGNORED_LOCATIONS = [
|
||||
"#{CAPISTRANO_LOCATION}/configuration/variables.rb:",
|
||||
"#{CAPISTRANO_LOCATION}/configuration.rb:",
|
||||
"#{CAPISTRANO_LOCATION}/dsl/env.rb:",
|
||||
"/dsl.rb:",
|
||||
"/forwardable.rb:"
|
||||
].freeze
|
||||
private_constant :CAPISTRANO_LOCATION, :IGNORED_LOCATIONS
|
||||
|
||||
def initialize(values={})
|
||||
@trusted_keys = []
|
||||
@fetched_keys = []
|
||||
@locations = {}
|
||||
@values = values
|
||||
@trusted = true
|
||||
end
|
||||
|
||||
def untrusted!
|
||||
@trusted = false
|
||||
yield
|
||||
ensure
|
||||
@trusted = true
|
||||
end
|
||||
|
||||
def set(key, value=nil, &block)
|
||||
invoke_validations(key, value, &block)
|
||||
@trusted_keys << key if trusted?
|
||||
remember_location(key)
|
||||
values[key] = block || value
|
||||
trace_set(key)
|
||||
values[key]
|
||||
end
|
||||
|
||||
def fetch(key, default=nil, &block)
|
||||
fetched_keys << key
|
||||
peek(key, default, &block)
|
||||
end
|
||||
|
||||
# Internal use only.
|
||||
def peek(key, default=nil, &block)
|
||||
value = fetch_for(key, default, &block)
|
||||
while callable_without_parameters?(value)
|
||||
value = (values[key] = value.call)
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
def fetch_for(key, default, &block)
|
||||
block ? values.fetch(key, &block) : values.fetch(key, default)
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
values.delete(key)
|
||||
end
|
||||
|
||||
def validate(key, &validator)
|
||||
vs = (validators[key] || [])
|
||||
vs << validator
|
||||
validators[key] = vs
|
||||
end
|
||||
|
||||
def trusted_keys
|
||||
@trusted_keys.dup
|
||||
end
|
||||
|
||||
def untrusted_keys
|
||||
keys - @trusted_keys
|
||||
end
|
||||
|
||||
def keys
|
||||
values.keys
|
||||
end
|
||||
|
||||
# Keys that have been set, but which have never been fetched.
|
||||
def unused_keys
|
||||
keys - fetched_keys
|
||||
end
|
||||
|
||||
# Returns an array of source file location(s) where the given key was
|
||||
# assigned (i.e. where `set` was called). If the key was never assigned,
|
||||
# returns `nil`.
|
||||
def source_locations(key)
|
||||
locations[key]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :locations, :values, :fetched_keys
|
||||
|
||||
def trusted?
|
||||
@trusted
|
||||
end
|
||||
|
||||
def remember_location(key)
|
||||
location = caller.find do |line|
|
||||
IGNORED_LOCATIONS.none? { |i| line.include?(i) }
|
||||
end
|
||||
(locations[key] ||= []) << location
|
||||
end
|
||||
|
||||
def callable_without_parameters?(x)
|
||||
x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity == 0)
|
||||
end
|
||||
|
||||
def validators
|
||||
@validators ||= {}
|
||||
end
|
||||
|
||||
def invoke_validations(key, value, &block)
|
||||
unless value.nil? || block.nil?
|
||||
raise Capistrano::ValidationError,
|
||||
"Value and block both passed to Configuration#set"
|
||||
end
|
||||
|
||||
return unless validators.key? key
|
||||
|
||||
validators[key].each do |validator|
|
||||
validator.call(key, block || value)
|
||||
end
|
||||
end
|
||||
|
||||
def trace_set(key)
|
||||
return unless fetch(:print_config_variables, false)
|
||||
puts "Config variable set: #{key.inspect} => #{values[key].inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
5
lib/capistrano/doctor.rb
Normal file
5
lib/capistrano/doctor.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
require "capistrano/doctor/environment_doctor"
|
||||
require "capistrano/doctor/gems_doctor"
|
||||
require "capistrano/doctor/variables_doctor"
|
||||
|
||||
load File.expand_path("../tasks/doctor.rake", __FILE__)
|
19
lib/capistrano/doctor/environment_doctor.rb
Normal file
19
lib/capistrano/doctor/environment_doctor.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require "capistrano/doctor/output_helpers"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
class EnvironmentDoctor
|
||||
include Capistrano::Doctor::OutputHelpers
|
||||
|
||||
def call
|
||||
title("Environment")
|
||||
puts <<-OUT.gsub(/^\s+/, "")
|
||||
Ruby #{RUBY_DESCRIPTION}
|
||||
Rubygems #{Gem::VERSION}
|
||||
Bundler #{defined?(Bundler::VERSION) ? Bundler::VERSION : 'N/A'}
|
||||
Command #{$PROGRAM_NAME} #{ARGV.join(' ')}
|
||||
OUT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
45
lib/capistrano/doctor/gems_doctor.rb
Normal file
45
lib/capistrano/doctor/gems_doctor.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
require "capistrano/doctor/output_helpers"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
# Prints table of all Capistrano-related gems and their version numbers. If
|
||||
# there is a newer version of a gem available, call attention to it.
|
||||
class GemsDoctor
|
||||
include Capistrano::Doctor::OutputHelpers
|
||||
|
||||
def call
|
||||
title("Gems")
|
||||
table(all_gem_names) do |gem, row|
|
||||
row.yellow if update_available?(gem)
|
||||
row << gem
|
||||
row << installed_gem_version(gem)
|
||||
row << "(update available)" if update_available?(gem)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def installed_gem_version(gem_name)
|
||||
Gem.loaded_specs[gem_name].version
|
||||
end
|
||||
|
||||
def update_available?(gem_name)
|
||||
latest = Gem.latest_version_for(gem_name)
|
||||
return false if latest.nil?
|
||||
latest > installed_gem_version(gem_name)
|
||||
end
|
||||
|
||||
def all_gem_names
|
||||
core_gem_names + plugin_gem_names
|
||||
end
|
||||
|
||||
def core_gem_names
|
||||
%w(capistrano airbrussh rake sshkit) & Gem.loaded_specs.keys
|
||||
end
|
||||
|
||||
def plugin_gem_names
|
||||
(Gem.loaded_specs.keys - ["capistrano"]).grep(/capistrano/).sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
79
lib/capistrano/doctor/output_helpers.rb
Normal file
79
lib/capistrano/doctor/output_helpers.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
module Capistrano
|
||||
module Doctor
|
||||
# Helper methods for pretty-printing doctor output to stdout. All output
|
||||
# (other than `title`) is indented by four spaces to facilitate copying and
|
||||
# pasting this output into e.g. GitHub or Stack Overflow to achieve code
|
||||
# formatting.
|
||||
module OutputHelpers
|
||||
class Row
|
||||
attr_reader :color
|
||||
attr_reader :values
|
||||
|
||||
def initialize
|
||||
@values = []
|
||||
end
|
||||
|
||||
def <<(value)
|
||||
values << value
|
||||
end
|
||||
|
||||
def yellow
|
||||
@color = :yellow
|
||||
end
|
||||
end
|
||||
|
||||
# Prints a table for a given array of records. For each record, the block
|
||||
# is yielded two arguments: the record and a Row object. To print values
|
||||
# for that record, add values using `row << "some value"`. A row can
|
||||
# optionally be highlighted in yellow using `row.yellow`.
|
||||
def table(records, &block)
|
||||
return if records.empty?
|
||||
rows = collect_rows(records, &block)
|
||||
col_widths = calculate_column_widths(rows)
|
||||
|
||||
rows.each do |row|
|
||||
line = row.values.each_with_index.map do |value, col|
|
||||
value.to_s.ljust(col_widths[col])
|
||||
end.join(" ").rstrip
|
||||
line = color.colorize(line, row.color) if row.color
|
||||
puts line
|
||||
end
|
||||
end
|
||||
|
||||
# Prints a title in blue with surrounding newlines.
|
||||
def title(text)
|
||||
# Use $stdout directly to bypass the indentation that our `puts` does.
|
||||
$stdout.puts(color.colorize("\n#{text}\n", :blue))
|
||||
end
|
||||
|
||||
# Prints text in yellow.
|
||||
def warning(text)
|
||||
puts color.colorize(text, :yellow)
|
||||
end
|
||||
|
||||
# Override `Kernel#puts` to prepend four spaces to each line.
|
||||
def puts(string=nil)
|
||||
$stdout.puts(string.to_s.gsub(/^/, " "))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collect_rows(records)
|
||||
records.map do |rec|
|
||||
Row.new.tap { |row| yield(rec, row) }
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_column_widths(rows)
|
||||
num_columns = rows.map { |row| row.values.length }.max
|
||||
Array.new(num_columns) do |col|
|
||||
rows.map { |row| row.values[col].to_s.length }.max
|
||||
end
|
||||
end
|
||||
|
||||
def color
|
||||
@color ||= SSHKit::Color.new($stdout)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
66
lib/capistrano/doctor/variables_doctor.rb
Normal file
66
lib/capistrano/doctor/variables_doctor.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
require "capistrano/doctor/output_helpers"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
# Prints a table of all Capistrano variables and their current values. If
|
||||
# there are unrecognized variables, print warnings for them.
|
||||
class VariablesDoctor
|
||||
# These are keys that have no default values in Capistrano, but are
|
||||
# nonetheless expected to be set.
|
||||
WHITELIST = [:application, :repo_url].freeze
|
||||
private_constant :WHITELIST
|
||||
|
||||
include Capistrano::Doctor::OutputHelpers
|
||||
|
||||
def initialize(env=Capistrano::Configuration.env)
|
||||
@env = env
|
||||
end
|
||||
|
||||
def call
|
||||
title("Variables")
|
||||
values = inspect_all_values
|
||||
|
||||
table(variables.keys.sort) do |key, row|
|
||||
row.yellow if suspicious_keys.include?(key)
|
||||
row << ":#{key}"
|
||||
row << values[key]
|
||||
end
|
||||
|
||||
puts if suspicious_keys.any?
|
||||
|
||||
suspicious_keys.sort.each do |key|
|
||||
warning(
|
||||
":#{key} is not a recognized Capistrano setting (#{location(key)})"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :env
|
||||
|
||||
def variables
|
||||
env.variables
|
||||
end
|
||||
|
||||
def inspect_all_values
|
||||
variables.keys.each_with_object({}) do |key, inspected|
|
||||
inspected[key] = if env.is_question?(key)
|
||||
"<ask>"
|
||||
else
|
||||
variables.peek(key).inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def suspicious_keys
|
||||
(variables.untrusted_keys & variables.unused_keys) - WHITELIST
|
||||
end
|
||||
|
||||
def location(key)
|
||||
loc = variables.source_locations(key).first
|
||||
loc && loc.sub(/^#{Regexp.quote(Dir.pwd)}/, "").sub(/:in.*/, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
require "capistrano/doctor"
|
||||
require "capistrano/immutable_task"
|
||||
include Capistrano::DSL
|
||||
|
||||
|
@ -22,8 +23,10 @@ stages.each do |stage|
|
|||
|
||||
invoke "load:defaults"
|
||||
Rake.application["load:defaults"].extend(Capistrano::ImmutableTask)
|
||||
load deploy_config_path
|
||||
load stage_config_path.join("#{stage}.rb")
|
||||
env.variables.untrusted! do
|
||||
load deploy_config_path
|
||||
load stage_config_path.join("#{stage}.rb")
|
||||
end
|
||||
load "capistrano/#{fetch(:scm)}.rb"
|
||||
I18n.locale = fetch(:locale, :en)
|
||||
configure_backend
|
||||
|
|
19
lib/capistrano/tasks/doctor.rake
Normal file
19
lib/capistrano/tasks/doctor.rake
Normal file
|
@ -0,0 +1,19 @@
|
|||
desc "Display a Capistrano troubleshooting report (all doctor: tasks)"
|
||||
task doctor: ["doctor:environment", "doctor:gems", "doctor:variables"]
|
||||
|
||||
namespace :doctor do
|
||||
desc "Display Ruby environment details"
|
||||
task :environment do
|
||||
Capistrano::Doctor::EnvironmentDoctor.new.call
|
||||
end
|
||||
|
||||
desc "Display Capistrano gem versions"
|
||||
task :gems do
|
||||
Capistrano::Doctor::GemsDoctor.new.call
|
||||
end
|
||||
|
||||
desc "Display the values of all Capistrano variables"
|
||||
task :variables do
|
||||
Capistrano::Doctor::VariablesDoctor.new.call
|
||||
end
|
||||
end
|
44
spec/lib/capistrano/doctor/environment_doctor_spec.rb
Normal file
44
spec/lib/capistrano/doctor/environment_doctor_spec.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
require "spec_helper"
|
||||
require "capistrano/doctor/environment_doctor"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
describe EnvironmentDoctor do
|
||||
let(:doc) { EnvironmentDoctor.new }
|
||||
|
||||
it "prints using 4-space indentation" do
|
||||
expect { doc.call }.to output(/^ {4}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints the Ruby version" do
|
||||
expect { doc.call }.to\
|
||||
output(/#{Regexp.quote(RUBY_DESCRIPTION)}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints the Rubygems version" do
|
||||
expect { doc.call }.to output(/#{Regexp.quote(Gem::VERSION)}/).to_stdout
|
||||
end
|
||||
|
||||
describe "Rake" do
|
||||
before do
|
||||
load File.expand_path("../../../../../lib/capistrano/doctor.rb",
|
||||
__FILE__)
|
||||
end
|
||||
|
||||
after do
|
||||
Rake::Task.clear
|
||||
end
|
||||
|
||||
it "has an doctor:environment task that calls EnvironmentDoctor" do
|
||||
EnvironmentDoctor.any_instance.expects(:call)
|
||||
Rake::Task["doctor:environment"].invoke
|
||||
end
|
||||
|
||||
it "has a doctor task that depends on doctor:environment" do
|
||||
expect(Rake::Task["doctor"].prerequisites).to \
|
||||
include("doctor:environment")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
61
spec/lib/capistrano/doctor/gems_doctor_spec.rb
Normal file
61
spec/lib/capistrano/doctor/gems_doctor_spec.rb
Normal file
|
@ -0,0 +1,61 @@
|
|||
require "spec_helper"
|
||||
require "capistrano/doctor/gems_doctor"
|
||||
require "airbrussh/version"
|
||||
require "sshkit/version"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
describe GemsDoctor do
|
||||
let(:doc) { GemsDoctor.new }
|
||||
|
||||
it "prints using 4-space indentation" do
|
||||
expect { doc.call }.to output(/^ {4}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints the Capistrano version" do
|
||||
expect { doc.call }.to\
|
||||
output(/capistrano\s+#{Regexp.quote(Capistrano::VERSION)}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints the Rake version" do
|
||||
expect { doc.call }.to\
|
||||
output(/rake\s+#{Regexp.quote(Rake::VERSION)}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints the SSHKit version" do
|
||||
expect { doc.call }.to\
|
||||
output(/sshkit\s+#{Regexp.quote(SSHKit::VERSION)}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints the Airbrussh version" do
|
||||
expect { doc.call }.to\
|
||||
output(/airbrussh\s+#{Regexp.quote(Airbrussh::VERSION)}/).to_stdout
|
||||
end
|
||||
|
||||
it "warns that new version is available" do
|
||||
Gem.stubs(:latest_version_for).returns(Gem::Version.new("99.0.0"))
|
||||
expect { doc.call }.to output(/\(update available\)/).to_stdout
|
||||
end
|
||||
|
||||
describe "Rake" do
|
||||
before do
|
||||
load File.expand_path("../../../../../lib/capistrano/doctor.rb",
|
||||
__FILE__)
|
||||
end
|
||||
|
||||
after do
|
||||
Rake::Task.clear
|
||||
end
|
||||
|
||||
it "has an doctor:gems task that calls GemsDoctor" do
|
||||
GemsDoctor.any_instance.expects(:call)
|
||||
Rake::Task["doctor:gems"].invoke
|
||||
end
|
||||
|
||||
it "has a doctor task that depends on doctor:gems" do
|
||||
expect(Rake::Task["doctor"].prerequisites).to include("doctor:gems")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
spec/lib/capistrano/doctor/output_helpers_spec.rb
Normal file
47
spec/lib/capistrano/doctor/output_helpers_spec.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
require "spec_helper"
|
||||
require "capistrano/doctor/output_helpers"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
describe OutputHelpers do
|
||||
include OutputHelpers
|
||||
|
||||
# Force color for the purpose of these tests
|
||||
before { ENV.stubs(:[]).with("SSHKIT_COLOR").returns("1") }
|
||||
|
||||
it "prints titles in blue with newlines and without indentation" do
|
||||
expect { title("Hello!") }.to\
|
||||
output("\e[0;34;49m\nHello!\n\e[0m\n").to_stdout
|
||||
end
|
||||
|
||||
it "prints warnings in yellow with 4-space indentation" do
|
||||
expect { warning("Yikes!") }.to\
|
||||
output(" \e[0;33;49mYikes!\e[0m\n").to_stdout
|
||||
end
|
||||
|
||||
it "overrides puts to indent 4 spaces per line" do
|
||||
expect { puts("one\ntwo") }.to output(" one\n two\n").to_stdout
|
||||
end
|
||||
|
||||
it "formats tables with indent, aligned columns and per-row color" do
|
||||
data = [
|
||||
["one", ".", "1"],
|
||||
["two", "..", "2"],
|
||||
["three", "...", "3"]
|
||||
]
|
||||
block = proc do |record, row|
|
||||
row.yellow if record.first == "two"
|
||||
row << record[0]
|
||||
row << record[1]
|
||||
row << record[2]
|
||||
end
|
||||
expected_output = <<-OUT
|
||||
one . 1
|
||||
\e[0;33;49mtwo .. 2\e[0m
|
||||
three ... 3
|
||||
OUT
|
||||
expect { table(data, &block) }.to output(expected_output).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
79
spec/lib/capistrano/doctor/variables_doctor_spec.rb
Normal file
79
spec/lib/capistrano/doctor/variables_doctor_spec.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
require "spec_helper"
|
||||
require "capistrano/doctor/variables_doctor"
|
||||
|
||||
module Capistrano
|
||||
module Doctor
|
||||
describe VariablesDoctor do
|
||||
include Capistrano::DSL
|
||||
|
||||
let(:doc) { VariablesDoctor.new }
|
||||
|
||||
before do
|
||||
set :branch, "master"
|
||||
set :pty, false
|
||||
|
||||
env.variables.untrusted! do
|
||||
set :application, "my_app"
|
||||
set :repo_url, ".git"
|
||||
set :copy_strategy, :scp
|
||||
set :custom_setting, "hello"
|
||||
ask :secret
|
||||
end
|
||||
|
||||
fetch :custom_setting
|
||||
end
|
||||
|
||||
after { Capistrano::Configuration.reset! }
|
||||
|
||||
it "prints using 4-space indentation" do
|
||||
expect { doc.call }.to output(/^ {4}/).to_stdout
|
||||
end
|
||||
|
||||
it "prints variable names and values" do
|
||||
expect { doc.call }.to output(/:branch\s+"master"$/).to_stdout
|
||||
expect { doc.call }.to output(/:pty\s+false$/).to_stdout
|
||||
expect { doc.call }.to output(/:application\s+"my_app"$/).to_stdout
|
||||
expect { doc.call }.to output(/:repo_url\s+".git"$/).to_stdout
|
||||
expect { doc.call }.to output(/:copy_strategy\s+:scp$/).to_stdout
|
||||
expect { doc.call }.to output(/:custom_setting\s+"hello"$/).to_stdout
|
||||
end
|
||||
|
||||
it "prints unanswered question variable as <ask>" do
|
||||
expect { doc.call }.to output(/:secret\s+<ask>$/).to_stdout
|
||||
end
|
||||
|
||||
it "prints warning for unrecognized variable" do
|
||||
expect { doc.call }.to \
|
||||
output(/:copy_strategy is not a recognized Capistrano setting/)\
|
||||
.to_stdout
|
||||
end
|
||||
|
||||
it "does not print warning for unrecognized variable that is fetched" do
|
||||
expect { doc.call }.not_to \
|
||||
output(/:custom_setting is not a recognized Capistrano setting/)\
|
||||
.to_stdout
|
||||
end
|
||||
|
||||
describe "Rake" do
|
||||
before do
|
||||
load File.expand_path("../../../../../lib/capistrano/doctor.rb",
|
||||
__FILE__)
|
||||
end
|
||||
|
||||
after do
|
||||
Rake::Task.clear
|
||||
end
|
||||
|
||||
it "has an doctor:variables task that calls VariablesDoctor" do
|
||||
VariablesDoctor.any_instance.expects(:call)
|
||||
Rake::Task["doctor:variables"].invoke
|
||||
end
|
||||
|
||||
it "has a doctor task that depends on doctor:variables" do
|
||||
expect(Rake::Task["doctor"].prerequisites).to \
|
||||
include("doctor:variables")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue