heroku-commander 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ 0.2.0 (02/14/2013)
2
+ ==================
3
+
4
+ * When the process exit status cannot be determined the error message says "The command failed without returning an exit status." - [@dblock](https://github.com/dblock).
5
+ * Added the process ID into the `Heroku::Commander::Errors::CommandError` problem description - [@dblock](https://github.com/dblock).
6
+ * Fix: Heroku `run` or `run:detached` output does not always combine "attached to terminal" and "up" status, which causes the runner to incorrectly parse the Heroku PID - [@dblock](https://github.com/dblock).
7
+ * Added `Heroku::Commander.processes` that returns or yields an array of `Heroku::Process` by running `heroku ps` - [@dblock](https://github.com/dblock).
8
+ * Added a `tail_timeout` option to `Heroku::Commander.run` with `detached: true` that suspends tail process termination for a chance to receive additional `heroku logs --tail` output - [@dblock](https://github.com/dblock).
9
+
1
10
  0.1.0 (01/31/2013)
2
11
  ==================
3
12
 
data/README.md CHANGED
@@ -25,6 +25,18 @@ commander = Heroku::Commander.new({ :app => "heroku-commander" })
25
25
  commander.config # => a hash of all settings for the heroku-commander app
26
26
  ```
27
27
 
28
+ Heroku Processes
29
+ ----------------
30
+
31
+ Returns or yields an array of processes by running `heroku ps`.
32
+
33
+ ``` ruby
34
+ commander = Heroku::Commander.new({ :app => "heroku-commander" })
35
+ commander.processes do |process|
36
+ # try process.pid and process.status
37
+ end
38
+ ```
39
+
28
40
  Heroku Run
29
41
  ----------
30
42
 
@@ -53,6 +65,10 @@ commander.run("ls -R", { :detached => true }) do |line|
53
65
  end
54
66
  ```
55
67
 
68
+ You can pass the following options along with `:detached`:
69
+
70
+ * **tail_timeout**: number of seconds to wait before terminating `heroku logs --tail`, expecting more output (default to 5).
71
+
56
72
  For more information about Heroku one-off dynos see [this documentation](https://devcenter.heroku.com/articles/one-off-dynos).
57
73
 
58
74
  More Examples
@@ -0,0 +1,16 @@
1
+ require 'bundler'
2
+ Bundler.setup(:default, :development)
3
+
4
+ require 'heroku-commander'
5
+
6
+ logger = Logger.new($stdout)
7
+ logger.level = Logger::INFO
8
+
9
+ uname = Heroku::Executor.run "uname -a", { :logger => logger }
10
+ logger.info "Local system is #{uname.join('\n')}."
11
+
12
+ files = []
13
+ Heroku::Executor.run "ls -1", { :logger => logger } do |line|
14
+ files << line
15
+ end
16
+ logger.info "Local file system has #{files.count} file(s): #{files.join(', ')}"
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler.setup(:default, :development)
3
+
4
+ require 'heroku-commander'
5
+
6
+ logger = Logger.new($stdout)
7
+ logger.level = Logger::DEBUG
8
+ commander = Heroku::Commander.new({ :logger => logger })
9
+
10
+ commander.processes.each do |process|
11
+ logger.info "Process: #{process.pid} (#{process.status})"
12
+ end
@@ -8,7 +8,7 @@ en:
8
8
  summary: "The heroku client stopped sending output before exiting."
9
9
  resolution: "This is unexpected and represents a Heroku server error. Try again."
10
10
  command_error:
11
- message: "The command `%{cmd}` failed with exit status %{status}."
11
+ message: "The command `%{cmd}`%{pid} failed%{status_message}."
12
12
  summary: "%{message}%{lines}"
13
13
  resolution: "Examine the command line and refer to the command documentation."
14
14
  unexpected_output:
@@ -20,7 +20,16 @@ en:
20
20
  summary: "The instance of Heroku::Runner is missing a command argument."
21
21
  resolution: "Specify a command, for example:\n
22
22
  \_\_Heroku::Runner.new({ :command => 'ls -1' })"
23
+ missing_pid_error:
24
+ message: "Missing pid."
25
+ summary: "The instance of Heroku::Status is missing a pid argument."
26
+ resolution: "Specify a pid, for example:\n
27
+ \_\_Heroku::Status.new({ :pid => 'run.1234' })"
23
28
  already_running_error:
24
29
  message: "The process is already running with pid %{pid}."
25
30
  summary: "You can only run! or detach! an instance of Heroku::Runner once."
26
31
  resolution: "This is a bug in your code."
32
+ no_such_process:
33
+ message: "The process `%{pid}` does not exist."
34
+ summary: "The output of heroku ps did not list the requested process."
35
+ resolution: "Verify that the process is running."
@@ -11,5 +11,5 @@ require 'heroku/commander/errors'
11
11
  require 'heroku/config'
12
12
  require 'heroku/commander'
13
13
  require 'heroku/executor'
14
+ require 'heroku/process'
14
15
  require 'heroku/runner'
15
-
@@ -15,6 +15,23 @@ module Heroku
15
15
  end
16
16
  end
17
17
 
18
+ def processes(&block)
19
+ processes = []
20
+ cmdline = [ "heroku", "ps", @app ? "--app #{@app}" : nil ].compact.join(" ")
21
+ Heroku::Executor.run cmdline, { :logger => logger } do |line|
22
+ next if ! line || line[0] == '=' || line.length == 0
23
+ if (match = line.match /(\w+\.\d+): (\w+)/)
24
+ process = Heroku::Process.new({ :logger => logger, :pid => match[1], :status => match[2], :app => app })
25
+ processes << process
26
+ logger.info "Process: #{process.pid} (#{process.status}) from '#{line}'" if logger
27
+ yield process if block_given?
28
+ else
29
+ logger.warn "Unexpected line from heroku ps: #{line}" if logger
30
+ end
31
+ end
32
+ processes
33
+ end
34
+
18
35
  # Run a process synchronously
19
36
  def run(command, options = {}, &block)
20
37
  runner = Heroku::Runner.new({ :app => app, :logger => logger, :command => command })
@@ -2,5 +2,7 @@ require 'heroku/commander/errors/base'
2
2
  require 'heroku/commander/errors/client_eio_error'
3
3
  require 'heroku/commander/errors/command_error'
4
4
  require 'heroku/commander/errors/missing_command_error'
5
+ require 'heroku/commander/errors/missing_pid_error'
5
6
  require 'heroku/commander/errors/unexpected_output_error'
6
7
  require 'heroku/commander/errors/already_running_error'
8
+ require 'heroku/commander/errors/no_such_process_error'
@@ -7,27 +7,40 @@ module Heroku
7
7
 
8
8
  def initialize(opts = {})
9
9
  @inner_exception = opts[:inner_exception]
10
- super(compose_message("command_error", prepare_lines(opts)))
10
+ opts = opts.dup
11
+ prepare_lines(opts)
12
+ prepare_status_message(opts)
13
+ prepare_pid(opts)
14
+ super(compose_message("command_error", opts))
11
15
  end
12
16
 
13
17
  private
14
18
 
19
+ def prepare_status_message(opts)
20
+ if opts[:status] && opts[:status] != ""
21
+ opts[:status_message] = " with exit status #{opts[:status]}"
22
+ else
23
+ opts[:status_message] = " without reporting an exit status"
24
+ end
25
+ end
26
+
15
27
  def prepare_lines(opts)
16
28
  if opts[:lines] && opts[:lines].size > 4
17
29
  lines = opts[:lines][0..2]
18
30
  lines.push "... skipping #{opts[:lines].size - 4} line(s) ..."
19
31
  lines.concat opts[:lines][-2..-1]
20
- result = opts.dup
21
- result[:lines] = "\n\t" + lines.join("\n\t")
22
- result
32
+ opts[:lines] = "\n\t" + lines.join("\n\t")
23
33
  elsif opts[:lines]
24
- result = opts.dup
25
- result[:lines] = "\n\t" + result[:lines].join("\n\t")
26
- result
27
- else
28
- opts
34
+ opts[:lines] = "\n\t" + opts[:lines].join("\n\t")
29
35
  end
30
36
  end
37
+
38
+ def prepare_pid(opts)
39
+ if opts[:pid]
40
+ opts[:pid] = " (pid: #{opts[:pid]})"
41
+ end
42
+ end
43
+
31
44
  end
32
45
  end
33
46
  end
@@ -0,0 +1,13 @@
1
+ module Heroku
2
+ class Commander
3
+ module Errors
4
+ class MissingPidError < Heroku::Commander::Errors::Base
5
+
6
+ def initialize
7
+ super(compose_message("missing_pid_error"))
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Heroku
2
+ class Commander
3
+ module Errors
4
+ class NoSuchProcessError < Heroku::Commander::Errors::Base
5
+
6
+ def initialize(opts = {})
7
+ super(compose_message("no_such_process", opts))
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  module Heroku
2
2
  class Commander
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -2,6 +2,12 @@ module Heroku
2
2
  class Executor
3
3
 
4
4
  class Terminate < StandardError
5
+
6
+ attr_accessor :timeout
7
+
8
+ def initialize(timeout = 0)
9
+ @timeout = timeout
10
+ end
5
11
  end
6
12
 
7
13
  class << self
@@ -9,47 +15,41 @@ module Heroku
9
15
  # Executes a command and yields output line-by-line.
10
16
  def run(cmd, options = {}, &block)
11
17
  lines = []
18
+ running_pid = nil
12
19
  logger = options[:logger]
13
20
  logger.debug "Running: #{cmd}" if logger
14
21
  PTY.spawn(cmd) do |r, w, pid|
22
+ running_pid = pid
15
23
  logger.debug "Started: #{pid}" if logger
16
24
  terminated = false
17
25
  begin
18
26
  r.sync = true
19
- r.each do |line|
20
- line.strip! if line
21
- logger.debug "#{pid}: #{line}" if logger
22
- if block_given?
23
- yield line
24
- end
25
- lines << line
26
- end
27
- rescue Heroku::Executor::Terminate
28
- logger.debug "Terminating #{pid}." if logger
29
- Process.kill("TERM", pid)
27
+ read_from(r, pid, options, lines, &block)
28
+ rescue Heroku::Executor::Terminate => e
29
+ logger.debug "Waiting: #{e.timeout} second(s) to terminate #{pid}" if logger
30
+ terminate_process! pid, e.timeout
30
31
  terminated = true
31
32
  rescue Errno::EIO, IOError => e
32
- logger.debug "#{e.class}: #{e.message}" if logger
33
+ # ignore
33
34
  rescue PTY::ChildExited => e
34
35
  logger.debug "Terminated: #{pid}" if logger
35
- terminted = true
36
+ terminated = true
36
37
  raise e
37
38
  ensure
38
39
  unless terminated
40
+ # wait for process
39
41
  logger.debug "Waiting: #{pid}" if logger
40
- Process.wait(pid) rescue Errno::ECHILD
42
+ ::Process.wait(pid)
41
43
  end
42
44
  end
43
45
  end
44
- check_exit_status! cmd, $?.exitstatus, lines
46
+ check_exit_status! cmd, running_pid, $?.exitstatus, lines
45
47
  lines
46
48
  rescue Errno::ECHILD => e
47
- logger.debug "#{e.class}: #{e.message}" if logger
48
- check_exit_status! cmd, $?.exitstatus, lines
49
+ check_exit_status! cmd, running_pid, $?.exitstatus, lines
49
50
  lines
50
51
  rescue PTY::ChildExited => e
51
- logger.debug "#{e.class}: #{e.message}" if logger
52
- check_exit_status! cmd, $!.status.exitstatus, lines
52
+ check_exit_status! cmd, running_pid, $!.status.exitstatus, lines
53
53
  lines
54
54
  rescue Heroku::Commander::Errors::Base => e
55
55
  logger.debug "Error: #{e.problem}" if logger
@@ -58,6 +58,7 @@ module Heroku
58
58
  logger.debug "#{e.class}: #{e.respond_to?(:problem) ? e.problem : e.message}" if logger
59
59
  raise Heroku::Commander::Errors::CommandError.new({
60
60
  :cmd => cmd,
61
+ :pid => running_pid,
61
62
  :status => $?.exitstatus,
62
63
  :message => e.message,
63
64
  :inner_exception => e,
@@ -67,16 +68,45 @@ module Heroku
67
68
 
68
69
  private
69
70
 
70
- def check_exit_status!(cmd, status, lines = nil)
71
+ def read_from(r, pid, options, lines, &block)
72
+ logger = options[:logger]
73
+ while ! r.eof do
74
+ line = r.readline
75
+ line.strip! if line
76
+ logger.debug "#{pid}: #{line}" if logger
77
+ if block_given?
78
+ yield line
79
+ end
80
+ lines << line
81
+ end
82
+ end
83
+
84
+ def check_exit_status!(cmd, pid, status, lines = nil)
71
85
  return if ! status || status == 0
72
86
  raise Heroku::Commander::Errors::CommandError.new({
73
87
  :cmd => cmd,
88
+ :pid => pid,
74
89
  :status => status,
75
90
  :message => "The command #{cmd} failed with exit status #{status}.",
76
91
  :lines => lines
77
92
  })
78
93
  end
79
94
 
95
+ def terminate_process!(pid, timeout)
96
+ if timeout
97
+ # Delay terminating of the process, usually to let the output flush.
98
+ Thread.new(pid, timeout) do |pid, timeout|
99
+ begin
100
+ sleep(timeout) if timeout
101
+ ::Process.kill("TERM", pid)
102
+ rescue
103
+ end
104
+ end
105
+ else
106
+ ::Process.kill("TERM", pid)
107
+ end
108
+ end
109
+
80
110
  end
81
111
  end
82
112
  end
@@ -0,0 +1,25 @@
1
+ module Heroku
2
+ class Process
3
+
4
+ attr_accessor :app, :logger, :pid, :status
5
+
6
+ def initialize(options = {})
7
+ @app = options[:app]
8
+ @logger = options[:logger]
9
+ @pid = options[:pid]
10
+ @status = options[:status]
11
+ raise Heroku::Commander::Errors::MissingPidError unless @pid
12
+ end
13
+
14
+ def refresh_status!
15
+ refreshed = false
16
+ Heroku::Commander.new({ :logger => logger, :app => app }).processes.each do |process|
17
+ next unless ! refreshed && process.pid == pid
18
+ @status = process.status
19
+ refreshed = true
20
+ end
21
+ refreshed
22
+ end
23
+
24
+ end
25
+ end
data/lib/heroku/runner.rb CHANGED
@@ -17,38 +17,40 @@ module Heroku
17
17
 
18
18
  def run!(options = {}, &block)
19
19
  if options && options[:detached]
20
- run_detached! &block
20
+ run_detached! options, &block
21
21
  else
22
- run_attached! &block
22
+ run_attached! options, &block
23
23
  end
24
24
  end
25
25
 
26
26
  protected
27
27
 
28
- def run_attached!(&block)
28
+ def run_attached!(options = {}, &block)
29
29
  @pid = nil
30
30
  previous_line = nil # delay by 1 to avoid rc=status line
31
+ lines_until_pid = 0
31
32
  lines = Heroku::Executor.run cmdline, { :logger => logger } do |line|
32
33
  if ! @pid
33
34
  check_pid(line)
35
+ lines_until_pid += 1
34
36
  elsif block_given?
35
37
  yield previous_line if previous_line
36
38
  previous_line = line
37
39
  end
38
40
  end
39
- lines.shift # remove Running `...` attached to terminal... up, run.xyz
41
+ lines.shift(lines_until_pid) # remove Running `...` attached to terminal... up, run.xyz
40
42
  check_exit_status! lines
41
43
  lines
42
44
  end
43
45
 
44
- def run_detached!(&block)
46
+ def run_detached!(options = {}, &block)
45
47
  raise Heroku::Commander::Errors::AlreadyRunningError.new({ :pid => @pid }) if running?
46
48
  @running = true
47
49
  @pid = nil
48
50
  @tail = nil
49
51
  lines = Heroku::Executor.run cmdline({ :detached => true }), { :logger => logger } do |line|
50
52
  check_pid(line) unless @pid
51
- @tail ||= tail!(&block) if @pid
53
+ @tail ||= tail!(options, &block) if @pid
52
54
  end
53
55
  check_exit_status! @tail || lines
54
56
  @running = false
@@ -64,37 +66,36 @@ module Heroku
64
66
  lines.pop if status
65
67
  raise Heroku::Commander::Errors::CommandError.new({
66
68
  :cmd => @command,
69
+ :pid => @pid,
67
70
  :status => status,
68
- :message => "The command #{@command} failed with exit status #{status}.",
71
+ :message => "The command #{@command} failed.",
69
72
  :lines => lines
70
73
  }) unless status && status == "0"
71
74
  end
72
75
 
73
76
  def check_pid(line)
74
- if (match = line.match /attached to terminal... up, (run.\d+)$/)
77
+ if (match = line.match /up, (run.\d+)$/)
75
78
  @pid = match[1]
76
79
  logger.debug "Heroku pid #{@pid} up." if logger
77
- elsif (match = line.match /detached... up, (run.\d+)$/)
78
- @pid = match[1]
79
- logger.debug "Heroku detached pid #{@pid} up." if logger
80
- else
81
- @pid = ''
82
80
  end
83
81
  end
84
82
 
85
- def tail!(&block)
83
+ def tail!(options = {}, &block)
86
84
  lines = []
87
85
  tail_cmdline = [ "heroku", "logs", "-p #{@pid}", "--tail", @app ? "--app #{@app}" : nil ].compact.join(" ")
88
- previous_line = nil # delay by 1 to avoid rc=status line
86
+ previous_line = nil # delay by 1 to avoid rc=status lines
89
87
  Heroku::Executor.run tail_cmdline, { :logger => logger } do |line|
88
+ line ||= ""
90
89
  # remove any ANSI output
91
90
  line = line.gsub /\e\[(\d+)m/, ''
92
91
  # lines are returned as [date/time] app/heroku[pid]: output
93
- line = line.split("[#{@pid}]:")[-1].strip
92
+ if (line_after_prefix = line.split("[#{@pid}]:")[-1])
93
+ line = line_after_prefix.strip
94
+ end
94
95
  if line.match(/Starting process with command/) || line.match(/State changed from \w+ to up/)
95
96
  # ignore
96
97
  elsif line.match(/State changed from \w+ to complete/) || line.match(/Process exited with status \d+/)
97
- terminate_executor!
98
+ terminate_executor!(options[:tail_timeout] || 5)
98
99
  else
99
100
  if block_given?
100
101
  yield previous_line if previous_line
@@ -106,8 +107,8 @@ module Heroku
106
107
  lines
107
108
  end
108
109
 
109
- def terminate_executor!
110
- raise Heroku::Executor::Terminate
110
+ def terminate_executor!(timeout = 10)
111
+ raise Heroku::Executor::Terminate.new(timeout)
111
112
  end
112
113
 
113
114
  end
@@ -73,8 +73,48 @@ describe Heroku::Commander do
73
73
  "2013-01-31T01:39:33+00:00 heroku[run.8748]: Process exited with status 0",
74
74
  "2013-01-31T01:39:33+00:00 heroku[run.8748]: State changed from up to complete"
75
75
  ])
76
- Heroku::Runner.any_instance.should_receive(:terminate_executor!).twice
77
- subject.run("ls -1", { :detached => true }).should == [ "bin", "app" ]
76
+ Heroku::Runner.any_instance.should_receive(:terminate_executor!).with(42).twice
77
+ subject.run("ls -1", { :detached => true, :tail_timeout => 42 }).should == [ "bin", "app" ]
78
+ end
79
+ end
80
+ context "processes" do
81
+ context "without processes" do
82
+ before :each do
83
+ Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil })
84
+ end
85
+ its(:processes) { should be_empty }
86
+ end
87
+ context "a web process" do
88
+ before :each do
89
+ Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil }).
90
+ and_yield("=== web: `bundle exec ruby config.ru`").
91
+ and_yield("web.1: idle 2013/02/04 13:23:40 (~ 4h ago)")
92
+ end
93
+ its(:processes) { should_not be_empty }
94
+ it "has correct pid and status" do
95
+ processes = subject.processes
96
+ processes.count.should == 1
97
+ process = processes.first
98
+ process.pid.should eq "web.1"
99
+ process.status.should eq "idle"
100
+ end
101
+ end
102
+ context "a combination of one-off and web processes" do
103
+ before :each do
104
+ Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil }).
105
+ and_yield("=== run: one-off processes").
106
+ and_yield("run.9174: up 2013/02/04 17:37:37 (~ 9m ago): `bundle exec rails console`").
107
+ and_yield(nil).
108
+ and_yield("").
109
+ and_yield("=== web: `bundle exec ruby config.ru`").
110
+ and_yield("web.1: up 2013/02/04 11:14:53 (~ 6h ago)").
111
+ and_yield("web.2: up 2013/02/04 11:14:17 (~ 6h ago)")
112
+ end
113
+ it "has correct pid and status" do
114
+ processes = subject.processes
115
+ processes.count.should == 3
116
+ processes.map(&:pid).should == [ "run.9174", "web.1", "web.2" ]
117
+ end
78
118
  end
79
119
  end
80
120
  end
@@ -7,7 +7,15 @@ describe Heroku::Executor do
7
7
  end
8
8
  context "command does not exist" do
9
9
  subject { lambda { Heroku::Executor.run "executor_spec.rb" } }
10
- it { should raise_error Heroku::Commander::Errors::CommandError, /The command `executor_spec.rb` failed with exit status \d+./ }
10
+ it { should raise_error Heroku::Commander::Errors::CommandError, /The command `executor_spec.rb`(.*)failed with exit status \d+./ }
11
+ end
12
+ context "command does not return exit status" do
13
+ before do
14
+ PTY.stub(:spawn).and_raise("foobar!")
15
+ Process::Status.any_instance.stub(:exitstatus).and_return(nil)
16
+ end
17
+ subject { lambda { Heroku::Executor.run "executor_spec.rb" } }
18
+ it { should raise_error Heroku::Commander::Errors::CommandError, /The command `executor_spec.rb` failed without reporting an exit status./ }
11
19
  end
12
20
  context "command exists" do
13
21
  subject { lambda { Heroku::Executor.run "ls -1" } }
@@ -25,7 +33,8 @@ describe Heroku::Executor do
25
33
  it "doesn't yield nil lines" do
26
34
  r = double(IO)
27
35
  r.stub(:sync=)
28
- r.stub(:each).and_yield("line1").and_yield(nil).and_yield("rc=0")
36
+ r.stub(:eof).and_return(false, false, false, true)
37
+ r.stub(:readline).and_return("line1", nil, "rc=0")
29
38
  Process.stub(:wait)
30
39
  PTY.stub(:spawn).and_yield(r, nil, 42)
31
40
  Heroku::Executor.run("foobar").should == [ "line1", nil, "rc=0" ]
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heroku::Process do
4
+ context "without pid" do
5
+ subject { lambda { Heroku::Process.new } }
6
+ it { should raise_error Heroku::Commander::Errors::MissingPidError, /Missing pid./ }
7
+ end
8
+ context "with pid" do
9
+ subject do
10
+ Heroku::Process.new({ :pid => "run.1234", :status => "idle" })
11
+ end
12
+ its(:pid) { should eq "run.1234" }
13
+ its(:status) { should eq "idle" }
14
+ context "refresh_status!" do
15
+ before do
16
+ Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil }).
17
+ and_yield("run.1234: up for ~ 1m")
18
+ subject.refresh_status!
19
+ end
20
+ its(:status) { should eq "up" }
21
+ end
22
+ end
23
+ end
24
+
@@ -17,14 +17,29 @@ describe Heroku::Runner do
17
17
  its(:command) { should eq "ls -1" }
18
18
  its(:cmdline) { should eq "heroku run \"(ls -1 2>&1 ; echo rc=\\$?)\"" }
19
19
  its(:running?) { should be_false }
20
+ context "check_pid" do
21
+ it "parses up, run.1234" do
22
+ subject.send(:check_pid, "up, run.1234")
23
+ subject.pid.should == "run.1234"
24
+ end
25
+ it "parses attached to terminal ... up, run.1234" do
26
+ subject.send(:check_pid, "parses attached to terminal ... up, run.1234")
27
+ subject.pid.should == "run.1234"
28
+ end
29
+ it "parses detached ... up, run.1234" do
30
+ subject.send(:check_pid, "detached ... up, run.1234")
31
+ subject.pid.should == "run.1234"
32
+ end
33
+ end
20
34
  context "run!" do
21
35
  before :each do
22
36
  Heroku::Executor.stub(:run).with(subject.send(:cmdline), { :logger => nil }).
23
- and_yield("Running `...` attached to terminal... up, run.9783").
37
+ and_yield("Running `...`").
38
+ and_yield("attached to terminal... up, run.9783").
24
39
  and_yield("app").
25
40
  and_yield("bin").
26
41
  and_yield("rc= 0").
27
- and_return([ "Running `...` attached to terminal... up, run.9783", "app", "bin", "rc=0" ])
42
+ and_return([ "Running `...`", "attached to terminal... up, run.9783", "app", "bin", "rc=0" ])
28
43
  end
29
44
  it "runs the command w/o a block" do
30
45
  subject.run!.should == [ "app", "bin" ]
@@ -59,6 +74,7 @@ describe Heroku::Runner do
59
74
  and_yield("2013-01-31T01:39:30+00:00 heroku[run.8748]: Starting process with command `ls -1`").
60
75
  and_yield("2013-01-31T01:39:31+00:00 app[run.8748]: bin").
61
76
  and_yield("2013-01-31T01:39:31+00:00 app[run.8748]: app").
77
+ and_yield(nil).
62
78
  and_yield("2013-01-31T00:56:13+00:00 app[run.8748]: rc=0").
63
79
  and_yield("2013-01-31T01:39:33+00:00 heroku[run.8748]: Process exited with status 0").
64
80
  and_yield("2013-01-31T01:39:33+00:00 heroku[run.8748]: State changed from up to complete").
@@ -73,7 +89,7 @@ describe Heroku::Runner do
73
89
  Heroku::Runner.any_instance.should_receive(:terminate_executor!).twice
74
90
  end
75
91
  it "runs the command w/o a block" do
76
- subject.run!({ :detached => true }).should == [ "bin", "app" ]
92
+ subject.run!({ :detached => true }).should == [ "bin", "app", "" ]
77
93
  subject.pid.should == "run.8748"
78
94
  subject.should_not be_running
79
95
  end
@@ -82,7 +98,7 @@ describe Heroku::Runner do
82
98
  subject.run!({ :detached => true }).each do |line|
83
99
  lines << line
84
100
  end
85
- lines.should == [ "bin", "app" ]
101
+ lines.should == [ "bin", "app", "" ]
86
102
  subject.pid.should == "run.8748"
87
103
  subject.should_not be_running
88
104
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku-commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-02 00:00:00.000000000 Z
13
+ date: 2013-02-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n
@@ -43,7 +43,9 @@ files:
43
43
  - README.md
44
44
  - Rakefile
45
45
  - assets/heroku-commander.png
46
+ - examples/executor-run.rb
46
47
  - examples/heroku-config.rb
48
+ - examples/heroku-processes.rb
47
49
  - examples/heroku-run-detached.rb
48
50
  - examples/heroku-run.rb
49
51
  - heroku-commander.gemspec
@@ -56,16 +58,20 @@ files:
56
58
  - lib/heroku/commander/errors/client_eio_error.rb
57
59
  - lib/heroku/commander/errors/command_error.rb
58
60
  - lib/heroku/commander/errors/missing_command_error.rb
61
+ - lib/heroku/commander/errors/missing_pid_error.rb
62
+ - lib/heroku/commander/errors/no_such_process_error.rb
59
63
  - lib/heroku/commander/errors/unexpected_output_error.rb
60
64
  - lib/heroku/commander/version.rb
61
65
  - lib/heroku/config.rb
62
66
  - lib/heroku/executor.rb
67
+ - lib/heroku/process.rb
63
68
  - lib/heroku/pty.rb
64
69
  - lib/heroku/runner.rb
65
70
  - lib/heroku_commander.rb
66
71
  - spec/heroku/commander_spec.rb
67
72
  - spec/heroku/config_spec.rb
68
73
  - spec/heroku/executor_spec.rb
74
+ - spec/heroku/process_spec.rb
69
75
  - spec/heroku/runner_spec.rb
70
76
  - spec/heroku/version_spec.rb
71
77
  - spec/spec_helper.rb
@@ -84,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
90
  version: '0'
85
91
  segments:
86
92
  - 0
87
- hash: 155166833
93
+ hash: -3503047870647109606
88
94
  required_rubygems_version: !ruby/object:Gem::Requirement
89
95
  none: false
90
96
  requirements:
@@ -93,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
99
  version: 1.3.6
94
100
  requirements: []
95
101
  rubyforge_project:
96
- rubygems_version: 1.8.24
102
+ rubygems_version: 1.8.25
97
103
  signing_key:
98
104
  specification_version: 3
99
105
  summary: Control Heroku from Ruby via its `heroku` shell command.