capistrano 2.12.0 → 2.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. data/CHANGELOG +29 -1
  2. data/README.mdown +3 -2
  3. data/bin/capify +1 -3
  4. data/capistrano.gemspec +3 -3
  5. data/lib/capistrano/command.rb +1 -0
  6. data/lib/capistrano/configuration.rb +6 -1
  7. data/lib/capistrano/configuration/actions/inspect.rb +2 -2
  8. data/lib/capistrano/configuration/actions/invocation.rb +7 -0
  9. data/lib/capistrano/configuration/callbacks.rb +22 -4
  10. data/lib/capistrano/configuration/execution.rb +2 -3
  11. data/lib/capistrano/configuration/log_formatters.rb +71 -0
  12. data/lib/capistrano/configuration/namespaces.rb +20 -13
  13. data/lib/capistrano/logger.rb +98 -4
  14. data/lib/capistrano/recipes/deploy.rb +45 -83
  15. data/lib/capistrano/recipes/deploy/assets.rb +1 -1
  16. data/lib/capistrano/recipes/deploy/scm/git.rb +0 -1
  17. data/lib/capistrano/recipes/deploy/scm/none.rb +7 -0
  18. data/lib/capistrano/recipes/deploy/strategy/base.rb +1 -1
  19. data/lib/capistrano/shell.rb +4 -4
  20. data/lib/capistrano/version.rb +2 -7
  21. data/test/command_test.rb +8 -0
  22. data/test/configuration/actions/inspect_test.rb +13 -2
  23. data/test/configuration/actions/invocation_test.rb +6 -1
  24. data/test/configuration/callbacks_test.rb +24 -0
  25. data/test/configuration/namespace_dsl_test.rb +2 -2
  26. data/test/configuration_test.rb +1 -1
  27. data/test/deploy/scm/git_test.rb +1 -1
  28. data/test/deploy/strategy/copy_test.rb +2 -1
  29. data/test/logger_formatting_test.rb +94 -0
  30. data/test/logger_test.rb +12 -1
  31. metadata +51 -19
  32. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  33. data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
@@ -1,5 +1,6 @@
1
1
  require 'benchmark'
2
2
  require 'yaml'
3
+ require 'shellwords'
3
4
  require 'capistrano/recipes/deploy/scm'
4
5
  require 'capistrano/recipes/deploy/strategy'
5
6
 
@@ -22,7 +23,7 @@ _cset(:repository) { abort "Please specify the repository that houses your appl
22
23
  # are not sufficient.
23
24
  # =========================================================================
24
25
 
25
- _cset :scm, :subversion
26
+ _cset(:scm) { scm_default }
26
27
  _cset :deploy_via, :checkout
27
28
 
28
29
  _cset(:deploy_to) { "/u/apps/#{application}" }
@@ -31,9 +32,6 @@ _cset(:revision) { source.head }
31
32
  _cset :rails_env, "production"
32
33
  _cset :rake, "rake"
33
34
 
34
- _cset :maintenance_basename, "maintenance"
35
- _cset(:maintenance_template_path) { File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml") }
36
-
37
35
  # =========================================================================
38
36
  # These variables should NOT be changed unless you are very confident in
39
37
  # what you are doing. Make sure you understand all the implications of your
@@ -79,6 +77,33 @@ _cset(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_r
79
77
  # These are helper methods that will be available to your recipes.
80
78
  # =========================================================================
81
79
 
80
+ # Checks known version control directories to intelligently set the version
81
+ # control in-use. For example, if a .svn directory exists in the project,
82
+ # it will set the :scm variable to :subversion, if a .git directory exists
83
+ # in the project, it will set the :scm variable to :git and so on. If no
84
+ # directory is found, it will default to :git.
85
+ def scm_default
86
+ if File.exist? '.git'
87
+ :git
88
+ elsif File.exist? '.accurev'
89
+ :accurev
90
+ elsif File.exist? '.bzr'
91
+ :bzr
92
+ elsif File.exist? '.cvs'
93
+ :cvs
94
+ elsif File.exist? '_darcs'
95
+ :darcs
96
+ elsif File.exist? '.hg'
97
+ :mercurial
98
+ elsif File.exist? '.perforce'
99
+ :perforce
100
+ elsif File.exist? '.svn'
101
+ :subversion
102
+ else
103
+ :none
104
+ end
105
+ end
106
+
82
107
  # Auxiliary helper method for the `deploy:check' task. Lets you set up your
83
108
  # own dependencies.
84
109
  def depend(location, type, *args)
@@ -240,23 +265,29 @@ namespace :deploy do
240
265
  using the :public_children variable.
241
266
  DESC
242
267
  task :finalize_update, :except => { :no_release => true } do
243
- run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
268
+ escaped_release = latest_release.to_s.shellescape
269
+ commands = []
270
+ commands << "chmod -R -- g+w #{escaped_release}" if fetch(:group_writable, true)
244
271
 
245
272
  # mkdir -p is making sure that the directories are there for some SCM's that don't
246
273
  # save empty folders
247
- run <<-CMD
248
- rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids &&
249
- mkdir -p #{latest_release}/public &&
250
- mkdir -p #{latest_release}/tmp
251
- CMD
252
- shared_children.map do |d|
253
- run "ln -s #{shared_path}/#{d.split('/').last} #{latest_release}/#{d}"
274
+ shared_children.map do |dir|
275
+ d = dir.shellescape
276
+ if (dir.rindex('/')) then
277
+ commands += ["rm -rf -- #{escaped_release}/#{d}",
278
+ "mkdir -p -- #{escaped_release}/#{dir.slice(0..(dir.rindex('/'))).shellescape}"]
279
+ else
280
+ commands << "rm -rf -- #{escaped_release}/#{d}"
281
+ end
282
+ commands << "ln -s -- #{shared_path}/#{dir.split('/').last.shellescape} #{escaped_release}/#{d}"
254
283
  end
255
284
 
285
+ run commands.join(' && ') if commands.any?
286
+
256
287
  if fetch(:normalize_asset_timestamps, true)
257
288
  stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
258
- asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
259
- run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
289
+ asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{escaped_release}/public/#{p}" }
290
+ run("find #{asset_paths.join(" ").shellescape} -exec touch -t -- #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }) if asset_paths.any?
260
291
  end
261
292
  end
262
293
 
@@ -522,73 +553,4 @@ namespace :deploy do
522
553
  system(source.local.log(from))
523
554
  end
524
555
  end
525
-
526
- namespace :web do
527
- desc <<-DESC
528
- Present a maintenance page to visitors. Disables your application's web \
529
- interface by writing a "#{maintenance_basename}.html" file to each web server. The \
530
- servers must be configured to detect the presence of this file, and if \
531
- it is present, always display it instead of performing the request.
532
-
533
- By default, the maintenance page will just say the site is down for \
534
- "maintenance", and will be back "shortly", but you can customize the \
535
- page by specifying the REASON and UNTIL environment variables:
536
-
537
- $ cap deploy:web:disable \\
538
- REASON="hardware upgrade" \\
539
- UNTIL="12pm Central Time"
540
-
541
- You can use a different template for the maintenance page by setting the \
542
- :maintenance_template_path variable in your deploy.rb file. The template file \
543
- should either be a plaintext or an erb file.
544
-
545
- Further customization will require that you write your own task.
546
- DESC
547
- task :disable, :roles => :web, :except => { :no_release => true } do
548
- require 'erb'
549
- on_rollback { run "rm -f #{shared_path}/system/#{maintenance_basename}.html" }
550
-
551
- warn <<-EOHTACCESS
552
-
553
- # Please add something like this to your site's Apache htaccess to redirect users to the maintenance page.
554
- # More Info: http://www.shiftcommathree.com/articles/make-your-rails-maintenance-page-respond-with-a-503
555
-
556
- ErrorDocument 503 /system/#{maintenance_basename}.html
557
- RewriteEngine On
558
- RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$
559
- RewriteCond %{DOCUMENT_ROOT}/system/#{maintenance_basename}.html -f
560
- RewriteCond %{SCRIPT_FILENAME} !#{maintenance_basename}.html
561
- RewriteRule ^.*$ - [redirect=503,last]
562
-
563
- # Or if you are using Nginx add this to your server config:
564
-
565
- if (-f $document_root/system/maintenance.html) {
566
- return 503;
567
- }
568
- error_page 503 @maintenance;
569
- location @maintenance {
570
- rewrite ^(.*)$ /system/maintenance.html last;
571
- break;
572
- }
573
- EOHTACCESS
574
-
575
- reason = ENV['REASON']
576
- deadline = ENV['UNTIL']
577
-
578
- template = File.read(maintenance_template_path)
579
- result = ERB.new(template).result(binding)
580
-
581
- put result, "#{shared_path}/system/#{maintenance_basename}.html", :mode => 0644
582
- end
583
-
584
- desc <<-DESC
585
- Makes the application web-accessible again. Removes the \
586
- "#{maintenance_basename}.html" page generated by deploy:web:disable, which (if your \
587
- web servers are configured correctly) will make your application \
588
- web-accessible again.
589
- DESC
590
- task :enable, :roles => :web, :except => { :no_release => true } do
591
- run "rm -f #{shared_path}/system/#{maintenance_basename}.html"
592
- end
593
- end
594
556
  end
@@ -16,7 +16,7 @@ namespace :deploy do
16
16
  for the assets directory. Assets are shared across deploys to avoid \
17
17
  mid-deploy mismatches between old application html asking for assets \
18
18
  and getting a 404 file not found error. The assets cache is shared \
19
- for efficiency. If you cutomize the assets path prefix, override the \
19
+ for efficiency. If you customize the assets path prefix, override the \
20
20
  :assets_prefix variable to match.
21
21
  DESC
22
22
  task :symlink, :roles => assets_role, :except => { :no_release => true } do
@@ -190,7 +190,6 @@ module Capistrano
190
190
 
191
191
  if variable(:git_enable_submodules)
192
192
  execute << "#{git} submodule #{verbose} init"
193
- execute << "for mod in `#{git} submodule status | awk '{ print $2 }'`; do #{git} config -f .git/config submodule.${mod}.url `#{git} config -f .gitmodules --get submodule.${mod}.url` && echo Synced $mod; done"
194
193
  execute << "#{git} submodule #{verbose} sync"
195
194
  if false == variable(:git_submodules_recursive)
196
195
  execute << "#{git} submodule #{verbose} update --init"
@@ -37,6 +37,13 @@ module Capistrano
37
37
  def query_revision(revision)
38
38
  revision
39
39
  end
40
+
41
+ # log: There's no log, so it just echos from and to.
42
+
43
+ def log(from="", to="")
44
+ "No SCM: #{from} - #{to}"
45
+ end
46
+
40
47
  end
41
48
 
42
49
  end
@@ -73,7 +73,7 @@ module Capistrano
73
73
  private
74
74
 
75
75
  def logger
76
- @logger ||= configuration[:logger] || Capistrano::Logger.new(:output => STDOUT)
76
+ @logger ||= configuration.logger || Capistrano::Logger.new(:output => STDOUT)
77
77
  end
78
78
 
79
79
  # The revision to deploy. Must return a real revision identifier,
@@ -251,10 +251,10 @@ HELP
251
251
  puts "scoping #{scope_type} #{scope_value}"
252
252
  end
253
253
  end
254
- end
255
254
 
256
- # All open sessions, needed to satisfy the Command::Processable include
257
- def sessions
258
- configuration.sessions.values
255
+ # All open sessions, needed to satisfy the Command::Processable include
256
+ def sessions
257
+ configuration.sessions.values
258
+ end
259
259
  end
260
260
  end
@@ -1,16 +1,11 @@
1
- require 'scanf'
2
1
  module Capistrano
3
-
4
2
  class Version
5
-
6
3
  MAJOR = 2
7
- MINOR = 12
8
- PATCH = 0
4
+ MINOR = 13
5
+ PATCH = 5
9
6
 
10
7
  def self.to_s
11
8
  "#{MAJOR}.#{MINOR}.#{PATCH}"
12
9
  end
13
-
14
10
  end
15
-
16
11
  end
@@ -254,6 +254,14 @@ class CommandTest < Test::Unit::TestCase
254
254
  Capistrano::Command.new("echo $CAPISTRANO:OTHER$", [session])
255
255
  end
256
256
 
257
+ def test_input_stream_closed_when_eof_option_is_true
258
+ channel = nil
259
+ session = setup_for_extracting_channel_action { |ch| channel = ch }
260
+ channel.expects(:eof!)
261
+ Capistrano::Command.new("cat", [session], :data => "here we go", :eof => true)
262
+ assert_equal({ :data => 'here we go', :eof => true }, channel[:options])
263
+ end
264
+
257
265
  private
258
266
 
259
267
  def mock_session(channel=nil)
@@ -9,13 +9,19 @@ class ConfigurationActionsInspectTest < Test::Unit::TestCase
9
9
  def setup
10
10
  @config = MockConfig.new
11
11
  @config.stubs(:logger).returns(stub_everything)
12
+ @config.stubs(:sudo).returns('sudo')
12
13
  end
13
14
 
14
15
  def test_stream_should_pass_options_through_to_run
15
- @config.expects(:invoke_command).with("tail -f foo.log", :once => true)
16
+ @config.expects(:invoke_command).with("tail -f foo.log", :once => true, :eof => true)
16
17
  @config.stream("tail -f foo.log", :once => true)
17
18
  end
18
19
 
20
+ def test_stream_with_sudo_should_avoid_closing_stdin
21
+ @config.expects(:invoke_command).with("sudo tail -f foo.log", :once => true, :eof => false)
22
+ @config.stream("sudo tail -f foo.log", :once => true)
23
+ end
24
+
19
25
  def test_stream_should_emit_stdout_via_puts
20
26
  @config.expects(:invoke_command).yields(mock("channel"), :out, "something streamed")
21
27
  @config.expects(:puts).with("something streamed")
@@ -33,10 +39,15 @@ class ConfigurationActionsInspectTest < Test::Unit::TestCase
33
39
  end
34
40
 
35
41
  def test_capture_should_pass_options_merged_with_once_to_run
36
- @config.expects(:invoke_command).with("hostname", :foo => "bar", :once => true)
42
+ @config.expects(:invoke_command).with("hostname", :foo => "bar", :once => true, :eof => true)
37
43
  @config.capture("hostname", :foo => "bar")
38
44
  end
39
45
 
46
+ def test_capture_with_sudo_should_avoid_closing_stdin
47
+ @config.expects(:invoke_command).with("sudo hostname", :foo => "bar", :once => true, :eof => false)
48
+ @config.capture("sudo hostname", :foo => "bar")
49
+ end
50
+
40
51
  def test_capture_with_stderr_should_emit_stderr_via_warn
41
52
  ch = mock("channel")
42
53
  ch.expects(:[]).with(:server).returns(server("capistrano"))
@@ -45,7 +45,7 @@ class ConfigurationActionsInvocationTest < Test::Unit::TestCase
45
45
  end
46
46
 
47
47
  def test_run_options_should_be_passed_to_execute_on_servers
48
- @config.expects(:execute_on_servers).with(:foo => "bar")
48
+ @config.expects(:execute_on_servers).with(:foo => "bar", :eof => true)
49
49
  @config.run "ls", :foo => "bar"
50
50
  end
51
51
 
@@ -115,6 +115,11 @@ class ConfigurationActionsInvocationTest < Test::Unit::TestCase
115
115
  @config.sudo "ls"
116
116
  end
117
117
 
118
+ def test_sudo_should_keep_input_stream_open
119
+ @config.expects(:execute_on_servers).with(:foo => "bar")
120
+ @config.sudo "ls", :foo => "bar"
121
+ end
122
+
118
123
  def test_sudo_should_use_sudo_variable_definition
119
124
  @config.expects(:run).with("/opt/local/bin/sudo -p 'sudo password: ' ls", {})
120
125
  @config.options[:sudo] = "/opt/local/bin/sudo"
@@ -39,11 +39,35 @@ class ConfigurationCallbacksTest < Test::Unit::TestCase
39
39
  @config.before :bar, :foo, "bing:blang", :zip => :zing
40
40
  end
41
41
 
42
+ def test_before_should_map_before_deploy_symlink
43
+ @config.before "deploy:symlink", "bing:blang", "deploy:symlink"
44
+ assert_equal "bing:blang", @config.callbacks[:before][0].source
45
+ assert_equal "deploy:create_symlink", @config.callbacks[:before][1].source
46
+ assert_equal ["deploy:create_symlink"], @config.callbacks[:before][1].only
47
+ end
48
+
49
+ def test_before_should_map_before_deploy_symlink_array
50
+ @config.before ["deploy:symlink", "bingo:blast"], "bing:blang"
51
+ assert_equal ["deploy:create_symlink", "bingo:blast"], @config.callbacks[:before].last.only
52
+ end
53
+
42
54
  def test_after_should_delegate_to_on
43
55
  @config.expects(:on).with(:after, :foo, "bing:blang", {:only => :bar, :zip => :zing})
44
56
  @config.after :bar, :foo, "bing:blang", :zip => :zing
45
57
  end
46
58
 
59
+ def test_after_should_map_before_deploy_symlink
60
+ @config.after "deploy:symlink", "bing:blang", "deploy:symlink"
61
+ assert_equal "bing:blang", @config.callbacks[:after][0].source
62
+ assert_equal "deploy:create_symlink", @config.callbacks[:after][1].source
63
+ assert_equal ["deploy:create_symlink"], @config.callbacks[:after][1].only
64
+ end
65
+
66
+ def test_after_should_map_before_deploy_symlink_array
67
+ @config.after ["deploy:symlink", "bingo:blast"], "bing:blang"
68
+ assert_equal ["deploy:create_symlink", "bingo:blast"], @config.callbacks[:after].last.only
69
+ end
70
+
47
71
  def test_on_with_single_reference_should_add_task_callback
48
72
  @config.on :before, :a_test
49
73
  assert_equal 1, @config.callbacks[:before].length
@@ -321,7 +321,7 @@ class ConfigurationNamespacesDSLTest < Test::Unit::TestCase
321
321
  Kernel.module_eval do
322
322
  def some_weird_method() 'kernel' end
323
323
  end
324
-
324
+
325
325
  @config.namespace(:clash2) {}
326
326
  namespace = @config.namespaces[:clash2]
327
327
  assert_equal 'config', namespace.some_weird_method
@@ -329,4 +329,4 @@ class ConfigurationNamespacesDSLTest < Test::Unit::TestCase
329
329
  Kernel.send :remove_method, :some_weird_method
330
330
  @config.class.send :remove_method, :some_weird_method
331
331
  end
332
- end
332
+ end
@@ -16,7 +16,7 @@ class ConfigurationTest < Test::Unit::TestCase
16
16
  process_args = Proc.new do |tree, session, opts|
17
17
  tree.fallback.command == "echo 'hello world'" &&
18
18
  session == [:session] &&
19
- opts == { :logger => @config.logger }
19
+ opts == { :logger => @config.logger, :eof => true }
20
20
  end
21
21
 
22
22
  Capistrano::Command.expects(:process).with(&process_args)
@@ -127,7 +127,7 @@ class DeploySCMGitTest < Test::Unit::TestCase
127
127
 
128
128
  # with submodules
129
129
  @config[:git_enable_submodules] = true
130
- assert_equal "cd #{dest} && #{git} fetch -q origin && #{git} fetch --tags -q origin && #{git} reset -q --hard #{rev} && #{git} submodule -q init && for mod in `#{git} submodule status | awk '{ print $2 }'`; do #{git} config -f .git/config submodule.${mod}.url `#{git} config -f .gitmodules --get submodule.${mod}.url` && echo Synced $mod; done && #{git} submodule -q sync && export GIT_RECURSIVE=$([ ! \"`#{git} --version`\" \\< \"git version 1.6.5\" ] && echo --recursive) && #{git} submodule -q update --init $GIT_RECURSIVE && #{git} clean -q -d -x -f", @source.sync(rev, dest)
130
+ assert_equal "cd #{dest} && #{git} fetch -q origin && #{git} fetch --tags -q origin && #{git} reset -q --hard #{rev} && #{git} submodule -q init && #{git} submodule -q sync && export GIT_RECURSIVE=$([ ! \"`#{git} --version`\" \\< \"git version 1.6.5\" ] && echo --recursive) && #{git} submodule -q update --init $GIT_RECURSIVE && #{git} clean -q -d -x -f", @source.sync(rev, dest)
131
131
  end
132
132
 
133
133
  def test_sync_with_remote
@@ -6,10 +6,11 @@ require 'stringio'
6
6
  class DeployStrategyCopyTest < Test::Unit::TestCase
7
7
  def setup
8
8
  @config = { :application => "captest",
9
- :logger => Capistrano::Logger.new(:output => StringIO.new),
10
9
  :releases_path => "/u/apps/test/releases",
11
10
  :release_path => "/u/apps/test/releases/1234567890",
12
11
  :real_revision => "154" }
12
+ @config.stubs(:logger).returns(stub_everything)
13
+
13
14
  @source = mock("source")
14
15
  @config.stubs(:source).returns(@source)
15
16
  @strategy = Capistrano::Deploy::Strategy::Copy.new(@config)
@@ -0,0 +1,94 @@
1
+ require File.expand_path("../utils", __FILE__)
2
+ require 'capistrano/logger'
3
+ require 'stringio'
4
+
5
+ Capistrano::Logger.class_eval do
6
+ # Allows formatters to be changed during tests
7
+ def self.formatters=(formatters)
8
+ @formatters = formatters
9
+ @sorted_formatters = nil
10
+ end
11
+ end
12
+
13
+ class LoggerFormattingTest < Test::Unit::TestCase
14
+ def setup
15
+ @io = StringIO.new
16
+ @io.stubs(:tty?).returns(true)
17
+ @logger = Capistrano::Logger.new(:output => @io, :level => 3)
18
+ end
19
+
20
+ def test_matching_with_style_and_color
21
+ Capistrano::Logger.formatters = [{ :match => /^err ::/, :color => :red, :style => :underscore, :level => 0 }]
22
+ @logger.log(0, "err :: Error Occurred")
23
+ assert @io.string.include? "\e[4;31merr :: Error Occurred\e[0m"
24
+ end
25
+
26
+ def test_style_without_color
27
+ Capistrano::Logger.formatters = [{ :match => /.*/, :style => :underscore, :level => 0 }]
28
+ @logger.log(0, "test message")
29
+ # Default color should be blank (0m)
30
+ assert @io.string.include? "\e[4;0mtest message\e[0m"
31
+ end
32
+
33
+ def test_prepending_text
34
+ Capistrano::Logger.formatters = [{ :match => /^executing/, :level => 0, :prepend => '== Currently ' }]
35
+ @logger.log(0, "executing task")
36
+ assert @io.string.include? '== Currently executing task'
37
+ end
38
+
39
+ def test_replacing_matched_text
40
+ Capistrano::Logger.formatters = [{ :match => /^executing/, :level => 0, :replace => 'running' }]
41
+ @logger.log(0, "executing task")
42
+ assert @io.string.include? 'running task'
43
+ end
44
+
45
+ def test_prepending_timestamps
46
+ Capistrano::Logger.formatters = [{ :match => /.*/, :level => 0, :timestamp => true }]
47
+ @logger.log(0, "test message")
48
+ assert @io.string.match /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} test message/
49
+ end
50
+
51
+ def test_formatter_priorities
52
+ Capistrano::Logger.formatters = [
53
+ { :match => /.*/, :color => :red, :level => 0, :priority => -10 },
54
+ { :match => /.*/, :color => :blue, :level => 0, :priority => -20, :prepend => '###' }
55
+ ]
56
+
57
+ @logger.log(0, "test message")
58
+ # Only the red formatter (color 31) should be applied.
59
+ assert @io.string.include? "\e[31mtest message"
60
+ # The blue formatter should not have prepended $$$
61
+ assert !@io.string.include?('###')
62
+ end
63
+
64
+ def test_no_formatting_if_no_color_or_style
65
+ Capistrano::Logger.formatters = []
66
+ @logger.log(0, "test message")
67
+ assert @io.string.include? "*** test message"
68
+ end
69
+
70
+ def test_formatter_log_levels
71
+ Capistrano::Logger.formatters = [{ :match => /.*/, :color => :blue, :level => 3 }]
72
+ @logger.log(0, "test message")
73
+ # Should not match log level
74
+ assert @io.string.include? "*** test message"
75
+
76
+ clear_logger
77
+ @logger.log(3, "test message")
78
+ # Should match log level and apply blue color
79
+ assert @io.string.include? "\e[34mtest message"
80
+ end
81
+
82
+ private
83
+
84
+ def colorize(message, color, style = nil)
85
+ style = "#{style};" if style
86
+ "\e[#{style}#{color}m" + message + "\e[0m"
87
+ end
88
+
89
+ def clear_logger
90
+ @io = StringIO.new
91
+ @io.stubs(:tty?).returns(true)
92
+ @logger.device = @io
93
+ end
94
+ end