capistrano 2.12.0 → 2.13.5

Sign up to get free protection for your applications and to get access to all the features.
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