derelict 0.3.4.travis.108 → 0.3.4.travis.115

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzY0YmUwMjBkNThkOTFiMGRkOTgxNWY0MzU1MGUzMDUzYzM2YmNkNw==
4
+ NjFhN2Q1ZGVhMThhODA1NjZlMGFjNzI3MWY0Mzg5Njg0M2EwM2QzZQ==
5
5
  data.tar.gz: !binary |-
6
- ZTRlYzI3NTVjN2ZmNDk1OGNkNDQ5ZGI3YmEwNjMxMzFmNmJjYjI5Mg==
6
+ Y2JkYjNiNjRmNGFjMzNmNTM1ODFmMTQ5ODczNWU0YzYwMjI4NmQzOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzRmOGY5ZGIxOTEwNzc4Y2FlNTE4MTEyNDRkNzQyY2MwNmU3N2Q5MWY5ZGFk
10
- MTg1ZDJkNGU4NzE3YzA1ZGZhYmMwNzNhZWYyZWJjNjU1MjBkMjgzZGEzMmFh
11
- ZWU4Zjc4NzgyN2M5MTUzNmRkMTk4Y2JkYjc3OTM4MWJlMzA2YWQ=
9
+ ZWNiOGVlYmQ4YjBkN2ZkOTFhMmJiNTgxNmIyYTc3ZWI3NjlkYjIxZTNhZGMx
10
+ NmUxZDQ5YWNiYmZhNjVlM2NjMTUxNjgwMDFlMjQzNzk5ODFkMzViNWNkNDA4
11
+ ODE3OTljYTkyZDIyMDVjODM1NzhjMDU3OWRiZGM0ZWQ4YWE1YjY=
12
12
  data.tar.gz: !binary |-
13
- ZDQ4MTVmYTZkYjNkZGNhZTIyMjFhODQzMzc1MzIxZGVkOWQxYWNhODRhNmYx
14
- NmIxMTIyODg5MmU3YTZlMjhiZmQzNTVkZTBhNjMxNzEwOTQ3ZTYwZjgxYWRj
15
- Y2FkZTJiZTQ5NWI0MDU2YWIyNzI3YTJhZDk4NTU4NjMzNTQwMjc=
13
+ ZmZmNWI1MzRlNTgzZDZiNmUxZmMwNGFmZDQ2N2ZiY2UwYmUzMGYxZGUzNTkw
14
+ MmQ1NzdjZDVmM2U3NGU2MWQxYjg2NzM2MWE5NDZhZjhjZjZhNTBkNWI5N2Vk
15
+ NjM2ZWU1ODUzMjYyZmM1NGZkYmMyOGZlYjUzZGRjNWM0OGQ5MjM=
data/README.md CHANGED
@@ -64,7 +64,7 @@ instance = Derelict.instance("/path/to/vagrant")
64
64
  instance = Derelict.instance # Defaults to /Applications/Vagrant
65
65
 
66
66
  # Issue commands to the instance directly (not usually necessary)
67
- result = instance.execute('--version') # Shell::Executer object
67
+ result = instance.execute('--version') # Derelict::Executer object
68
68
  print "success" if result.success? # if Vagrant's exit status was 0
69
69
  print result.stdout # "Vagrant 1.3.3\n"
70
70
 
@@ -73,7 +73,7 @@ connection = instance.connect("/path/to/project")
73
73
 
74
74
  # Issue commands to the connection directly (runs from the project dir)
75
75
  result = connection.execute(:up) # runs "vagrant up" in project dir
76
- result.success? # it's a Shell::Executer object again
76
+ result.success? # Derelict::Executer object again
77
77
 
78
78
  # Retrieve a particular VM from a connection (multi-machine support)
79
79
  vm = connection.vm(:web) # "vm" is a Derelict::VirtualMachine
data/derelict.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.add_runtime_dependency "log4r"
36
36
  spec.add_runtime_dependency "memoist"
37
- spec.add_runtime_dependency "shell-executer"
37
+ spec.add_runtime_dependency "open4"
38
38
 
39
39
 
40
40
  version_major = RbConfig::CONFIG["MAJOR"].to_i
@@ -48,7 +48,7 @@ module Derelict
48
48
  #
49
49
  # * subcommand: Vagrant subcommand to run (:up, :status, etc.)
50
50
  # * arguments: Arguments to pass to the subcommand (optional)
51
- # * block: Passed through to Shell.execute (shell-executer)
51
+ # * block: Passed through to Derelict::Executer.execute
52
52
  #
53
53
  # Raises +Derelict::Instance::CommandFailed+ if the command fails.
54
54
  def execute!(subcommand, *arguments, &block)
@@ -0,0 +1,173 @@
1
+ module Derelict
2
+ # Executes an external (shell) command "safely"
3
+ #
4
+ # The safety involved is mainly ensuring that the command is
5
+ # gracefully terminated if this process is about to terminate.
6
+ class Executer
7
+ attr_reader :stdout, :stderr, :success
8
+
9
+ # Executes <tt>command</tt> and returns after execution
10
+ #
11
+ # * command: A string containing the command to run
12
+ # * options: A hash of options, with the following (symbol) keys:
13
+ # * :mode: Controls how the process' output is given to
14
+ # the block, one of :chars (pass each character
15
+ # one by one, retrieved with getc), or :lines
16
+ # (pass only whole lines, retrieved with gets).
17
+ # (optional, defaults to :lines)
18
+ # * :no_buffer: If true, the process' stdout and stderr won't
19
+ # be collected in the stdout and stderr
20
+ # properties, and will only be passed to the
21
+ # block (optional, defaults to false)
22
+ # * block: Gets passed stdout and stderr every time the process
23
+ # outputs to each stream (first parameter is stdout,
24
+ # second parameter is stderr; only one will contain
25
+ # data, the other will be nil)
26
+ def self.execute(command, options = {}, &block)
27
+ self.new(options).execute(command, &block)
28
+ end
29
+
30
+
31
+ # Initializes an Executer instance with particular options
32
+ #
33
+ # * options: A hash of options, with the following (symbol) keys:
34
+ # * :mode: Controls how the process' output is given to
35
+ # the block, one of :chars (pass each character
36
+ # one by one, retrieved with getc), or :lines
37
+ # (pass only whole lines, retrieved with gets).
38
+ # (optional, defaults to :lines)
39
+ # * :no_buffer: If true, the process' stdout and stderr won't
40
+ # be collected in the stdout and stderr
41
+ # properties, and will only be passed to the
42
+ # block (optional, defaults to false)
43
+ def initialize(options = {})
44
+ @options = {:mode => :lines, :no_buffer => false}.merge(options)
45
+
46
+ if @options[:mode] == :chars
47
+ @reader = proc {|s| s.getc }
48
+ else
49
+ @reader = proc {|s| s.gets }
50
+ end
51
+
52
+ @mutex = Mutex.new
53
+ reset
54
+ end
55
+
56
+ # Executes <tt>command</tt> and returns after execution
57
+ #
58
+ # * command: A string containing the command to run
59
+ # * block: Gets passed stdout and stderr every time the process
60
+ # outputs to each stream (first parameter is stdout,
61
+ # second parameter is stderr; only one will contain
62
+ # data, the other will be nil)
63
+ def execute(command, &block)
64
+ reset
65
+ pid, stdin, stdout, stderr = Open4::popen4(command)
66
+
67
+ save_exit_status(pid)
68
+ forward_signals_to(pid) { handle_streams stdout, stderr, &block }
69
+ self
70
+ end
71
+
72
+
73
+ private
74
+ # Clears the variables relating to a particular command execution
75
+ #
76
+ # This is done when first initialising, and just before a command
77
+ # is run, to get rid of the previous command's data.
78
+ def reset
79
+ @stdout = ''
80
+ @stderr = ''
81
+ @success = nil
82
+ end
83
+
84
+ # Waits for the exit status of a process (in a thread) saving it
85
+ #
86
+ # This will set the @status instance variable to true if the exit
87
+ # status was 0, or false if the exit status was anything else.
88
+ def save_exit_status(pid)
89
+ Thread.start do
90
+ @success = nil
91
+ @success = (Process.waitpid2(pid).last.exitstatus == 0)
92
+ end
93
+ end
94
+
95
+ # Forward signals to a process while running the given block
96
+ #
97
+ # * pid: The process ID to forward signals to
98
+ # * signals: The names of the signals to handle (optional,
99
+ # defaults to SIGINT only)
100
+ def forward_signals_to(pid, signals = %w[INT])
101
+ # Set up signal handlers
102
+ signals.each do |signal|
103
+ Signal.trap(signal) { Process.kill signal, pid }
104
+ end
105
+
106
+ # Run the block now that the signals are being forwarded
107
+ yield
108
+
109
+ # Reset signal handlers
110
+ signals.each do |signal|
111
+ Signal.trap signal, "DEFAULT"
112
+ end
113
+ end
114
+
115
+ # Manages reading from the stdout and stderr streams
116
+ #
117
+ # * stdout: The process' stdout stream
118
+ # * stderr: The process' stderr stream
119
+ # * block: The block to pass any read data to (optional)
120
+ def handle_streams(stdout, stderr, &block)
121
+ streams = [stdout, stderr]
122
+ until streams.empty?
123
+ # Find which streams are ready for reading, timeout 0.1s
124
+ selected, = select(streams, nil, nil, 0.1)
125
+
126
+ # Try again if none were ready
127
+ next if selected.nil? or selected.empty?
128
+
129
+ selected.each do |stream|
130
+ if stream.eof?
131
+ streams.delete(stream) unless @success.nil?
132
+ next
133
+ end
134
+
135
+ while data = @reader.call(stream)
136
+ data = ((@options[:mode] == :chars) ? data.chr : data)
137
+ stream_name = (stream == stdout) ? :stdout : :stderr
138
+ output data, stream_name, &block unless block.nil?
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Outputs data to the block
145
+ #
146
+ # * data: The data that needs to be passed to the block
147
+ # * stream_name: The stream data came from (:stdout or :stderr)
148
+ # * block: The block to pass the data to
149
+ def output(data, stream_name = :stdout, &block)
150
+ # Pass the output to the block
151
+ if block.arity == 2
152
+ args = [nil, nil]
153
+ if stream_name == :stdout
154
+ args[0] = data
155
+ else
156
+ args[1] = data
157
+ end
158
+ block.call(*args)
159
+ else
160
+ yield data if stream_name == :stdout
161
+ end
162
+
163
+ # Add to the buffers
164
+ unless @options[:no_buffer]
165
+ if stream_name == :stdout
166
+ @stdout += data
167
+ else
168
+ @stderr += data
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -2,11 +2,13 @@ module Derelict
2
2
  class Instance
3
3
  # Represents an invalid instance, which can't be used with Derelict
4
4
  class CommandFailed < Derelict::Exception
5
- # Initializes a new instance of this exception, with a reason
5
+ # Initializes a new instance of this exception, for a command
6
6
  #
7
- # * reason: The result (Shell::Executer) for the command that
8
- # failed (optional, provides extra detail in the
9
- # message)
7
+ # * command: The name of the command which failed (optional,
8
+ # provides extra detail in the message)
9
+ # * result: The result (Derelict::Executer) for the command
10
+ # which failed (optional, provides extra detail in
11
+ # the message)
10
12
  def initialize(command = nil, result = nil)
11
13
  super [default_message, describe(command, result)].join
12
14
  end
@@ -62,24 +62,24 @@ module Derelict
62
62
  # as a hash of options. A list of valid options is
63
63
  # below. Any options provided that aren't in the
64
64
  # list of valid options will get passed through to
65
- # Shell.execute (from the "shell-executer" gem).
65
+ # Derelict::Executer.execute.
66
66
  # Valid option keys:
67
67
  # * sudo: Whether to run the command as root, or not
68
68
  # (defaults to false)
69
- # * block: Passed through to Shell.execute (shell-executer)
69
+ # * block: Passed through to Derelict::Executer.execute
70
70
  def execute(subcommand, *arguments, &block)
71
71
  options = arguments.last.is_a?(Hash) ? arguments.pop : Hash.new
72
72
  command = command(subcommand, *arguments)
73
73
  command = "sudo -- #{command}" if options.delete(:sudo)
74
74
  logger.debug "Executing #{command} using #{description}"
75
- Shell.execute command, options, &block
75
+ Executer.execute command, options, &block
76
76
  end
77
77
 
78
78
  # Executes a Vagrant subcommand, raising an exception on failure
79
79
  #
80
80
  # * subcommand: Vagrant subcommand to run (:up, :status, etc.)
81
81
  # * arguments: Arguments to pass to the subcommand (optional)
82
- # * block: Passed through to Shell.execute (shell-executer)
82
+ # * block: Passed through to Derelict::Executer.execute
83
83
  #
84
84
  # Raises +Derelict::Instance::CommandFailed+ if the command fails.
85
85
  def execute!(subcommand, *arguments, &block)
data/lib/derelict.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require "derelict/version"
2
2
  require "log4r"
3
3
  require "memoist"
4
+ require "open4"
4
5
  require "set"
5
- require "shell/executer"
6
6
  require "shellwords"
7
7
 
8
8
  Log4r::Logger["root"] # creates the level constants (INFO, etc).
@@ -12,6 +12,7 @@ module Derelict
12
12
  autoload :Box, "derelict/box"
13
13
  autoload :Connection, "derelict/connection"
14
14
  autoload :Exception, "derelict/exception"
15
+ autoload :Executer, "derelict/executer"
15
16
  autoload :Instance, "derelict/instance"
16
17
  autoload :Parser, "derelict/parser"
17
18
  autoload :Plugin, "derelict/plugin"
@@ -0,0 +1,99 @@
1
+ require "spec_helper"
2
+
3
+ describe Derelict::Executer do
4
+ let(:executer) { described_class.new }
5
+ let(:command) { double("command") }
6
+ let(:options) { Hash.new }
7
+ let(:block) { Proc.new do end }
8
+ let(:result) { double("result") }
9
+
10
+ describe ".execute" do
11
+ before do
12
+ expect(described_class).to receive(:new).with(options).and_return(executer)
13
+ expect(executer).to receive(:execute).with(command, &block).and_return(result)
14
+ end
15
+
16
+ subject { described_class.execute command, options, &block }
17
+ it { should be result }
18
+ end
19
+
20
+ describe "#initialize" do
21
+ subject { described_class.new options }
22
+ it { should be_a described_class }
23
+ its(:stdout) { should eq "" }
24
+ its(:stderr) { should eq "" }
25
+ its(:success) { should be_nil }
26
+
27
+ context "with :chars mode specified" do
28
+ let(:options) { {:mode => :chars} }
29
+ it { should be_a described_class }
30
+ end
31
+ end
32
+
33
+ # The way #execute is tested is much slower than a unit test
34
+ # (obviously), but I ran into some difficulty implementing a proper
35
+ # unit test for this method.
36
+ #
37
+ # TODO: rewrite as a unit test
38
+ describe "#execute" do
39
+ subject { executer.execute command, &block }
40
+
41
+ context "without a block" do
42
+ let(:command) { "echo 'test 1'" }
43
+ its(:stdout) { should eq "test 1\n" }
44
+ its(:stderr) { should eq "" }
45
+ its(:success) { should be_true }
46
+
47
+ context "with non-existent command" do
48
+ let(:command) { "not_actually_a_command" }
49
+ it "should raise ENOENT" do
50
+ expect { subject }.to raise_error(Errno::ENOENT)
51
+ end
52
+ end
53
+
54
+ context "with unsuccessful command" do
55
+ let(:command) { "false" }
56
+ its(:success) { should be_false }
57
+ end
58
+ end
59
+
60
+ context "with a block" do
61
+ context "with one argument" do
62
+ let(:command) { "echo 'test 2'" }
63
+ let(:block) do
64
+ @buffer = ""
65
+ Proc.new do |stdout|
66
+ @buffer << stdout
67
+ end
68
+ end
69
+
70
+ its(:stdout) { should eq "test 2\n" }
71
+ its(:stderr) { should eq "" }
72
+ its(:success) { should be_true }
73
+ specify "the block should get called" do
74
+ subject; expect(@buffer).to eq executer.stdout
75
+ end
76
+ end
77
+
78
+ context "with two arguments" do
79
+ let(:command) { "echo 'test 3'; echo 'test 4' 1>&2" }
80
+ let(:block) do
81
+ @stdout = ""; @stderr = ""
82
+ Proc.new do |stdout, stderr|
83
+ @stdout << stdout unless stdout.nil?
84
+ @stderr << stderr unless stderr.nil?
85
+ end
86
+ end
87
+
88
+ its(:stdout) { should eq "test 3\n" }
89
+ its(:stderr) { should eq "test 4\n" }
90
+ its(:success) { should be_true }
91
+ specify "the block should get called" do
92
+ subject
93
+ expect(@stdout).to eq executer.stdout
94
+ expect(@stderr).to eq executer.stderr
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -165,10 +165,10 @@ describe Derelict::Instance do
165
165
  ]}
166
166
  end
167
167
 
168
- context "with mock Shell" do
168
+ context "with mock Executer" do
169
169
  let(:expected_command) { "/foo/bar/bin/vagrant test arg\\ 1" }
170
170
  before do
171
- expect(Shell).to receive(:execute).with(expected_command, options).and_return(result)
171
+ expect(Derelict::Executer).to receive(:execute).with(expected_command, options).and_return(result)
172
172
  end
173
173
 
174
174
  let(:options) { Hash.new }
@@ -207,7 +207,7 @@ describe Derelict::Instance do
207
207
 
208
208
  context "with :sudo option enabled" do
209
209
  let(:options_argument) { {:sudo => true} }
210
- let(:options) { Hash.new } # Don't pass sudo opt to Shell.execute
210
+ let(:options) { Hash.new } # Don't pass sudo opt to Executer.execute
211
211
  let(:expected_command) { "sudo -- /foo/bar/bin/vagrant test arg\\ 1" }
212
212
  subject { instance.execute :test, "arg 1", options_argument }
213
213
  its(:stdout) { should eq "stdout\n" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: derelict
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4.travis.108
4
+ version: 0.3.4.travis.115
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Feehan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-16 00:00:00.000000000 Z
11
+ date: 2013-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: log4r
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: shell-executer
42
+ name: open4
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ! '>='
@@ -198,6 +198,7 @@ files:
198
198
  - lib/derelict/connection/not_found.rb
199
199
  - lib/derelict/exception.rb
200
200
  - lib/derelict/exception/optional_reason.rb
201
+ - lib/derelict/executer.rb
201
202
  - lib/derelict/instance.rb
202
203
  - lib/derelict/instance/command_failed.rb
203
204
  - lib/derelict/instance/invalid.rb
@@ -234,6 +235,7 @@ files:
234
235
  - spec/derelict/connection_spec.rb
235
236
  - spec/derelict/exception/optional_reason_spec.rb
236
237
  - spec/derelict/exception_spec.rb
238
+ - spec/derelict/executer_spec.rb
237
239
  - spec/derelict/instance/command_failed_spec.rb
238
240
  - spec/derelict/instance/invalid_spec.rb
239
241
  - spec/derelict/instance/missing_binary_spec.rb
@@ -296,6 +298,7 @@ test_files:
296
298
  - spec/derelict/connection_spec.rb
297
299
  - spec/derelict/exception/optional_reason_spec.rb
298
300
  - spec/derelict/exception_spec.rb
301
+ - spec/derelict/executer_spec.rb
299
302
  - spec/derelict/instance/command_failed_spec.rb
300
303
  - spec/derelict/instance/invalid_spec.rb
301
304
  - spec/derelict/instance/missing_binary_spec.rb