terrapin 0.6.0 → 1.1.1

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
- SHA1:
3
- metadata.gz: fe1ba61cb02d982ac5535ff8423186dcb045c9de
4
- data.tar.gz: d1eae833ed7b0041442275d6a005188c4300a5fa
2
+ SHA256:
3
+ metadata.gz: de8290a9a7c02f78729afc093bc4f91ded448cde41dfa85d3d7c57ce0acb1aa8
4
+ data.tar.gz: ebdcaed25d509d37f0652f3a3a8ed4f195d574a2ccf2e7ac57e541da4585b243
5
5
  SHA512:
6
- metadata.gz: d8d2a8e41b0f12e41062cfcbbacaa7abcc9e2d02519ffe27214a4ffaa93c5c0243b681066fa76e59c66e13dd2f8abf87258644b5cf421c7e66e47118434c764d
7
- data.tar.gz: 3e3e8d27c8a9e569fe99b30a09baacbe05a7dae3a260b36470453bcaaa7303f685a6868d84304d6e1afd55661ead32b31d89f3d4debf01bd7b31c67379ac47b0
6
+ metadata.gz: 8abf06d0433f3955d508291e81e44d5a9300a4bbddbb804ee40e959152a2bea72eaf0454a673fc427aba84aa5a747e90afacb0efa7ccf64aa159adec8e87647e
7
+ data.tar.gz: d4b1e42addf7855b8b38656aa7ad36f149041e2a1b8595fcad71fc5624178e6fde1b9472dd618a95a4766ebe781f0e44ba00f572ad72423216379dd2e83304a1
@@ -0,0 +1,32 @@
1
+ name: CI
2
+ on:
3
+ - push
4
+ - pull_request
5
+
6
+ jobs:
7
+ build:
8
+ name: Ruby ${{ matrix.ruby }}
9
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby:
14
+ - jruby-9.4.12.0
15
+ - "2.7"
16
+ - "3.0"
17
+ - "3.1"
18
+ - "3.2"
19
+ - "3.3"
20
+ - "3.4"
21
+
22
+ runs-on: 'ubuntu-latest'
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby }}
29
+ - name: Setup project
30
+ run: bundle install
31
+ - name: Run test
32
+ run: bundle exec rake
@@ -0,0 +1,17 @@
1
+ name: update-templates
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ update-templates:
11
+ permissions:
12
+ contents: write
13
+ pull-requests: write
14
+ pages: write
15
+ uses: thoughtbot/templates/.github/workflows/dynamic-readme.yaml@main
16
+ secrets:
17
+ token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,19 @@
1
+ name: update-security
2
+
3
+ on:
4
+ push:
5
+ paths:
6
+ - SECURITY.md
7
+ branches:
8
+ - main
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ update-security:
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+ pages: write
17
+ uses: thoughtbot/templates/.github/workflows/dynamic-security.yaml@main
18
+ secrets:
19
+ token: ${{ secrets.GITHUB_TOKEN }}
data/CODEOWNERS ADDED
@@ -0,0 +1,15 @@
1
+ # Lines starting with '#' are comments.
2
+ # Each line is a file pattern followed by one or more owners.
3
+
4
+ # More details are here: https://help.github.com/articles/about-codeowners/
5
+
6
+ # The '*' pattern is global owners.
7
+
8
+ # Order is important. The last matching pattern has the most precedence.
9
+ # The folders are ordered as follows:
10
+
11
+ # In each subsection folders are ordered first by depth, then alphabetically.
12
+ # This should make it easy to add new rules without breaking existing ones.
13
+
14
+ # Global rule:
15
+ * @cpytel
data/Gemfile CHANGED
@@ -1,7 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- platforms :ruby do
6
- gem "posix-spawn"
7
- end
data/NEWS.md CHANGED
@@ -1,3 +1,29 @@
1
+ New for 1.1.1:
2
+
3
+ * Fix specs flaking due to leaky path and runner
4
+ * Use String.new to create a mutable string
5
+
6
+ New for 1.1.0:
7
+
8
+ * Remove Travis CI configuration
9
+ * Fix the stderr test
10
+ * Add Ruby 3.3, 3.4 to the build matrix
11
+ * Upgrade actions/checkout from v2 to v4
12
+ * Upgrade JRuby to 9.4.12.0
13
+ * Insert an inspect on the exit status
14
+ * Don't hang on stderr
15
+ * Add 'logger' as development dependency (fixes ruby 3.4 warning)
16
+ * Use String.new to create a mutable string
17
+
18
+ New for 1.0.1:
19
+
20
+ * Relax version requirement for `climate_control` dependency
21
+
22
+ New for 1.0.0:
23
+
24
+ * Terrapin::CommandLine::PosixRunner was removed. You can replace any usage of this with Terrapin::CommandLine::ProcessRunner, which uses Ruby’s builtin Process.spawn.
25
+ * Moved CI from Travis to GH Actions.
26
+
1
27
  New for 0.6.0:
2
28
 
3
29
  * Rename the project to Terrapin
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Terrapin [![Build Status](https://secure.travis-ci.org/thoughtbot/terrapin.png?branch=master)](http://travis-ci.org/thoughtbot/terrapin)
1
+ # Terrapin
2
2
 
3
3
  Run shell commands safely, even with user-supplied values
4
4
 
@@ -105,11 +105,11 @@ line.command # => "lolwut", but it looks in /opt/bin for it.
105
105
  You can even give it a bunch of places to look:
106
106
 
107
107
  ```ruby
108
- FileUtils.rm("/opt/bin/lolwut")
109
- File.open('/usr/local/bin/lolwut') {|f| f.write('echo Hello') }
110
- Terrapin::CommandLine.path = ["/opt/bin", "/usr/local/bin"]
111
- line = Terrapin::CommandLine.new("lolwut")
112
- line.run # => prints 'Hello', because it searches the path
108
+ FileUtils.rm("/opt/bin/lolwut")
109
+ File.open('/usr/local/bin/lolwut') { |f| f.write('echo Hello') }
110
+ Terrapin::CommandLine.path = ["/opt/bin", "/usr/local/bin"]
111
+ line = Terrapin::CommandLine.new("lolwut")
112
+ line.run # => prints 'Hello', because it searches the path
113
113
  ```
114
114
 
115
115
  Or just put it in the command:
@@ -145,23 +145,18 @@ second argument of `new`. Terrapin assumes that you will not be manually
145
145
  passing user-generated data to that argument and will be using it as a template
146
146
  for your command line's structure.
147
147
 
148
- ## POSIX Spawn
148
+ ## Runners
149
149
 
150
- You can potentially increase performance by installing [the posix-spawn
151
- gem](https://rubygems.org/gems/posix-spawn). This gem can keep your
152
- application's heap from being copied when forking command line
153
- processes. For applications with large heaps the gain can be
154
- significant. To include `posix-spawn`, simply add it to your `Gemfile` or,
155
- if you don't use bundler, install the gem.
150
+ Terrapin will choose from among a couple different ways of running commands.
151
+ The simplest is `Process.spawn`, which is also the default. Terrapin can also just use [backticks], so if for some reason you'd prefer that, you can ask Terrapin to use that:
156
152
 
157
- ## Runners
153
+ ```ruby
154
+ Terrapin::CommandLine.runner = Terrapin::CommandLine::BackticksRunner.new
155
+ ```
156
+
157
+ And if you really want to, you can define your own Runner, though I can't imagine why you would.
158
158
 
159
- Terrapin will attempt to choose from among 3 different ways of running commands.
160
- The simplest is using backticks, and is the default in 1.8. In Ruby 1.9, it
161
- will attempt to use `Process.spawn`. And, as mentioned above, if the
162
- `posix-spawn` gem is installed, it will attempt to use that. If for some reason
163
- one of the `.spawn` runners don't work for you, you can override them manually
164
- by setting a new runner, like so:
159
+ [backticks]: https://ruby-doc.org/3.2.1/Kernel.html#method-i-60
165
160
 
166
161
  ```ruby
167
162
  Terrapin::CommandLine.runner = Terrapin::CommandLine::BackticksRunner.new
@@ -212,16 +207,26 @@ Question? Idea? Problem? Bug? Comment? Concern? Like using question marks?
212
207
  Thank you to all [the
213
208
  contributors](https://github.com/thoughtbot/terrapin/graphs/contributors)!
214
209
 
215
- ![thoughtbot](http://thoughtbot.com/logo.png)
216
-
217
- Terrapin is maintained and funded by [thoughtbot,
218
- inc](http://thoughtbot.com/community)
219
-
220
- The names and logos for thoughtbot are trademarks of thoughtbot, inc.
221
-
222
210
  ## License
223
211
 
224
- Copyright 2011-2018 Jon Yurek and thoughtbot, inc. This is free software, and
212
+ Copyright © 2011 Jon Yurek and thoughtbot, inc. This is free software, and
225
213
  may be redistributed under the terms specified in the
226
214
  [LICENSE](https://github.com/thoughtbot/terrapin/blob/master/LICENSE)
227
215
  file.
216
+
217
+ <!-- START /templates/footer.md -->
218
+ ## About thoughtbot
219
+
220
+ ![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg)
221
+
222
+ This repo is maintained and funded by thoughtbot, inc.
223
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
224
+
225
+ We love open source software!
226
+ See [our other projects][community].
227
+ We are [available for hire][hire].
228
+
229
+ [community]: https://thoughtbot.com/community?utm_source=github
230
+ [hire]: https://thoughtbot.com/hire-us?utm_source=github
231
+
232
+ <!-- END /templates/footer.md -->
data/SECURITY.md ADDED
@@ -0,0 +1,20 @@
1
+ <!-- START /templates/security.md -->
2
+ # Security Policy
3
+
4
+ ## Supported Versions
5
+
6
+ Only the the latest version of this project is supported at a given time. If
7
+ you find a security issue with an older version, please try updating to the
8
+ latest version first.
9
+
10
+ If for some reason you can't update to the latest version, please let us know
11
+ your reasons so that we can have a better understanding of your situation.
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ For security inquiries or vulnerability reports, visit
16
+ <https://thoughtbot.com/security>.
17
+
18
+ If you have any suggestions to improve this policy, visit <https://thoughtbot.com/security>.
19
+
20
+ <!-- END /templates/security.md -->
@@ -29,19 +29,51 @@ module Terrapin
29
29
  end
30
30
 
31
31
  def read
32
- @stdout_output = read_stream(@stdout_in)
33
- @stderr_output = read_stream(@stderr_in)
32
+ read_streams(@stdout_in, @stderr_in)
34
33
  end
35
34
 
36
35
  def close_read
37
- @stdout_in.close
36
+ begin
37
+ @stdout_in.close
38
+ rescue IOError
39
+ # do nothing
40
+ end
41
+
42
+ begin
38
43
  @stderr_in.close
44
+ rescue IOError
45
+ # do nothing
46
+ end
47
+ end
48
+
49
+ def read_streams(output, error)
50
+ @stdout_output = String.new
51
+ @stderr_output = String.new
52
+ read_fds = [output, error]
53
+ while !read_fds.empty?
54
+ to_read, = IO.select(read_fds)
55
+ if to_read.include?(output)
56
+ @stdout_output << read_stream(output)
57
+ read_fds.delete(output) if output.closed?
58
+ end
59
+
60
+ if to_read.include?(error)
61
+ @stderr_output << read_stream(error)
62
+ read_fds.delete(error) if error.closed?
63
+ end
64
+ end
39
65
  end
40
66
 
41
67
  def read_stream(io)
42
- result = ""
43
- while partial_result = io.read(8192)
44
- result << partial_result
68
+ result = String.new
69
+ begin
70
+ while partial_result = io.read_nonblock(8192)
71
+ result << partial_result
72
+ end
73
+ rescue EOFError, Errno::EPIPE
74
+ io.close
75
+ rescue Errno::EINTR, Errno::EWOULDBLOCK, Errno::EAGAIN
76
+ # do nothing
45
77
  end
46
78
  result
47
79
  end
@@ -2,6 +2,5 @@
2
2
 
3
3
  require 'terrapin/command_line/runners/backticks_runner'
4
4
  require 'terrapin/command_line/runners/process_runner'
5
- require 'terrapin/command_line/runners/posix_runner'
6
5
  require 'terrapin/command_line/runners/popen_runner'
7
6
  require 'terrapin/command_line/runners/fake_runner'
@@ -38,7 +38,7 @@ module Terrapin
38
38
  private
39
39
 
40
40
  def best_runner
41
- [PosixRunner, ProcessRunner, BackticksRunner].detect do |runner|
41
+ [ProcessRunner, BackticksRunner].detect do |runner|
42
42
  runner.supported?
43
43
  end.new
44
44
  end
@@ -49,15 +49,20 @@ module Terrapin
49
49
  attr_reader :exit_status, :runner
50
50
 
51
51
  def initialize(binary, params = "", options = {})
52
- @binary = binary.dup
53
- @params = params.dup
54
- @options = options.dup
55
- @runner = @options.delete(:runner) || self.class.runner
56
- @logger = @options.delete(:logger) || self.class.logger
57
- @swallow_stderr = @options.delete(:swallow_stderr)
52
+ if options.nil?
53
+ raise ArgumentError, "3rd argument to CommandLine.new should be a" \
54
+ "hash of values that will be interpolated into the command line"
55
+ end
56
+
57
+ @options = options.dup
58
+ @binary = binary.dup
59
+ @params = params.to_s.dup
60
+ @runner = @options.delete(:runner) || self.class.runner
61
+ @logger = @options.delete(:logger) || self.class.logger
62
+ @swallow_stderr = @options.delete(:swallow_stderr)
58
63
  @expected_outcodes = @options.delete(:expected_outcodes) || [0]
59
- @environment = @options.delete(:environment) || {}
60
- @runner_options = @options.delete(:runner_options) || {}
64
+ @environment = @options.delete(:environment) || {}
65
+ @runner_options = @options.delete(:runner_options) || {}
61
66
  end
62
67
 
63
68
  def command(interpolations = {})
@@ -84,7 +89,7 @@ module Terrapin
84
89
 
85
90
  unless @expected_outcodes.include?(@exit_status)
86
91
  message = [
87
- "Command '#{full_command}' returned #{@exit_status}. Expected #{@expected_outcodes.join(", ")}",
92
+ "Command '#{full_command}' returned #{@exit_status.inspect}. Expected #{@expected_outcodes.join(", ")}",
88
93
  "Here is the command output: STDOUT:\n", command_output,
89
94
  "\nSTDERR:\n", command_error_output
90
95
  ].join("\n")
@@ -1,4 +1,4 @@
1
1
  # coding: UTF-8
2
2
  module Terrapin
3
- VERSION = "0.6.0".freeze
3
+ VERSION = "1.1.1".freeze
4
4
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  require 'rspec'
2
- require 'mocha/api'
3
- require 'bourne'
4
2
  require 'terrapin'
5
3
  require 'timeout'
6
4
  require 'tempfile'
@@ -14,10 +12,13 @@ begin; require 'active_support/buffered_logger'; rescue LoadError; end
14
12
  Dir[File.dirname(__FILE__) + "/support/**.rb"].each{|support_file| require support_file }
15
13
 
16
14
  RSpec.configure do |config|
17
- config.mock_with :mocha
18
15
  config.include WithExitstatus
19
16
  config.include StubOS
20
- config.include UnsettingExitstatus
17
+
18
+ config.before(:example) do
19
+ Terrapin::CommandLine.path = nil
20
+ Terrapin::CommandLine.runner = nil
21
+ end
21
22
  end
22
23
 
23
24
  def best_logger
@@ -1,12 +1,23 @@
1
- shared_examples_for "a command that does not block" do
1
+ shared_examples_for "a command that does not block" do |opts = {}|
2
+ if opts[:supports_stderr]
3
+ it "does not block if the command output a lot on stderr" do
4
+ Timeout.timeout(5) do
5
+ output = subject.call("ruby -e '$stdout.puts %{hello}; $stderr.puts %{goodbye}*10_000'")
6
+
7
+ expect(output.output).to eq "hello\n"
8
+ expect(output.error_output).to eq "#{"goodbye" * 10_000}\n"
9
+ end
10
+ end
11
+ end
12
+
2
13
  it 'does not block if the command outputs a lot of data' do
3
14
  garbage_file = Tempfile.new("garbage")
4
15
  10.times{ garbage_file.write("A" * 1024 * 1024) }
5
16
 
6
17
  Timeout.timeout(5) do
7
18
  output = subject.call("cat '#{garbage_file.path}'")
8
- $?.exitstatus.should == 0
9
- output.output.length.should == 10 * 1024 * 1024
19
+ expect($?.exitstatus).to eq(0)
20
+ expect(output.output.length).to eq(10 * 1024 * 1024)
10
21
  end
11
22
 
12
23
  garbage_file.close
@@ -1,25 +1,25 @@
1
1
  module StubOS
2
2
  def on_windows!
3
3
  stub_os('mswin')
4
- Terrapin::OS.stubs(:path_separator).returns(";")
4
+ allow(Terrapin::OS).to receive(:path_separator).and_return(";")
5
5
  end
6
6
 
7
7
  def on_unix!
8
8
  stub_os('darwin11.0.0')
9
- Terrapin::OS.stubs(:path_separator).returns(":")
9
+ allow(Terrapin::OS).to receive(:path_separator).and_return(":")
10
10
  end
11
11
 
12
12
  def on_mingw!
13
13
  stub_os('mingw')
14
- Terrapin::OS.stubs(:path_separator).returns(";")
14
+ allow(Terrapin::OS).to receive(:path_separator).and_return(";")
15
15
  end
16
16
 
17
17
  def on_java!
18
- Terrapin::OS.stubs(:arch).returns("universal-java1.7")
18
+ allow(Terrapin::OS).to receive(:arch).and_return("universal-java1.7")
19
19
  end
20
20
 
21
21
  def stub_os(host_string)
22
22
  # http://blog.emptyway.com/2009/11/03/proper-way-to-detect-windows-platform-in-ruby/
23
- RbConfig::CONFIG.stubs(:[]).with('host_os').returns(host_string)
23
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return(host_string)
24
24
  end
25
25
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Terrapin::CommandLine::BackticksRunner do
4
4
  if Terrapin::CommandLine::BackticksRunner.supported?
5
- it_behaves_like 'a command that does not block'
5
+ it_behaves_like 'a command that does not block', { :supports_stderr => false }
6
6
 
7
7
  it 'runs the command given and captures the output in an Output' do
8
8
  output = subject.call("echo hello")
@@ -16,9 +16,9 @@ describe Terrapin::CommandLine::BackticksRunner do
16
16
 
17
17
  it 'sets the exitstatus when a command completes' do
18
18
  subject.call("ruby -e 'exit 0'")
19
- $?.exitstatus.should == 0
19
+ expect($?.exitstatus).to eq(0)
20
20
  subject.call("ruby -e 'exit 5'")
21
- $?.exitstatus.should == 5
21
+ expect($?.exitstatus).to eq(5)
22
22
  end
23
23
  end
24
24
  end
@@ -4,19 +4,19 @@ describe Terrapin::CommandLine::FakeRunner do
4
4
  it 'records commands' do
5
5
  subject.call("some command", :environment)
6
6
  subject.call("other command", :other_environment)
7
- subject.commands.should eq [["some command", :environment], ["other command", :other_environment]]
7
+ expect(subject.commands).to eq [["some command", :environment], ["other command", :other_environment]]
8
8
  end
9
9
 
10
10
  it 'can tell if a command was run' do
11
11
  subject.call("some command", :environment)
12
12
  subject.call("other command", :other_environment)
13
- subject.ran?("some command").should eq true
14
- subject.ran?("no command").should eq false
13
+ expect(subject.ran?("some command")).to eq true
14
+ expect(subject.ran?("no command")).to eq false
15
15
  end
16
16
 
17
17
  it 'can tell if a command was run even if shell options were set' do
18
18
  subject.call("something 2>/dev/null", :environment)
19
- subject.ran?("something").should eq true
19
+ expect(subject.ran?("something")).to eq true
20
20
  end
21
21
 
22
22
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Terrapin::CommandLine::PopenRunner do
4
4
  if Terrapin::CommandLine::PopenRunner.supported?
5
- it_behaves_like 'a command that does not block'
5
+ it_behaves_like 'a command that does not block', { :supports_stderr => false }
6
6
 
7
7
  it 'runs the command given and captures the output in an Output' do
8
8
  output = subject.call("echo hello")
@@ -16,9 +16,9 @@ describe Terrapin::CommandLine::PopenRunner do
16
16
 
17
17
  it 'sets the exitstatus when a command completes' do
18
18
  subject.call("ruby -e 'exit 0'")
19
- $?.exitstatus.should == 0
19
+ expect($?.exitstatus).to eq(0)
20
20
  subject.call("ruby -e 'exit 5'")
21
- $?.exitstatus.should == 5
21
+ expect($?.exitstatus).to eq(5)
22
22
  end
23
23
  end
24
24
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Terrapin::CommandLine::ProcessRunner do
4
4
  if Terrapin::CommandLine::ProcessRunner.supported?
5
- it_behaves_like "a command that does not block"
5
+ it_behaves_like "a command that does not block", { :supports_stderr => true }
6
6
 
7
7
  it 'runs the command given and captures the output' do
8
8
  output = subject.call("echo hello")
@@ -21,9 +21,9 @@ describe Terrapin::CommandLine::ProcessRunner do
21
21
 
22
22
  it 'sets the exitstatus when a command completes' do
23
23
  subject.call("ruby -e 'exit 0'")
24
- $?.exitstatus.should == 0
24
+ expect($?.exitstatus).to eq(0)
25
25
  subject.call("ruby -e 'exit 5'")
26
- $?.exitstatus.should == 5
26
+ expect($?.exitstatus).to eq(5)
27
27
  end
28
28
 
29
29
  it "runs the command it's given and allows access to stderr afterwards" do