rundoc 4.1.3 → 4.1.4
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 +4 -4
- data/CHANGELOG.md +5 -0
- data/lib/rundoc/cli.rb +2 -2
- data/lib/rundoc/code_command/background/process_spawn.rb +6 -4
- data/lib/rundoc/code_command/website/driver.rb +23 -7
- data/lib/rundoc/code_command/website/visit.rb +3 -2
- data/lib/rundoc/version.rb +1 -1
- data/test/integration/background_stdin_test.rb +65 -15
- data/test/integration/website_test.rb +19 -0
- data/test/rundoc/code_commands/background_test.rb +2 -0
- data/test/test_helper.rb +28 -0
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83fd435266e63543e96c3e24fc4d116cd043f2c89e396b8839d283eace07248a
|
4
|
+
data.tar.gz: 9818843a327f3ed3c4a46548db1dc8e9b2c706824716749d7a173563a752d6ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5972a3213f3700659aa2d9ca602821f0cb443e9e56120b5e43485da6b0240a4b7b998dea1b0637b470ac098d0fab5c7ee7922b056eebfd08575c0177bc312d9
|
7
|
+
data.tar.gz: c58f0fdf37e46817eb28871e1136811b0dd2c2a12925948e461f286b47de8c2b3bae1ba06345956908ac09c859ad0d1fe284d10a2c2a019516ba07a134103d07
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## HEAD
|
2
2
|
|
3
|
+
## 4.1.4
|
4
|
+
|
5
|
+
- Fix: Net::ReadTimeout errors on `website.visit` are now retried by default (https://github.com/zombocom/rundoc/pull/103)
|
6
|
+
- Fix: When a background process (`background.start`) does not exit cleanly, print its logs for help with debugging. (https://github.com/zombocom/rundoc/pull/104)
|
7
|
+
|
3
8
|
## 4.1.3
|
4
9
|
|
5
10
|
- Fix: Internal error in `background.wait` introduced in 4.1.2 (https://github.com/zombocom/rundoc/pull/97)
|
data/lib/rundoc/cli.rb
CHANGED
@@ -176,8 +176,8 @@ module Rundoc
|
|
176
176
|
Rundoc::CodeCommand::Background::ProcessSpawn.tasks.each do |name, task|
|
177
177
|
next unless task.alive?
|
178
178
|
|
179
|
-
io.puts "Warning background task is still running, cleaning up:
|
180
|
-
task.stop
|
179
|
+
io.puts "Warning background task is still running, cleaning up: `#{name}`"
|
180
|
+
task.stop(print_io: io)
|
181
181
|
end
|
182
182
|
|
183
183
|
if execution_context.output_dir.exist?
|
@@ -45,16 +45,16 @@ class Rundoc::CodeCommand::Background
|
|
45
45
|
attr_reader :log, :pid, :command
|
46
46
|
|
47
47
|
def initialize(command, timeout: 5, log: Tempfile.new("log"), out: "2>&1")
|
48
|
-
@
|
48
|
+
@original_command = command
|
49
49
|
@timeout_value = timeout
|
50
|
-
@log_reference = log #
|
50
|
+
@log_reference = log # Need to keep a reference to `Tempfile` or it will be deleted. Pathname does not retain the passed in reference
|
51
51
|
|
52
52
|
@log = Pathname.new(log)
|
53
53
|
@log.dirname.mkpath
|
54
54
|
FileUtils.touch(@log)
|
55
55
|
@pipe_output, @pipe_input = IO.pipe
|
56
56
|
|
57
|
-
@command = "/usr/bin/env bash -c #{@
|
57
|
+
@command = "/usr/bin/env bash -c #{@original_command.shellescape} >> #{@log} #{out}"
|
58
58
|
@pid = nil
|
59
59
|
end
|
60
60
|
|
@@ -120,13 +120,15 @@ class Rundoc::CodeCommand::Background
|
|
120
120
|
contents
|
121
121
|
end
|
122
122
|
|
123
|
-
def stop
|
123
|
+
def stop(print_io: nil)
|
124
124
|
return unless alive?
|
125
125
|
@pipe_input.close
|
126
126
|
Process.kill("TERM", -Process.getpgid(@pid))
|
127
127
|
Process.wait(@pid)
|
128
128
|
rescue Errno::ESRCH => e
|
129
129
|
puts "Error stopping process (command: #{command}): #{e}"
|
130
|
+
ensure
|
131
|
+
print_io&.puts "Log contents for `#{command}`:\n#{@log.read}"
|
130
132
|
end
|
131
133
|
|
132
134
|
def check_alive!
|
@@ -6,7 +6,8 @@ class Rundoc::CodeCommand::Website
|
|
6
6
|
class Driver
|
7
7
|
attr_reader :session
|
8
8
|
|
9
|
-
def initialize(name:, url:, width: 1024, height: 720, visible: false)
|
9
|
+
def initialize(name:, url:, width: 1024, height: 720, visible: false, io: $stdout, read_timeout: 60)
|
10
|
+
@io = io
|
10
11
|
browser_options = ::Selenium::WebDriver::Chrome::Options.new
|
11
12
|
browser_options.args << "--headless" unless visible
|
12
13
|
browser_options.args << "--disable-gpu" if Gem.win_platform?
|
@@ -15,7 +16,10 @@ class Rundoc::CodeCommand::Website
|
|
15
16
|
@width = width
|
16
17
|
@height = height
|
17
18
|
|
18
|
-
|
19
|
+
client = Selenium::WebDriver::Remote::Http::Default.new
|
20
|
+
client.read_timeout = read_timeout
|
21
|
+
|
22
|
+
@driver = Capybara::Selenium::Driver.new(nil, browser: :chrome, options: browser_options, http_client: client)
|
19
23
|
driver_name = :"rundoc_driver_#{name}"
|
20
24
|
Capybara.register_driver(driver_name) do |app|
|
21
25
|
@driver
|
@@ -24,8 +28,20 @@ class Rundoc::CodeCommand::Website
|
|
24
28
|
@session = Capybara::Session.new(driver_name)
|
25
29
|
end
|
26
30
|
|
27
|
-
def visit(url)
|
28
|
-
|
31
|
+
def visit(url, max_attempts: 3, delay: 1)
|
32
|
+
attempts = 0
|
33
|
+
begin
|
34
|
+
@session.visit(url)
|
35
|
+
rescue ::Net::ReadTimeout => e
|
36
|
+
attempts += 1
|
37
|
+
if attempts > max_attempts
|
38
|
+
raise e
|
39
|
+
else
|
40
|
+
@io.puts "Error visiting url (#{attempts}/#{max_attempts}) `#{url}`:\n#{e}"
|
41
|
+
sleep delay
|
42
|
+
retry
|
43
|
+
end
|
44
|
+
end
|
29
45
|
end
|
30
46
|
|
31
47
|
def timestamp
|
@@ -54,7 +70,7 @@ class Rundoc::CodeCommand::Website
|
|
54
70
|
msg = +""
|
55
71
|
msg << "Error running code #{code.inspect} at #{current_url.inspect}\n"
|
56
72
|
msg << "saving a screenshot to: `tmp/error.png`"
|
57
|
-
puts msg
|
73
|
+
@io.puts msg
|
58
74
|
error_path = env[:context].screenshots_dir.join("error.png")
|
59
75
|
session.save_screenshot(error_path)
|
60
76
|
raise e
|
@@ -66,13 +82,13 @@ class Rundoc::CodeCommand::Website
|
|
66
82
|
file_name = self.class.next_screenshot_name
|
67
83
|
file_path = screenshots_dir.join(file_name)
|
68
84
|
session.save_screenshot(file_path)
|
69
|
-
puts "Screenshot saved to #{file_path}"
|
85
|
+
@io.puts "Screenshot saved to #{file_path}"
|
70
86
|
|
71
87
|
return file_path unless upload
|
72
88
|
|
73
89
|
case upload
|
74
90
|
when "s3", "aws"
|
75
|
-
puts "Uploading screenshot to S3"
|
91
|
+
@io.puts "Uploading screenshot to S3"
|
76
92
|
require "aws-sdk-s3"
|
77
93
|
ENV.fetch("AWS_ACCESS_KEY_ID")
|
78
94
|
s3 = Aws::S3::Resource.new(region: ENV.fetch("AWS_REGION"))
|
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
class Rundoc::CodeCommand::Website
|
4
4
|
class Visit < Rundoc::CodeCommand
|
5
|
-
def initialize(name:, url: nil, scroll: nil, height: 720, width: 1024, visible: false)
|
5
|
+
def initialize(name:, url: nil, scroll: nil, height: 720, width: 1024, visible: false, max_attempts: 3)
|
6
6
|
@name = name
|
7
7
|
@url = url
|
8
8
|
@scroll = scroll
|
9
9
|
@height = height
|
10
10
|
@width = width
|
11
11
|
@visible = visible
|
12
|
+
@max_attempts = max_attempts
|
12
13
|
end
|
13
14
|
|
14
15
|
def driver
|
@@ -33,7 +34,7 @@ class Rundoc::CodeCommand::Website
|
|
33
34
|
|
34
35
|
puts message
|
35
36
|
|
36
|
-
driver.visit(@url) if @url
|
37
|
+
driver.visit(@url, max_attempts: @max_attempts) if @url
|
37
38
|
driver.scroll(@scroll) if @scroll
|
38
39
|
|
39
40
|
return "" if contents.nil? || contents.empty?
|
data/lib/rundoc/version.rb
CHANGED
@@ -6,21 +6,7 @@ class BackgroundStdinTest < Minitest::Test
|
|
6
6
|
Dir.chdir(dir) do
|
7
7
|
dir = Pathname(dir)
|
8
8
|
script = dir.join("script.rb")
|
9
|
-
script.write
|
10
|
-
$stdout.sync = true
|
11
|
-
|
12
|
-
print "> "
|
13
|
-
while line = gets
|
14
|
-
puts line
|
15
|
-
if line.strip == "exit"
|
16
|
-
puts "Bye"
|
17
|
-
return
|
18
|
-
else
|
19
|
-
puts "You said: #{line}"
|
20
|
-
end
|
21
|
-
print "> "
|
22
|
-
end
|
23
|
-
EOF
|
9
|
+
script.write loop_script
|
24
10
|
|
25
11
|
source_path = dir.join("RUNDOC.md")
|
26
12
|
source_path.write <<~EOF
|
@@ -54,4 +40,68 @@ class BackgroundStdinTest < Minitest::Test
|
|
54
40
|
end
|
55
41
|
end
|
56
42
|
end
|
43
|
+
|
44
|
+
def test_print_output_on_exit
|
45
|
+
Dir.mktmpdir do |dir|
|
46
|
+
Dir.chdir(dir) do
|
47
|
+
dir = Pathname(dir)
|
48
|
+
script = dir.join("script.rb")
|
49
|
+
script.write loop_script
|
50
|
+
|
51
|
+
source_path = dir.join("RUNDOC.md")
|
52
|
+
source_path.write <<~EOF
|
53
|
+
```
|
54
|
+
:::-- background.start("ruby #{script}",
|
55
|
+
name: "background_ungraceful_exit",
|
56
|
+
wait: ">",
|
57
|
+
timeout: 15
|
58
|
+
)
|
59
|
+
:::-- background.stdin_write("hello", name: "background_ungraceful_exit", wait: "hello")
|
60
|
+
```
|
61
|
+
EOF
|
62
|
+
|
63
|
+
io = StringIO.new
|
64
|
+
Rundoc::CLI.new(
|
65
|
+
io: io,
|
66
|
+
source_path: source_path,
|
67
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
68
|
+
).call
|
69
|
+
|
70
|
+
logs = io.string
|
71
|
+
|
72
|
+
match_after = partition_match_after(actual: logs, include_str: "Warning background task is still running, cleaning up: `background_ungraceful_exit`")
|
73
|
+
match_after = partition_match_after(actual: match_after, include_str: "Log contents for `/usr/bin/env bash -c")
|
74
|
+
partition_match_after(actual: match_after, include_str: "> hello")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Finds the include_str if it exists or raises an error
|
80
|
+
# Returns the contents of that string and everything after it
|
81
|
+
#
|
82
|
+
# Used to handle the case where output might be in the logs twice and we want to verify the order
|
83
|
+
def partition_match_after(actual:, include_str:)
|
84
|
+
_before, match, after = actual.partition(include_str)
|
85
|
+
found = match && !match.empty?
|
86
|
+
assert found, "Expected to find `#{include_str}` in output, but did not. Output:\n#{actual}"
|
87
|
+
[match, after].join
|
88
|
+
end
|
89
|
+
|
90
|
+
def loop_script
|
91
|
+
<<~'EOF'
|
92
|
+
$stdout.sync = true
|
93
|
+
|
94
|
+
print "> "
|
95
|
+
while line = gets
|
96
|
+
puts line
|
97
|
+
if line.strip == "exit"
|
98
|
+
puts "Bye"
|
99
|
+
return
|
100
|
+
else
|
101
|
+
puts "You said: #{line}"
|
102
|
+
end
|
103
|
+
print "> "
|
104
|
+
end
|
105
|
+
EOF
|
106
|
+
end
|
57
107
|
end
|
@@ -34,4 +34,23 @@ class IntegrationWebsiteTest < Minitest::Test
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
+
|
38
|
+
def test_retry_client_closed_early
|
39
|
+
tcp_unexpected_exit do |port|
|
40
|
+
io = StringIO.new
|
41
|
+
driver = Rundoc::CodeCommand::Website::Driver.new(name: SecureRandom.hex, url: nil, read_timeout: 0.1, io: io)
|
42
|
+
assert_raises(Net::ReadTimeout) do
|
43
|
+
driver.visit("http://localhost:#{port}", max_attempts: 3, delay: 0)
|
44
|
+
end
|
45
|
+
|
46
|
+
logs = io.string
|
47
|
+
assert_logs_include(logs: logs, include_str: "Error visiting url (1/3)")
|
48
|
+
assert_logs_include(logs: logs, include_str: "Error visiting url (2/3)")
|
49
|
+
assert_logs_include(logs: logs, include_str: "Error visiting url (3/3)")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_logs_include(logs:, include_str:)
|
54
|
+
assert logs.include?(include_str), "Expected logs to include #{include_str} but they didnt. Logs:\n#{logs}"
|
55
|
+
end
|
37
56
|
end
|
data/test/test_helper.rb
CHANGED
@@ -9,6 +9,8 @@ require "rundoc"
|
|
9
9
|
require "minitest/autorun"
|
10
10
|
require "mocha/minitest"
|
11
11
|
require "tmpdir"
|
12
|
+
require "socket"
|
13
|
+
require "timeout"
|
12
14
|
|
13
15
|
class Minitest::Test
|
14
16
|
SUCCESS_DIRNAME = Rundoc::CLI::DEFAULTS::ON_SUCCESS_DIR
|
@@ -81,4 +83,30 @@ class Minitest::Test
|
|
81
83
|
|
82
84
|
attr_reader :value
|
83
85
|
end
|
86
|
+
|
87
|
+
# Yields port, exits unexpectedly
|
88
|
+
def tcp_unexpected_exit(timeout: 30)
|
89
|
+
Timeout.timeout(timeout) do
|
90
|
+
threads = []
|
91
|
+
server = TCPServer.new("127.0.0.1", 0)
|
92
|
+
port = server.addr[1]
|
93
|
+
threads << Thread.new do
|
94
|
+
# Accept one connection, then raise an error to simulate unexpected close
|
95
|
+
server.accept
|
96
|
+
raise "Unexpected server error!"
|
97
|
+
rescue
|
98
|
+
# Simulate crash, but let ensure run
|
99
|
+
end.tap { |t| t.abort_on_exception = false }
|
100
|
+
|
101
|
+
threads << Thread.new do
|
102
|
+
yield port
|
103
|
+
end
|
104
|
+
|
105
|
+
while threads.all?(&:alive?)
|
106
|
+
sleep 0.1
|
107
|
+
end
|
108
|
+
ensure
|
109
|
+
server.close if server && !server.closed?
|
110
|
+
end
|
111
|
+
end
|
84
112
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rundoc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1.
|
4
|
+
version: 4.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Schneeman
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: thor
|
@@ -288,7 +287,6 @@ homepage: https://github.com/schneems/rundoc
|
|
288
287
|
licenses:
|
289
288
|
- MIT
|
290
289
|
metadata: {}
|
291
|
-
post_install_message:
|
292
290
|
rdoc_options: []
|
293
291
|
require_paths:
|
294
292
|
- lib
|
@@ -303,8 +301,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
303
301
|
- !ruby/object:Gem::Version
|
304
302
|
version: '0'
|
305
303
|
requirements: []
|
306
|
-
rubygems_version: 3.
|
307
|
-
signing_key:
|
304
|
+
rubygems_version: 3.6.7
|
308
305
|
specification_version: 4
|
309
306
|
summary: RunDOC generates runable code from docs
|
310
307
|
test_files: []
|