buildbox 0.2.3 → 0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29832c2693fb640f1003b5a010eeb170a792f9c2
4
- data.tar.gz: dd13bc62a427512418a1bdaa7dabce4098c009dd
3
+ metadata.gz: 943e5240e1dc1d41d6086cdf88dadc990477f165
4
+ data.tar.gz: 7f03003bb7c71db8971a5c313325fc5461cca2dc
5
5
  SHA512:
6
- metadata.gz: bc853759496d74190b5b5edf806325693bace3870809c3e78ef868a2e8bc45f33cadb3f3feab5268ec54f0d76ccbf600d682fea035fa863284b253ee1c6f1ded
7
- data.tar.gz: 2d728c74a1571837bd7d3c93f85b49574bc65fb393435c8b61b69f2987012c13129de744df72415cf201e5d3520527b4eb1eeb223956305401dd3cb5ff0cbbe0
6
+ metadata.gz: 581a02441d7887327dd69584eb0c476b90dfcd01899e81f3da7bafe03b7d505ffbf4e3af385154d17394fe0db255eed477d098f41fe7ff3ae18ce502252fafce
7
+ data.tar.gz: c9fec973a2d1814c2342a719d14861cb0478ecdcabf7723c5e3085144430e1fea8fc35e42082b40071a723beadaaedc3413e28b297902920cc30bb4977621b1d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- buildbox (0.2.3)
4
+ buildbox (0.3)
5
5
  celluloid (~> 0.14)
6
6
  childprocess (~> 0.3)
7
7
  faraday (~> 0.8)
data/bin/buildbox-pty CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Disable stdout,stderr buffering
4
+ STDERR.sync = STDOUT.sync = true
5
+
3
6
  require 'pty'
4
7
 
5
8
  # spawn the process in a pseudo terminal so colors out outputted
data/lib/buildbox.rb CHANGED
@@ -8,6 +8,7 @@ module Buildbox
8
8
  autoload :CLI, "buildbox/cli"
9
9
  autoload :Configuration, "buildbox/configuration"
10
10
  autoload :Monitor, "buildbox/monitor"
11
+ autoload :Platform, "buildbox/platform"
11
12
  autoload :Runner, "buildbox/runner"
12
13
  autoload :Script, "buildbox/script"
13
14
  autoload :Server, "buildbox/server"
@@ -23,6 +24,10 @@ module Buildbox
23
24
  @logger ||= Logger.new(STDOUT).tap { |logger| logger.level = Logger::INFO }
24
25
  end
25
26
 
27
+ def self.logger=(logger)
28
+ @logger = logger
29
+ end
30
+
26
31
  def self.gem_root
27
32
  path = File.expand_path(File.join(__FILE__, "..", ".."))
28
33
  end
@@ -1,56 +1,146 @@
1
1
  require 'childprocess'
2
2
  require 'pty'
3
3
 
4
+ # Inspiration from:
5
+ # https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/subprocess.rb
6
+
4
7
  module Buildbox
5
8
  class Command
6
- class Result < Struct.new(:output, :exit_status)
7
- end
9
+ # The chunk size for reading from subprocess IO.
10
+ READ_CHUNK_SIZE = 4096
8
11
 
9
- def self.command(command, options = {}, &block)
10
- new(command, options).start(&block)
11
- end
12
+ # An error which occurs when the process doesn't end within
13
+ # the given timeout.
14
+ class TimeoutExceeded < StandardError; end
15
+
16
+ attr_reader :output, :exit_status
12
17
 
13
- def self.script(script, options = {}, &block)
14
- new(script, options).start(&block)
18
+ def self.run(*args, &block)
19
+ options = args.last.is_a?(Hash) ? args.pop : {}
20
+ arguments = args.dup
21
+
22
+ # Run the command
23
+ command = new(arguments, options, &block)
24
+ command.start(&block)
25
+ command
15
26
  end
16
27
 
17
28
  def initialize(arguments, options = {})
18
- @arguments = arguments
19
- @environment = options[:environment] || {}
20
- @directory = options[:directory] || "."
29
+ @arguments = arguments
30
+ @options = options
31
+ @logger = Buildbox.logger
21
32
  end
22
33
 
23
34
  def start(&block)
24
- read_io, write_io = IO.pipe
35
+ # Get the timeout, if we have one
36
+ timeout = @options[:timeout]
37
+
38
+ # Build the command we're going to run
39
+ arguments = [ *runner, *@arguments ].compact.map(&:to_s) # all arguments must be a string
40
+
41
+ # Build the ChildProcess
42
+ @logger.info("Starting process: #{arguments}")
25
43
 
26
- arguments = [ *runner, *@arguments ].compact.map(&:to_s) # all arguments must be a string
27
- process = ChildProcess.build(*arguments)
28
- process.cwd = expanded_directory
29
- process.io.stdout = process.io.stderr = write_io
44
+ process = ChildProcess.build(*arguments)
45
+ process.cwd = File.expand_path(@options[:directory] || Dir.pwd)
30
46
 
31
- @environment.each_pair do |key, value|
32
- process.environment[key] = value
47
+ # Create the pipes so we can read the output in real tim
48
+ read_pipe, write_pipe = IO.pipe
49
+ process.io.stdout = write_pipe
50
+ process.io.stderr = write_pipe
51
+ process.duplex = true
52
+
53
+ # Set the environment on the process
54
+ if @options[:environment]
55
+ @options[:environment].each_pair do |key, value|
56
+ process.environment[key] = value
57
+ end
33
58
  end
34
59
 
60
+ # Start the process
35
61
  process.start
36
- write_io.close
37
62
 
63
+ # Make sure the stdin does not buffer
64
+ process.io.stdin.sync = true
65
+
66
+ if RUBY_PLATFORM != "java"
67
+ # On Java, we have to close after. See down the method...
68
+ # Otherwise, we close the writer right here, since we're
69
+ # not on the writing side.
70
+ write_pipe.close
71
+ end
72
+
73
+ # Record the start time for timeout purposes
74
+ start_time = Time.now.to_i
75
+
76
+ # Track the output as it goes
38
77
  output = ""
39
- begin
40
- loop do
41
- chunk = read_io.readpartial(10240)
42
- cleaned_chunk = UTF8.clean(chunk)
43
78
 
44
- output << chunk
45
- yield cleaned_chunk if block_given?
79
+ @logger.debug("Selecting on IO")
80
+ while true
81
+ results = IO.select([read_pipe], nil, nil, timeout || 0.1) || []
82
+ readers = results[0]
83
+
84
+ # Check if we have exceeded our timeout
85
+ raise TimeoutExceeded if timeout && (Time.now.to_i - start_time) > timeout
86
+ # Kill the process and wait a bit for it to disappear
87
+ # Process.kill('KILL', process.pid)
88
+ # Process.waitpid2(process.pid)
89
+
90
+ # Check the readers to see if they're ready
91
+ if readers && !readers.empty?
92
+ readers.each do |r|
93
+ # Read from the IO object
94
+ data = read_io(r)
95
+
96
+ # We don't need to do anything if the data is empty
97
+ next if data.empty?
98
+
99
+ output << cleaned_data = UTF8.clean(data)
100
+ yield cleaned_data if block_given?
101
+ end
46
102
  end
47
- rescue EOFError
103
+
104
+ # Break out if the process exited. We have to do this before
105
+ # attempting to write to stdin otherwise we'll get a broken pipe
106
+ # error.
107
+ break if process.exited?
108
+ end
109
+
110
+ # Wait for the process to end.
111
+ begin
112
+ remaining = (timeout || 32000) - (Time.now.to_i - start_time)
113
+ remaining = 0 if remaining < 0
114
+ @logger.debug("Waiting for process to exit. Remaining to timeout: #{remaining}")
115
+
116
+ process.poll_for_exit(remaining)
117
+ rescue ChildProcess::TimeoutError
118
+ raise TimeoutExceeded
119
+ end
120
+
121
+ @logger.debug("Exit status: #{process.exit_code}")
122
+
123
+ # Read the final output data, since it is possible we missed a small
124
+ # amount of text between the time we last read data and when the
125
+ # process exited.
126
+
127
+ # Read the extra data
128
+ extra_data = read_io(read_pipe)
129
+
130
+ # If there's some that we missed
131
+ if extra_data != ""
132
+ output << cleaned_data = UTF8.clean(extra_data)
133
+ yield cleaned_data if block_given?
48
134
  end
49
135
 
50
- process.wait
136
+ if RUBY_PLATFORM == "java"
137
+ # On JRuby, we need to close the writers after the process,
138
+ # for some reason. See https://github.com/mitchellh/vagrant/pull/711
139
+ write_pipe.close
140
+ end
51
141
 
52
- # the final result!
53
- Result.new(output.chomp, process.exit_code)
142
+ @output = output.chomp
143
+ @exit_status = process.exit_code
54
144
  end
55
145
 
56
146
  private
@@ -67,8 +157,63 @@ module Buildbox
67
157
  [ "bash", "-c" ]
68
158
  end
69
159
 
70
- def expanded_directory
71
- File.expand_path(@directory)
160
+ # Reads data from an IO object while it can, returning the data it reads.
161
+ # When it encounters a case when it can't read anymore, it returns the
162
+ # data.
163
+ #
164
+ # @return [String]
165
+ def read_io(io)
166
+ data = ""
167
+
168
+ while true
169
+ begin
170
+ if Platform.windows?
171
+ # Windows doesn't support non-blocking reads on
172
+ # file descriptors or pipes so we have to get
173
+ # a bit more creative.
174
+
175
+ # Check if data is actually ready on this IO device.
176
+ # We have to do this since `readpartial` will actually block
177
+ # until data is available, which can cause blocking forever
178
+ # in some cases.
179
+ results = IO.select([io], nil, nil, 0.1)
180
+ break if !results || results[0].empty?
181
+
182
+ # Read!
183
+ data << io.readpartial(READ_CHUNK_SIZE)
184
+ else
185
+ # Do a simple non-blocking read on the IO object
186
+ data << io.read_nonblock(READ_CHUNK_SIZE)
187
+ end
188
+ rescue Exception => e
189
+ # The catch-all rescue here is to support multiple Ruby versions,
190
+ # since we use some Ruby 1.9 specific exceptions.
191
+
192
+ breakable = false
193
+ if e.is_a?(EOFError)
194
+ # An `EOFError` means this IO object is done!
195
+ breakable = true
196
+ elsif defined?(IO::WaitReadable) && e.is_a?(IO::WaitReadable)
197
+ # IO::WaitReadable is only available on Ruby 1.9+
198
+
199
+ # An IO::WaitReadable means there may be more IO but this
200
+ # IO object is not ready to be read from yet. No problem,
201
+ # we read as much as we can, so we break.
202
+ breakable = true
203
+ elsif e.is_a?(Errno::EAGAIN)
204
+ # Otherwise, we just look for the EAGAIN error which should be
205
+ # all that IO::WaitReadable does in Ruby 1.9.
206
+ breakable = true
207
+ end
208
+
209
+ # Break out if we're supposed to. Otherwise re-raise the error
210
+ # because it is a real problem.
211
+ break if breakable
212
+ raise
213
+ end
214
+ end
215
+
216
+ data
72
217
  end
73
218
  end
74
219
  end
@@ -0,0 +1,23 @@
1
+ module Buildbox
2
+ class Platform
3
+ class << self
4
+ [:cygwin, :darwin, :bsd, :freebsd, :linux, :solaris].each do |type|
5
+ define_method("#{type}?") do
6
+ platform.include?(type.to_s)
7
+ end
8
+ end
9
+
10
+ def windows?
11
+ %W[mingw mswin].each do |text|
12
+ return true if platform.include?(text)
13
+ end
14
+
15
+ false
16
+ end
17
+
18
+ def platform
19
+ RbConfig::CONFIG["host_os"].downcase
20
+ end
21
+ end
22
+ end
23
+ end
@@ -23,8 +23,8 @@ module Buildbox
23
23
  info "Running script: #{script_path}"
24
24
 
25
25
  build.output = ""
26
- result = Command.script(script_path, :environment => @build.env,
27
- :directory => directory_path) do |chunk|
26
+ result = Command.run(script_path, :environment => @build.env,
27
+ :directory => directory_path) do |chunk|
28
28
  build.output << chunk
29
29
  end
30
30
 
@@ -1,3 +1,3 @@
1
1
  module Buildbox
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3"
3
3
  end
@@ -5,34 +5,34 @@ require "spec_helper"
5
5
  describe Buildbox::Command do
6
6
  describe "#run" do
7
7
  it "is run within a tty" do
8
- result = Buildbox::Command.command(%{ruby -e "puts STDOUT.tty?"})
8
+ result = Buildbox::Command.run(%{ruby -e "puts STDOUT.tty?"})
9
9
 
10
10
  result.output.should == "true"
11
11
  end
12
12
 
13
13
  it "successfully runs and returns the output from a simple comment" do
14
- result = Buildbox::Command.command('echo hello world')
14
+ result = Buildbox::Command.run('echo hello world')
15
15
 
16
16
  result.exit_status.should == 0
17
17
  result.output.should == "hello world"
18
18
  end
19
19
 
20
20
  it "redirects stdout to stderr" do
21
- result = Buildbox::Command.command('echo hello world 1>&2')
21
+ result = Buildbox::Command.run('echo hello world 1>&2')
22
22
 
23
23
  result.exit_status.should == 0
24
24
  result.output.should == "hello world"
25
25
  end
26
26
 
27
27
  it "handles commands that fail and returns the correct status" do
28
- result = Buildbox::Command.command('(exit 1)')
28
+ result = Buildbox::Command.run('(exit 1)')
29
29
 
30
30
  result.exit_status.should_not == 0
31
31
  result.output.should == ''
32
32
  end
33
33
 
34
34
  it "handles running malformed commands" do
35
- result = Buildbox::Command.command('if (')
35
+ result = Buildbox::Command.run('if (')
36
36
 
37
37
  result.exit_status.should_not == 0
38
38
  # bash 3.2.48 prints "syntax error" in lowercase.
@@ -45,7 +45,7 @@ describe Buildbox::Command do
45
45
 
46
46
  it "can collect output in chunks" do
47
47
  chunked_output = ''
48
- result = Buildbox::Command.command('echo hello world') do |chunk|
48
+ result = Buildbox::Command.run('echo hello world') do |chunk|
49
49
  unless chunk.nil?
50
50
  chunked_output += chunk
51
51
  end
@@ -60,8 +60,8 @@ describe Buildbox::Command do
60
60
  result = nil
61
61
  second_result = nil
62
62
  thread = Thread.new do
63
- result = Buildbox::Command.command('sillycommandlololol')
64
- second_result = Buildbox::Command.command('export FOO=bar; doesntexist.rb')
63
+ result = Buildbox::Command.run('sillycommandlololol')
64
+ second_result = Buildbox::Command.run('export FOO=bar; doesntexist.rb')
65
65
  end
66
66
  thread.join
67
67
 
@@ -76,7 +76,7 @@ describe Buildbox::Command do
76
76
 
77
77
  it "captures color'd output from a command" do
78
78
  chunked_output = ''
79
- result = Buildbox::Command.command("rspec #{FIXTURES_PATH.join('rspec', 'test_spec.rb')}") do |chunk|
79
+ result = Buildbox::Command.run("rspec #{FIXTURES_PATH.join('rspec', 'test_spec.rb')}") do |chunk|
80
80
  chunked_output += chunk unless chunk.nil?
81
81
  end
82
82
 
@@ -87,7 +87,7 @@ describe Buildbox::Command do
87
87
 
88
88
  it "runs scripts in a tty" do
89
89
  chunked_output = ''
90
- result = Buildbox::Command.script(FIXTURES_PATH.join('tty_script')) do |chunk|
90
+ result = Buildbox::Command.run(FIXTURES_PATH.join('tty_script')) do |chunk|
91
91
  chunked_output += chunk unless chunk.nil?
92
92
  end
93
93
 
@@ -97,19 +97,29 @@ describe Buildbox::Command do
97
97
 
98
98
  it "still runs even if pty isn't available" do
99
99
  PTY.should_receive(:spawn).and_raise(RuntimeError.new)
100
- result = Buildbox::Command.command('echo hello world')
100
+ result = Buildbox::Command.run('echo hello world')
101
101
 
102
102
  result.exit_status.should == 0
103
103
  result.output.should == "hello world"
104
104
  end
105
105
 
106
106
  it "supports utf8 characters" do
107
- result = Buildbox::Command.command('echo "hello"; echo "\xE2\x98\xA0"')
107
+ result = Buildbox::Command.run('bash', '-c', 'echo "hello"; echo "\xE2\x98\xA0"')
108
108
 
109
109
  result.exit_status.should == 0
110
110
  # just trying to interact with the string that has utf8 in it to make sure that it
111
111
  # doesn't blow up like it doesn on osx. this is hacky - need a better test.
112
112
  added = result.output + "hello"
113
113
  end
114
+
115
+ it "can collect chunks from within a thread" do
116
+ chunks = []
117
+
118
+ result = Buildbox::Command.run(FIXTURES_PATH.join('sleep_script')) do |chunk|
119
+ chunks << chunk
120
+ end
121
+
122
+ chunks.should == ["0\r\n", "1\r\n", "2\r\n"]
123
+ end
114
124
  end
115
125
  end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 3.times do |i|
4
+ puts i
5
+ sleep 1
6
+ end
@@ -8,5 +8,6 @@ RSpec.configure do |config|
8
8
  logger = Logger.new(StringIO.new)
9
9
 
10
10
  Celluloid.logger = logger
11
+ Buildbox.logger = logger
11
12
  end
12
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: buildbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Pitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-08 00:00:00.000000000 Z
11
+ date: 2013-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -121,6 +121,7 @@ files:
121
121
  - lib/buildbox/command.rb
122
122
  - lib/buildbox/configuration.rb
123
123
  - lib/buildbox/monitor.rb
124
+ - lib/buildbox/platform.rb
124
125
  - lib/buildbox/runner.rb
125
126
  - lib/buildbox/server.rb
126
127
  - lib/buildbox/utf8.rb
@@ -157,6 +158,7 @@ files:
157
158
  - spec/fixtures/repo.git/objects/e9/8d8a9be514ef53609a52c9e1b820dbcc8e6603
158
159
  - spec/fixtures/repo.git/refs/heads/master
159
160
  - spec/fixtures/rspec/test_spec.rb
161
+ - spec/fixtures/sleep_script
160
162
  - spec/fixtures/tty_script
161
163
  - spec/integration/running_a_build_spec.rb
162
164
  - spec/spec_helper.rb
@@ -219,6 +221,7 @@ test_files:
219
221
  - spec/fixtures/repo.git/objects/e9/8d8a9be514ef53609a52c9e1b820dbcc8e6603
220
222
  - spec/fixtures/repo.git/refs/heads/master
221
223
  - spec/fixtures/rspec/test_spec.rb
224
+ - spec/fixtures/sleep_script
222
225
  - spec/fixtures/tty_script
223
226
  - spec/integration/running_a_build_spec.rb
224
227
  - spec/spec_helper.rb