<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -142,7 +142,13 @@ module Capistrano
     # * +data+: (optional), a string to be sent to the command via it's stdin
     # * +env+: (optional), a string or hash to be interpreted as environment
     #   variables that should be defined for this command invocation.
-    def initialize(tree, sessions, options={})
+    def initialize(tree, sessions, options={}, &amp;block)
+      if String === tree
+        tree = Tree.new(nil) { |t| t.else(tree, &amp;block) }
+      elsif block
+        raise ArgumentError, &quot;block given with tree argument&quot;
+      end
+
       @tree = tree
       @sessions = sessions
       @options = options
@@ -160,9 +166,10 @@ module Capistrano
       logger.trace &quot;command finished&quot; if logger
 
       if (failed = @channels.select { |ch| ch[:status] != 0 }).any?
-        hosts = failed.map { |ch| ch[:server] }
-        error = CommandError.new(&quot;command #{command.inspect} failed on #{hosts.join(',')}&quot;)
-        error.hosts = hosts
+        commands = failed.inject({}) { |map, ch| (map[ch[:command]] ||= []) &lt;&lt; ch[:server]; map }
+        message = commands.map { |command, list| &quot;#{command.inspect} on #{list.join(',')}&quot; }.join(&quot;; &quot;)
+        error = CommandError.new(&quot;failed: #{message}&quot;)
+        error.hosts = commands.values.flatten
         raise error
       end
 
@@ -207,6 +214,7 @@ module Capistrano
                   end
 
                   command_line = [environment, shell, cmd].compact.join(&quot; &quot;)
+                  ch[:command] = command_line
 
                   ch.exec(command_line)
                   ch.send_data(options[:data]) if options[:data]</diff>
      <filename>lib/capistrano/command.rb</filename>
    </modified>
    <modified>
      <diff>@@ -50,7 +50,7 @@ module Capistrano
         # stdout), and the data that was received.
         def run(cmd, options={}, &amp;block)
           block ||= self.class.default_io_proc
-          tree = Command::Tree.new(self) { |t| t.else(cmd, block) }
+          tree = Command::Tree.new(self) { |t| t.else(cmd, &amp;block) }
           run_tree(tree, options)
         end
 </diff>
      <filename>lib/capistrano/configuration/actions/invocation.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,22 +1,21 @@
 require &quot;utils&quot;
 require 'capistrano/command'
+require 'capistrano/configuration'
 
 class CommandTest &lt; Test::Unit::TestCase
   def test_command_should_open_channels_on_all_sessions
-    s1 = mock(:open_channel =&gt; nil)
-    s2 = mock(:open_channel =&gt; nil)
-    s3 = mock(:open_channel =&gt; nil)
-    assert_equal &quot;ls&quot;, Capistrano::Command.new(&quot;ls&quot;, [s1, s2, s3]).command
+    s1, s2, s3 = mock_session, mock_session, mock_session
+    assert_equal &quot;ls&quot;, Capistrano::Command.new(&quot;ls&quot;, [s1, s2, s3]).tree.fallback.command
   end
 
   def test_command_with_newlines_should_be_properly_escaped
-    cmd = Capistrano::Command.new(&quot;ls\necho&quot;, [mock(:open_channel =&gt; nil)])
-    assert_equal &quot;ls\\\necho&quot;, cmd.command
+    cmd = Capistrano::Command.new(&quot;ls\necho&quot;, [mock_session])
+    assert_equal &quot;ls\\\necho&quot;, cmd.tree.fallback.command
   end
 
   def test_command_with_windows_newlines_should_be_properly_escaped
-    cmd = Capistrano::Command.new(&quot;ls\r\necho&quot;, [mock(:open_channel =&gt; nil)])
-    assert_equal &quot;ls\\\necho&quot;, cmd.command
+    cmd = Capistrano::Command.new(&quot;ls\r\necho&quot;, [mock_session])
+    assert_equal &quot;ls\\\necho&quot;, cmd.tree.fallback.command
   end
 
   def test_command_with_pty_should_request_pty_and_register_success_callback
@@ -76,25 +75,17 @@ class CommandTest &lt; Test::Unit::TestCase
   end
 
   def test_open_channel_should_set_host_key_on_channel
-    session = mock(:xserver =&gt; server(&quot;capistrano&quot;))
-    channel = stub_everything
-
-    session.expects(:open_channel).yields(channel)
-    channel.expects(:[]=).with(:host, &quot;capistrano&quot;)
-    channel.stubs(:[]).with(:host).returns(&quot;capistrano&quot;)
-
+    channel = nil
+    session = setup_for_extracting_channel_action { |ch| channel = ch }
     Capistrano::Command.new(&quot;ls&quot;, [session])
+    assert_equal &quot;capistrano&quot;, channel[:host]
   end
 
   def test_open_channel_should_set_options_key_on_channel
-    session = mock(:xserver =&gt; server(&quot;capistrano&quot;))
-    channel = stub_everything
-
-    session.expects(:open_channel).yields(channel)
-    channel.expects(:[]=).with(:options, {:data =&gt; &quot;here we go&quot;})
-    channel.stubs(:[]).with(:host).returns(&quot;capistrano&quot;)
-
+    channel = nil
+    session = setup_for_extracting_channel_action { |ch| channel = ch }
     Capistrano::Command.new(&quot;ls&quot;, [session], :data =&gt; &quot;here we go&quot;)
+    assert_equal({ :data =&gt; 'here we go' }, channel[:options])
   end
 
   def test_successful_channel_should_send_command
@@ -157,17 +148,17 @@ class CommandTest &lt; Test::Unit::TestCase
 
   def test_on_request_should_record_exit_status
     data = mock(:read_long =&gt; 5)
-    session = setup_for_extracting_channel_action([:on_request, &quot;exit-status&quot;], data) do |ch|
-      ch.expects(:[]=).with(:status, 5)
-    end
+    channel = nil
+    session = setup_for_extracting_channel_action([:on_request, &quot;exit-status&quot;], data) { |ch| channel = ch }
     Capistrano::Command.new(&quot;ls&quot;, [session])
+    assert_equal 5, channel[:status]
   end
 
   def test_on_close_should_set_channel_closed
-    session = setup_for_extracting_channel_action(:on_close) do |ch|
-      ch.expects(:[]=).with(:closed, true)
-    end
+    channel = nil
+    session = setup_for_extracting_channel_action(:on_close) { |ch| channel = ch }
     Capistrano::Command.new(&quot;ls&quot;, [session])
+    assert channel[:closed]
   end
 
   def test_stop_should_close_all_open_channels
@@ -228,10 +219,8 @@ class CommandTest &lt; Test::Unit::TestCase
 
   def test_process_should_instantiate_command_and_process!
     cmd = mock(&quot;command&quot;, :process! =&gt; nil)
-    Capistrano::Command.expects(:new).with(&quot;ls -l&quot;, %w(a b c), {:foo =&gt; &quot;bar&quot;}).yields(:command).returns(cmd)
-    parameter = nil
-    Capistrano::Command.process(&quot;ls -l&quot;, %w(a b c), :foo =&gt; &quot;bar&quot;) { |cmd| parameter = cmd }
-    assert_equal :command, parameter
+    Capistrano::Command.expects(:new).with(&quot;ls -l&quot;, %w(a b c), {:foo =&gt; &quot;bar&quot;}).returns(cmd)
+    Capistrano::Command.process(&quot;ls -l&quot;, %w(a b c), :foo =&gt; &quot;bar&quot;)
   end
 
   def test_process_with_host_placeholder_should_substitute_placeholder_with_each_host
@@ -254,16 +243,20 @@ class CommandTest &lt; Test::Unit::TestCase
       stub('session', :open_channel =&gt; channel,
         :preprocess =&gt; true,
         :postprocess =&gt; true,
-        :listeners =&gt; {})
+        :listeners =&gt; {},
+        :xserver =&gt; server(&quot;capistrano&quot;))
+    end
+
+    class MockChannel &lt; Hash
+      def close
+      end
     end
 
     def new_channel(closed, status=nil)
-      ch = mock(&quot;channel&quot;)
-      ch.expects(:[]).with(:closed).returns(closed)
-      ch.expects(:[]).with(:status).returns(status) if status
+      ch = MockChannel.new
+      ch.update({ :closed =&gt; closed, :host =&gt; &quot;capistrano&quot;, :server =&gt; server(&quot;capistrano&quot;) })
+      ch[:status] = status if status
       ch.expects(:close) unless closed
-      ch.stubs(:[]).with(:host).returns(&quot;capistrano&quot;)
-      ch.stubs(:[]).with(:server).returns(server(&quot;capistrano&quot;))
       ch
     end
 
@@ -271,11 +264,15 @@ class CommandTest &lt; Test::Unit::TestCase
       s = server(&quot;capistrano&quot;)
       session = mock(&quot;session&quot;, :xserver =&gt; s)
 
-      channel = stub_everything
+      channel = {}
       session.expects(:open_channel).yields(channel)
 
-      channel.stubs(:[]).with(:server).returns(s)
-      channel.stubs(:[]).with(:host).returns(s.host)
+      channel.stubs(:on_data)
+      channel.stubs(:on_extended_data)
+      channel.stubs(:on_request)
+      channel.stubs(:on_close)
+      channel.stubs(:exec)
+      channel.stubs(:send_data)
 
       if action
         action = Array(action)</diff>
      <filename>test/command_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -47,22 +47,6 @@ class ConfigurationActionsInvocationTest &lt; Test::Unit::TestCase
     @config.run &quot;ls&quot;, :foo =&gt; &quot;bar&quot;
   end
 
-  def test_run_without_block_should_use_default_io_proc
-    @config.expects(:execute_on_servers).yields(%w(s1 s2 s3).map { |s| server(s) })
-    @config.expects(:sessions).returns(Hash.new { |h,k| h[k] = k.host.to_sym }).times(3)
-    prepare_command(&quot;ls&quot;, [:s1, :s2, :s3], {:logger =&gt; @config.logger})
-    MockConfig.default_io_proc = inspectable_proc
-    @config.run &quot;ls&quot;
-  end
-
-  def test_run_with_block_should_use_block
-    @config.expects(:execute_on_servers).yields(%w(s1 s2 s3).map { |s| mock(:host =&gt; s) })
-    @config.expects(:sessions).returns(Hash.new { |h,k| h[k] = k.host.to_sym }).times(3)
-    prepare_command(&quot;ls&quot;, [:s1, :s2, :s3], {:logger =&gt; @config.logger})
-    MockConfig.default_io_proc = Proc.new { |a,b,c| raise &quot;shouldn't get here&quot; }
-    @config.run(&quot;ls&quot;, &amp;inspectable_proc)
-  end
-
   def test_add_default_command_options_should_return_bare_options_if_there_is_no_env_or_shell_specified
     assert_equal({:foo =&gt; &quot;bar&quot;}, @config.add_default_command_options(:foo =&gt; &quot;bar&quot;))
   end
@@ -182,7 +166,7 @@ class ConfigurationActionsInvocationTest &lt; Test::Unit::TestCase
     a = mock(&quot;channel&quot;, :called =&gt; true)
     b = mock(&quot;stream&quot;, :called =&gt; true)
     c = mock(&quot;data&quot;, :called =&gt; true)
-
+  
     callback[a, b, c]
   end
 
@@ -210,6 +194,11 @@ class ConfigurationActionsInvocationTest &lt; Test::Unit::TestCase
       a = mock(&quot;channel&quot;, :called =&gt; true)
       b = mock(&quot;stream&quot;, :called =&gt; true)
       c = mock(&quot;data&quot;, :called =&gt; true)
-      Capistrano::Command.expects(:process).with(command, sessions, options).yields(a, b, c)
+
+      compare_args = Proc.new do |tree, sess, opts|
+        tree.fallback.command == command &amp;&amp; sess == sessions &amp;&amp; opts == options
+      end
+
+      Capistrano::Command.expects(:process).with(&amp;compare_args)
     end
 end</diff>
      <filename>test/configuration/actions/invocation_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,14 @@ class ConfigurationTest &lt; Test::Unit::TestCase
 
   def test_connections_execution_loading_namespaces_roles_and_variables_modules_should_integrate_correctly
     Capistrano::SSH.expects(:connect).with { |s,c| s.host == &quot;www.capistrano.test&quot; &amp;&amp; c == @config }.returns(:session)
-    Capistrano::Command.expects(:process).with(&quot;echo 'hello world'&quot;, [:session], :logger =&gt; @config.logger)
+
+    process_args = Proc.new do |tree, session, opts|
+      tree.fallback.command == &quot;echo 'hello world'&quot; &amp;&amp;
+      session == [:session] &amp;&amp;
+      opts == { :logger =&gt; @config.logger }
+    end
+
+    Capistrano::Command.expects(:process).with(&amp;process_args)
 
     @config.load do
       role :test, &quot;www.capistrano.test&quot;</diff>
      <filename>test/configuration_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>c70e595c36ae7cd3f63c1212ddf566efe50e2b5d</id>
    </parent>
  </parents>
  <author>
    <name>Jamis Buck</name>
    <email>jamis@37signals.com</email>
  </author>
  <url>http://github.com/jamis/capistrano/commit/5d76e1d2c0dfefd3780e2278789d201d6b4a9abf</url>
  <id>5d76e1d2c0dfefd3780e2278789d201d6b4a9abf</id>
  <committed-date>2008-08-21T13:29:28-07:00</committed-date>
  <authored-date>2008-08-21T13:29:28-07:00</authored-date>
  <message>make tests pass</message>
  <tree>1c5e026fd5935b454ca5fda3a337d8d0fd8b665f</tree>
  <committer>
    <name>Jamis Buck</name>
    <email>jamis@37signals.com</email>
  </committer>
</commit>
