expect-pty 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.
- checksums.yaml +7 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +264 -0
- data/Rakefile +38 -0
- data/docs/COMPATIBILITY.md +65 -0
- data/docs/RELEASING.md +55 -0
- data/docs/VERIFICATION.md +146 -0
- data/examples/dialogue.rb +30 -0
- data/examples/kibitz/README.md +73 -0
- data/examples/kibitz/kibitz.rb +139 -0
- data/examples/kibitz/test_kibitz.rb +37 -0
- data/examples/ssh_auto.rb +94 -0
- data/examples/ssh_interact.rb +159 -0
- data/examples/ssh_login.rb +64 -0
- data/expect-pty.gemspec +25 -0
- data/lib/expect/configuration.rb +113 -0
- data/lib/expect/engine.rb +141 -0
- data/lib/expect/interconnect.rb +170 -0
- data/lib/expect/pattern.rb +62 -0
- data/lib/expect/pattern_list.rb +90 -0
- data/lib/expect/pty.rb +4 -0
- data/lib/expect/resources.rb +63 -0
- data/lib/expect/result.rb +14 -0
- data/lib/expect/version.rb +6 -0
- data/lib/expect.rb +591 -0
- data/script/ci +44 -0
- data/script/release.rb +267 -0
- data/test/compare_upstream.rb +157 -0
- data/test/configuration_test.rb +90 -0
- data/test/edge_case_test.rb +183 -0
- data/test/fixtures/ssh_scripts/01_identity.sh +4 -0
- data/test/fixtures/ssh_scripts/02_output.sh +5 -0
- data/test/fixtures/ssh_scripts/03_delayed.sh +6 -0
- data/test/fixtures/ssh_scripts/04_failure.sh +2 -0
- data/test/fixtures/ssh_scripts/05_recovery.sh +3 -0
- data/test/integration/README.md +90 -0
- data/test/integration/ssh_scripts.rb +94 -0
- data/test/interact_test.rb +151 -0
- data/test/interconnect_test.rb +226 -0
- data/test/io_test.rb +184 -0
- data/test/kibitz_test.rb +45 -0
- data/test/matching_test.rb +178 -0
- data/test/multi_session_test.rb +66 -0
- data/test/process_test.rb +244 -0
- data/test/release_test.rb +153 -0
- data/test/ruby_api_test.rb +515 -0
- data/test/script_logging_test.rb +124 -0
- data/test/support/interact_probe.rb +125 -0
- data/test/support/kibitz_probe.rb +177 -0
- data/test/support/script_probe.rb +157 -0
- data/test/test_helper.rb +58 -0
- data/test/timeout_test.rb +170 -0
- metadata +97 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class ProcessTest < ExpectTest
|
|
6
|
+
def test_spawn_has_controlling_terminal_and_merged_stderr
|
|
7
|
+
session = child(<<~RUBY)
|
|
8
|
+
terminal = File.open("/dev/tty", &:tty?)
|
|
9
|
+
puts [STDIN.tty?, STDOUT.tty?, STDERR.tty?, terminal].join(":")
|
|
10
|
+
STDERR.puts "error-stream"
|
|
11
|
+
RUBY
|
|
12
|
+
assert_equal 1, session.expect("true:true:true:true", timeout: 2)
|
|
13
|
+
assert_equal 1, session.expect("error-stream", timeout: 2)
|
|
14
|
+
session.soft_close(timeout: 1)
|
|
15
|
+
assert_equal 0, session.exit_code
|
|
16
|
+
assert_equal 0, session.process_status.to_i
|
|
17
|
+
assert session.closed?
|
|
18
|
+
refute session.alive?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_raw_mode_is_applied_before_child_starts
|
|
22
|
+
session = child(<<~'RUBY', raw_pty: true)
|
|
23
|
+
print "name: "
|
|
24
|
+
value = STDIN.gets
|
|
25
|
+
puts "reply=#{value.strip.reverse}"
|
|
26
|
+
RUBY
|
|
27
|
+
session.expect("name: ", timeout: 2)
|
|
28
|
+
session.write("crate\n")
|
|
29
|
+
assert_equal 1, session.expect("reply=etarc\n", timeout: 2)
|
|
30
|
+
assert_equal "", session.before
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_new_pty_supports_slave_configuration_before_spawn
|
|
34
|
+
session = Expect.new(log_stdout: false)
|
|
35
|
+
@sessions << session
|
|
36
|
+
assert session.slave.tty?
|
|
37
|
+
session.slave.echo = false
|
|
38
|
+
session.slave.winsize = [37, 111]
|
|
39
|
+
session.spawn(RbConfig.ruby, "--disable-gems", "-rio/console", "-e",
|
|
40
|
+
'STDOUT.sync = true; puts STDIN.winsize.join(":"); puts STDIN.gets')
|
|
41
|
+
assert_equal 1, session.expect("37:111", timeout: 2)
|
|
42
|
+
assert_equal [37, 111], session.winsize
|
|
43
|
+
session.winsize = [24, 80]
|
|
44
|
+
assert_equal [24, 80], session.winsize
|
|
45
|
+
session.clear_buffer
|
|
46
|
+
session.write("line\n")
|
|
47
|
+
session.expect("line", timeout: 2)
|
|
48
|
+
session.expect(timeout: 2)
|
|
49
|
+
assert_equal "\r\n", session.before
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_command_arguments_environment_and_directory
|
|
53
|
+
Dir.mktmpdir do |dir|
|
|
54
|
+
session = Expect.spawn(RbConfig.ruby, "--disable-gems", "-e",
|
|
55
|
+
'puts ARGV.first; puts ENV.fetch("EXPECT_TEST_VALUE"); puts Dir.pwd', "a;$(exit) b",
|
|
56
|
+
env: { "EXPECT_TEST_VALUE" => "environment" }, chdir: dir,
|
|
57
|
+
log_stdout: false, raw_pty: true)
|
|
58
|
+
@sessions << session
|
|
59
|
+
assert_equal 1, session.expect("a;$(exit) b\nenvironment\n#{File.realpath(dir)}\n", timeout: 2)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_string_command_supports_shell_semantics
|
|
64
|
+
session = Expect.new("printf 'shell-one'; printf 'shell-two'", log_stdout: false)
|
|
65
|
+
@sessions << session
|
|
66
|
+
assert_equal 1, session.expect("shell-oneshell-two", timeout: 2)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_exec_failure_is_synchronous_and_reaped
|
|
70
|
+
session = Expect.new(log_stdout: false)
|
|
71
|
+
@sessions << session
|
|
72
|
+
error = assert_raises(Expect::SpawnError) { session.spawn("/no/such/expect-test-command") }
|
|
73
|
+
assert_match(/ENOENT/, error.message)
|
|
74
|
+
assert session.closed?
|
|
75
|
+
assert_nil session.pid
|
|
76
|
+
assert_raises(Expect::SpawnError) { session.spawn("cat") }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_block_closes_child_on_exception
|
|
80
|
+
pid = nil
|
|
81
|
+
assert_raises(RuntimeError) do
|
|
82
|
+
Expect.spawn(RbConfig.ruby, "--disable-gems", "-e", "sleep 60", log_stdout: false) do |session|
|
|
83
|
+
pid = session.pid
|
|
84
|
+
raise "stop"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) }
|
|
88
|
+
assert_raises(Errno::ESRCH) { Process.kill(0, pid) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_soft_close_drains_output_before_exit_and_returns_process_status
|
|
92
|
+
session = child('sleep 0.03; print "last output"; exit 7', raw_pty: true)
|
|
93
|
+
log = StringIO.new
|
|
94
|
+
session.log_to(log)
|
|
95
|
+
assert_equal 7, session.soft_close(timeout: 1).exitstatus
|
|
96
|
+
assert_equal "last output", log.string
|
|
97
|
+
assert_equal "last output", session.buffer
|
|
98
|
+
assert_equal 7, session.exit_code
|
|
99
|
+
refute log.closed?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_hard_close_escalates_uncooperative_child_and_reaps
|
|
103
|
+
session = child('Signal.trap("HUP", "IGNORE"); Signal.trap("TERM", "IGNORE"); puts "ready"; loop { sleep 1 }')
|
|
104
|
+
session.expect("ready", timeout: 2)
|
|
105
|
+
pid = session.pid
|
|
106
|
+
bounded(2) { session.hard_close(timeout: 0.03) }
|
|
107
|
+
assert_equal Signal.list.fetch("KILL"), session.process_status.termsig
|
|
108
|
+
assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) }
|
|
109
|
+
assert_equal session.process_status, session.hard_close
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_soft_close_sends_term_and_returns_the_exit_status
|
|
113
|
+
session = child(<<~RUBY, raw_pty: true)
|
|
114
|
+
Signal.trap("HUP", "IGNORE")
|
|
115
|
+
Signal.trap("TERM") { exit 7 }
|
|
116
|
+
puts "ready"
|
|
117
|
+
sleep 60
|
|
118
|
+
RUBY
|
|
119
|
+
session.expect("ready", timeout: 2)
|
|
120
|
+
status = bounded { session.soft_close(timeout: 0, term_timeout: 1) }
|
|
121
|
+
assert_equal 7, status.exitstatus
|
|
122
|
+
assert session.closed?
|
|
123
|
+
refute session.alive?
|
|
124
|
+
assert_same status, session.soft_close(timeout: 0)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_soft_close_never_sends_kill_and_retains_a_live_pid_for_hard_close
|
|
128
|
+
session = child(<<~RUBY)
|
|
129
|
+
Signal.trap("HUP", "IGNORE")
|
|
130
|
+
Signal.trap("TERM", "IGNORE")
|
|
131
|
+
puts "ready"
|
|
132
|
+
sleep 60
|
|
133
|
+
RUBY
|
|
134
|
+
session.expect("ready", timeout: 2)
|
|
135
|
+
pid = session.pid
|
|
136
|
+
assert_nil(bounded { session.soft_close(timeout: 0, term_timeout: 0.03) })
|
|
137
|
+
assert session.closed?
|
|
138
|
+
assert session.alive?
|
|
139
|
+
assert_equal pid, session.pid
|
|
140
|
+
assert_nil session.wait(timeout: 0)
|
|
141
|
+
status = bounded { session.hard_close(timeout: 0.03) }
|
|
142
|
+
assert_equal Signal.list.fetch("KILL"), status.termsig
|
|
143
|
+
assert_nil session.pid
|
|
144
|
+
assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_block_cleanup_uses_the_configured_graceful_close_and_drains_output
|
|
148
|
+
log = StringIO.new
|
|
149
|
+
connection = nil
|
|
150
|
+
value = Expect.spawn(RbConfig.ruby, "--disable-gems", "-e", 'sleep 0.03; puts "tail"',
|
|
151
|
+
raw_pty: true, graceful_close: true) do |session|
|
|
152
|
+
connection = session
|
|
153
|
+
session.log_output = log
|
|
154
|
+
:finished
|
|
155
|
+
end
|
|
156
|
+
assert_equal :finished, value
|
|
157
|
+
assert_equal "tail\n", log.string
|
|
158
|
+
assert connection.closed?
|
|
159
|
+
assert_equal 0, connection.exit_code
|
|
160
|
+
refute log.closed?
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_explicit_graceful_close_returns_nil_like_io_close
|
|
164
|
+
session = child('sleep 0.03; print "tail"', raw_pty: true)
|
|
165
|
+
assert_nil session.close(graceful: true)
|
|
166
|
+
assert_equal "tail", session.buffer
|
|
167
|
+
assert_equal 0, session.exit_code
|
|
168
|
+
assert_nil session.close
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_close_still_reaps_the_child_when_graceful_logging_fails
|
|
172
|
+
session = child('puts "ready"; sleep 60', raw_pty: true)
|
|
173
|
+
pid = session.pid
|
|
174
|
+
session.log_to { raise IOError, "log failed" }
|
|
175
|
+
assert_raises(IOError) { bounded { session.close(graceful: true) } }
|
|
176
|
+
assert session.closed?
|
|
177
|
+
refute session.alive?
|
|
178
|
+
assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_invalid_close_timeout_does_not_close_or_signal_the_child
|
|
182
|
+
session = child('puts "ready"; sleep 60')
|
|
183
|
+
session.expect("ready", timeout: 2)
|
|
184
|
+
assert_raises(ArgumentError) { session.soft_close(timeout: 0, term_timeout: nil) }
|
|
185
|
+
assert_raises(ArgumentError) { session.soft_close(timeout: 0, term_timeout: -1) }
|
|
186
|
+
assert_raises(ArgumentError) { session.hard_close(timeout: nil) }
|
|
187
|
+
refute session.closed?
|
|
188
|
+
assert session.alive?
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_eof_does_not_prevent_buffered_match
|
|
192
|
+
session = child('print "final"; exit 4', raw_pty: true)
|
|
193
|
+
assert_equal 1, session.expect("final", timeout: 2)
|
|
194
|
+
assert session.expect_result(:eof, timeout: 2).eof?
|
|
195
|
+
assert_equal 4, session.wait(timeout: 1).exitstatus
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_wait_timeout_and_explicit_close
|
|
199
|
+
session = child("sleep 0.15; exit 0")
|
|
200
|
+
assert_nil session.wait(timeout: 0)
|
|
201
|
+
assert session.alive?
|
|
202
|
+
assert_equal 0, session.wait(timeout: 1).exitstatus
|
|
203
|
+
refute session.alive?
|
|
204
|
+
session.close
|
|
205
|
+
assert_raises(IOError) { session.write("x") }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def test_borrowed_and_owned_io_lifetimes
|
|
209
|
+
session, writer = pipe_session
|
|
210
|
+
reader = session.to_io
|
|
211
|
+
session.close
|
|
212
|
+
refute reader.closed?
|
|
213
|
+
writer.write("x")
|
|
214
|
+
assert_equal "x", reader.read(1)
|
|
215
|
+
owned = Expect.open(reader, own: true)
|
|
216
|
+
owned.close
|
|
217
|
+
assert reader.closed?
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_stty_roundtrip
|
|
221
|
+
session = child("sleep 30")
|
|
222
|
+
initial = session.stty
|
|
223
|
+
configuration = terminal_configuration(session)
|
|
224
|
+
session.stty("raw -echo")
|
|
225
|
+
refute session.to_io.echo?
|
|
226
|
+
session.stty(initial)
|
|
227
|
+
assert_equal configuration, terminal_configuration(session)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def test_configuration_and_session_attributes
|
|
231
|
+
Expect.configure(timeout: 0.01, log_stdout: false)
|
|
232
|
+
session = Expect.new
|
|
233
|
+
@sessions << session
|
|
234
|
+
refute session.log_stdout?
|
|
235
|
+
assert_equal 0.01, session.timeout
|
|
236
|
+
session.log_stdout = true
|
|
237
|
+
assert session.log_stdout
|
|
238
|
+
session.log_stdout = false
|
|
239
|
+
refute session.log_stdout
|
|
240
|
+
session.buffer_limit = 30
|
|
241
|
+
assert_equal 30, session.buffer_limit
|
|
242
|
+
assert_match(/\A#<Expect .*closed=false>\z/, session.inspect)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "minitest/mock"
|
|
5
|
+
require_relative "../script/release"
|
|
6
|
+
|
|
7
|
+
class ReleaseTest < Minitest::Test
|
|
8
|
+
def test_extracts_only_the_selected_version
|
|
9
|
+
changelog = "# Changelog\n\n## Unreleased\n\n## 0.2.0 - 2026-09-12\n\n- New API.\n\n## 0.1.1\n\n- Old API.\n"
|
|
10
|
+
assert_equal "- New API.", Release.release_notes(changelog, "0.2.0")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_rejects_unreleased_missing_empty_or_invalid_versions
|
|
14
|
+
["## Unreleased\n- Pending.\n## 0.2.0\n- Ready.", "## 0.1.1\n- Old.", "## 0.2.0\n"].each do |changelog|
|
|
15
|
+
assert_raises(RuntimeError) { Release.release_notes(changelog, "0.2.0") }
|
|
16
|
+
end
|
|
17
|
+
assert_raises(RuntimeError) { Release.release_notes("## 0.2.0.rc1\n- Preview.", "0.2.0.rc1") }
|
|
18
|
+
assert_raises(RuntimeError) { Release.release_notes("## Unreleased \t\n- Pending.\n## 0.2.0\n- Ready.", "0.2.0") }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_dry_run_verifies_the_artifact_without_remote_calls
|
|
22
|
+
with_package do |release, artifact|
|
|
23
|
+
release.stub(:github, ->(*) { flunk "dry run contacted GitHub" }) do
|
|
24
|
+
release.stub(:get, ->(*) { flunk "dry run contacted RubyGems" }) do
|
|
25
|
+
capture_io { release.run }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
directory = Dir.glob(File.join("pkg", "release", Expect::VERSION, "candidate-*")).fetch(0)
|
|
29
|
+
checksum = File.read(File.join(directory, "SHA256SUMS"))
|
|
30
|
+
assert_equal "#{Digest::SHA256.file(artifact).hexdigest} #{File.basename(artifact)}\n", checksum
|
|
31
|
+
assert_equal "- Release fixture.\n", File.read(File.join(directory, "release-notes.md"))
|
|
32
|
+
File.write(artifact, "another build")
|
|
33
|
+
assert_equal checksum.split.first, Digest::SHA256.file(File.join(directory, File.basename(artifact))).hexdigest
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_rejects_a_package_from_different_source
|
|
38
|
+
with_package do |release, _artifact|
|
|
39
|
+
File.write("payload.rb", "puts :changed\n")
|
|
40
|
+
assert_match "Artifact differs from source", assert_raises(RuntimeError) { release.send(:verify_package) }.message
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_rejects_different_installation_metadata_even_with_identical_source_files
|
|
45
|
+
with_package do |release, _artifact|
|
|
46
|
+
expected = Gem::Specification.load(File.expand_path("expect-pty.gemspec")).dup
|
|
47
|
+
expected.add_runtime_dependency "unexpected-dependency", "= 1.0.0"
|
|
48
|
+
Gem::Specification.stub(:load, expected) do
|
|
49
|
+
assert_match "metadata", assert_raises(RuntimeError) { release.send(:verify_package) }.message
|
|
50
|
+
end
|
|
51
|
+
expected = Gem::Specification.load(File.expand_path("expect-pty.gemspec")).dup
|
|
52
|
+
expected.extensions = ["payload.rb"]
|
|
53
|
+
Gem::Specification.stub(:load, expected) do
|
|
54
|
+
assert_match "metadata", assert_raises(RuntimeError) { release.send(:verify_package) }.message
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_registry_errors_are_not_treated_as_an_unpublished_version
|
|
60
|
+
release = Release.new
|
|
61
|
+
response = Struct.new(:code, :body).new("503", "unavailable")
|
|
62
|
+
release.stub(:get, response) { assert_raises(RuntimeError) { release.send(:registry_version) } }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_rejects_overwriting_a_published_version_or_reusing_a_yanked_version
|
|
66
|
+
release = Release.new
|
|
67
|
+
release.instance_variable_set(:@sha256, "expected")
|
|
68
|
+
[{ "sha" => "different", "yanked" => false }, { "sha" => "expected", "yanked" => true }].each do |version|
|
|
69
|
+
assert_raises(RuntimeError) { release.send(:verify_registry_checksum, version) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_existing_registry_version_is_downloaded_and_verified_without_pushing_again
|
|
74
|
+
release = Release.new
|
|
75
|
+
bytes = "published gem"
|
|
76
|
+
checksum = Digest::SHA256.hexdigest(bytes)
|
|
77
|
+
release.instance_variable_set(:@sha256, checksum)
|
|
78
|
+
response = Struct.new(:code, :body).new("200", bytes)
|
|
79
|
+
release.stub(:registry_version, { "sha" => checksum, "yanked" => false }) do
|
|
80
|
+
release.stub(:system, ->(*) { flunk "repushed an existing gem" }) do
|
|
81
|
+
release.stub(:get, response) { capture_io { release.send(:publish_rubygems) } }
|
|
82
|
+
response.body = "different bytes"
|
|
83
|
+
release.stub(:get, response) { assert_raises(RuntimeError) { release.send(:publish_rubygems) } }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_remote_tag_must_point_to_the_verified_commit
|
|
89
|
+
release = Release.new
|
|
90
|
+
release.instance_variable_set(:@commit, "verified")
|
|
91
|
+
capture = ->(*arguments) { arguments.include?("rev-parse") ? "verified" : "" }
|
|
92
|
+
github = lambda do |path, **|
|
|
93
|
+
{ "status" => (path.end_with?("...main") ? "ahead" : "behind") }
|
|
94
|
+
end
|
|
95
|
+
release.stub(:capture, capture) do
|
|
96
|
+
release.stub(:github, github) do
|
|
97
|
+
assert_match "another commit", assert_raises(RuntimeError) { release.send(:verify_remote_source) }.message
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_package_validation_uses_archive_modes_regardless_of_umask
|
|
103
|
+
with_package do |release, artifact|
|
|
104
|
+
File.chmod(0o755, "payload.rb")
|
|
105
|
+
capture_io { Gem::Package.build(Gem::Specification.load(File.expand_path("expect-pty.gemspec"))) }
|
|
106
|
+
previous_umask = File.umask(0o077)
|
|
107
|
+
begin
|
|
108
|
+
release.send(:verify_package)
|
|
109
|
+
assert File.file?(artifact)
|
|
110
|
+
File.chmod(0o644, "payload.rb")
|
|
111
|
+
assert_match "permissions differ", assert_raises(RuntimeError) { release.send(:verify_package) }.message
|
|
112
|
+
ensure
|
|
113
|
+
File.umask(previous_umask)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_incomplete_github_asset_has_an_explicit_recovery_message
|
|
119
|
+
release = Release.new
|
|
120
|
+
release.instance_variable_set(:@checksum_file, "SHA256SUMS")
|
|
121
|
+
remote = { "assets" => [{ "name" => "SHA256SUMS", "state" => "starter" }] }
|
|
122
|
+
release.stub(:github_release, remote) do
|
|
123
|
+
error = assert_raises(RuntimeError) { release.send(:publish_github) }
|
|
124
|
+
assert_match "Incomplete GitHub asset: SHA256SUMS", error.message
|
|
125
|
+
assert_match "stop any active upload", error.message
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def with_package
|
|
132
|
+
Dir.mktmpdir("expect-release-test-") do |directory|
|
|
133
|
+
Dir.chdir(directory) do
|
|
134
|
+
File.write("payload.rb", "puts :original\n")
|
|
135
|
+
File.write("CHANGELOG.md", "## #{Expect::VERSION}\n\n- Release fixture.\n")
|
|
136
|
+
File.write("expect-pty.gemspec", <<~RUBY)
|
|
137
|
+
Gem::Specification.new do |spec|
|
|
138
|
+
spec.name = "expect-pty"
|
|
139
|
+
spec.version = "#{Expect::VERSION}"
|
|
140
|
+
spec.summary = "Release test fixture"
|
|
141
|
+
spec.authors = ["Test"]
|
|
142
|
+
spec.license = "MIT"
|
|
143
|
+
spec.homepage = "https://github.com/gatework/expect-ruby"
|
|
144
|
+
spec.files = ["payload.rb", "CHANGELOG.md", "expect-pty.gemspec"]
|
|
145
|
+
end
|
|
146
|
+
RUBY
|
|
147
|
+
artifact = nil
|
|
148
|
+
capture_io { artifact = Gem::Package.build(Gem::Specification.load(File.expand_path("expect-pty.gemspec"))) }
|
|
149
|
+
yield Release.new(artifact: artifact, dry_run: true), artifact
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|