heroku-commander 0.1.0 → 0.2.0

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.
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.