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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b65a04eca7d7099223b2a626c47eb78e14a8e102266cf880cddc747a0d9fc4a5
4
- data.tar.gz: 840585e8f0c6b3cddbb1d15354f4fdf862d2440b20054cd1b5344c026adb23d9
3
+ metadata.gz: 83fd435266e63543e96c3e24fc4d116cd043f2c89e396b8839d283eace07248a
4
+ data.tar.gz: 9818843a327f3ed3c4a46548db1dc8e9b2c706824716749d7a173563a752d6ae
5
5
  SHA512:
6
- metadata.gz: 99661d14543918c21a988bd1c4cffc1d5c9d73a852a95c16b0905b5ddde312dc986d970b4082921752cd2eac4d786a0ae37f42f3577fdea2df54c6de50376810
7
- data.tar.gz: 6819d3babc4cb2a7ad1f76386e3d7e7f28886a989e6a31ef052ea87b3b226a6979cc1c2092653fa7aadadd34d3f8af9b268464c2a14c70f385b559ee8a13ca9a
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: name: #{name}"
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
- @command = command
48
+ @original_command = command
49
49
  @timeout_value = timeout
50
- @log_reference = log # https://twitter.com/schneems/status/1285289971083907075
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 #{@command.shellescape} >> #{@log} #{out}"
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
- @driver = Capybara::Selenium::Driver.new(nil, browser: :chrome, options: browser_options)
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
- @session.visit(url)
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?
@@ -1,3 +1,3 @@
1
1
  module Rundoc
2
- VERSION = "4.1.3"
2
+ VERSION = "4.1.4"
3
3
  end
@@ -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 <<~'EOF'
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
@@ -88,6 +88,8 @@ class BackgroundTest < Minitest::Test
88
88
 
89
89
  run!("echo 'bar' >> #{file}")
90
90
 
91
+ background_start.background.wait("bar")
92
+
91
93
  log_read = Rundoc::CodeCommand::Background::Log::Read.new(name: "tail")
92
94
  output = log_read.call
93
95
 
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.3
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: 2024-12-16 00:00:00.000000000 Z
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.5.23
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: []