Skip to content

Commit a133118

Browse files
committed
added more tests
1 parent e35ffdd commit a133118

5 files changed

Lines changed: 342 additions & 0 deletions

File tree

lib/log_struct/integrations/sorbet.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ module Sorbet
1717
def self.setup(config)
1818
return nil unless config.integrations.enable_sorbet_error_handlers
1919

20+
clear_sig_error_handler!
21+
install_error_handler!
22+
2023
# Install inline type error handler
2124
# Called when T.let, T.cast, T.must, etc. fail
2225
T::Configuration.inline_type_error_handler = lambda do |error, _opts|
@@ -44,6 +47,51 @@ def self.setup(config)
4447

4548
true
4649
end
50+
51+
@installed = T.let(false, T::Boolean)
52+
53+
class << self
54+
extend T::Sig
55+
56+
private
57+
58+
sig { void }
59+
def install_error_handler!
60+
return if installed?
61+
62+
T::Configuration.sig_builder_error_handler = lambda do |error, source|
63+
LogStruct.handle_exception(error, source: source, context: nil)
64+
end
65+
66+
@installed = true
67+
end
68+
69+
sig do
70+
returns(
71+
T.nilable(
72+
T.proc.params(error: StandardError, location: Thread::Backtrace::Location).void
73+
)
74+
)
75+
end
76+
def clear_sig_error_handler!
77+
previous_handler = T.cast(
78+
T::Configuration.instance_variable_get(:@sig_builder_error_handler),
79+
T.nilable(
80+
T.proc.params(error: StandardError, location: Thread::Backtrace::Location).void
81+
)
82+
)
83+
T::Configuration.sig_builder_error_handler = nil
84+
85+
@installed = false
86+
87+
previous_handler
88+
end
89+
90+
sig { returns(T::Boolean) }
91+
def installed?
92+
@installed
93+
end
94+
end
4795
end
4896
end
4997
end
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
class BootBufferTest < Minitest::Test
7+
def setup
8+
LogStruct::BootBuffer.clear
9+
end
10+
11+
def teardown
12+
LogStruct::BootBuffer.clear
13+
end
14+
15+
def test_add_and_flush_emits_logs
16+
log_entry = LogStruct::Log::Plain.new(
17+
message: "boot message",
18+
source: LogStruct::Source::App,
19+
level: LogStruct::Level::Info,
20+
timestamp: Time.now
21+
)
22+
23+
emitted = []
24+
25+
LogStruct.stub(:info, ->(log) { emitted << log }) do
26+
LogStruct::BootBuffer.add(log_entry)
27+
LogStruct::BootBuffer.flush
28+
end
29+
30+
assert_equal [log_entry], emitted
31+
end
32+
33+
def test_clear_discards_logs
34+
log_entry = LogStruct::Log::Plain.new(
35+
message: "discard me",
36+
source: LogStruct::Source::App,
37+
level: LogStruct::Level::Info,
38+
timestamp: Time.now
39+
)
40+
41+
LogStruct::BootBuffer.add(log_entry)
42+
LogStruct::BootBuffer.clear
43+
44+
LogStruct.stub(:info, ->(_log) { flunk("Boot buffer should be empty") }) do
45+
LogStruct::BootBuffer.flush
46+
end
47+
end
48+
end
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
class ErrorHandlingTest < Minitest::Test
7+
def setup
8+
@original_config = LogStruct.config
9+
LogStruct.configuration = LogStruct::Configuration.new
10+
end
11+
12+
def teardown
13+
LogStruct.configuration = @original_config
14+
end
15+
16+
def test_error_handling_mode_for_returns_standard_errors
17+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::Log
18+
mode = LogStruct.error_handling_mode_for(LogStruct::Source::App)
19+
20+
assert_equal LogStruct::ErrorHandlingMode::Log, mode
21+
end
22+
23+
def test_handle_exception_ignore
24+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::Ignore
25+
error = RuntimeError.new("ignore me")
26+
27+
LogStruct.stub(:error, proc { flunk("should not log in ignore mode") }) do
28+
LogStruct.handle_exception(error, source: LogStruct::Source::App)
29+
end
30+
end
31+
32+
def test_handle_exception_raise
33+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::Raise
34+
error = RuntimeError.new("boom")
35+
36+
assert_raises(RuntimeError) do
37+
LogStruct.handle_exception(error, source: LogStruct::Source::App)
38+
end
39+
end
40+
41+
def test_handle_exception_log
42+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::Log
43+
error = RuntimeError.new("log me")
44+
logged = []
45+
46+
LogStruct.stub(:error, ->(log) { logged << log }) do
47+
LogStruct.handle_exception(error, source: LogStruct::Source::App)
48+
end
49+
50+
assert_equal 1, logged.length
51+
assert_equal "log me", logged.first.message
52+
end
53+
54+
def test_handle_exception_report
55+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::Report
56+
error = RuntimeError.new("report me")
57+
logged = []
58+
reported = []
59+
60+
LogStruct.stub(:error, ->(log) { logged << log }) do
61+
LogStruct::MultiErrorReporter.stub(:report_error, ->(_err, _ctx) { reported << true }) do
62+
LogStruct.handle_exception(error, source: LogStruct::Source::App)
63+
end
64+
end
65+
66+
assert_equal 1, logged.length
67+
assert_equal [true], reported
68+
end
69+
70+
def test_handle_exception_log_production_raises_when_not_production
71+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::LogProduction
72+
error = RuntimeError.new("prod log")
73+
74+
LogStruct.stub(:is_production?, false) do
75+
assert_raises(RuntimeError) do
76+
LogStruct.handle_exception(error, source: LogStruct::Source::App)
77+
end
78+
end
79+
end
80+
81+
def test_handle_exception_log_production_logs_in_production
82+
LogStruct.config.error_handling_modes.standard_errors = LogStruct::ErrorHandlingMode::LogProduction
83+
error = RuntimeError.new("prod log")
84+
logged = []
85+
86+
LogStruct.stub(:is_production?, true) do
87+
LogStruct.stub(:error, ->(log) { logged << log }) do
88+
LogStruct.handle_exception(error, source: LogStruct::Source::App)
89+
end
90+
end
91+
92+
assert_equal 1, logged.length
93+
end
94+
end
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
class PumaIntegrationUnitTest < Minitest::Test
7+
PUMA = LogStruct::Integrations::Puma
8+
9+
def setup
10+
PUMA.send(:state_reset!)
11+
@original_argv = ARGV.dup
12+
end
13+
14+
def teardown
15+
PUMA.send(:state_reset!)
16+
ARGV.replace(@original_argv)
17+
end
18+
19+
def test_process_line_builds_started_log_from_boot_sequence
20+
started_logs = []
21+
22+
LogStruct.stub(:info, ->(log) { started_logs << log }) do
23+
feed_boot_lines
24+
end
25+
26+
started = started_logs.find { |log| log.is_a?(LogStruct::Log::Puma::Started) }
27+
28+
refute_nil started, "expected a started log"
29+
assert_equal "single", started.mode
30+
assert_equal "test", started.environment
31+
assert_equal ["http://127.0.0.1:3000"], started.listening_addresses
32+
assert_equal 5, started.min_threads
33+
assert_equal 7, started.max_threads
34+
end
35+
36+
def test_process_line_use_ctrl_c_fallback_infers_listening_address
37+
ARGV.replace(["server", "--port", "4000"])
38+
started_logs = []
39+
40+
LogStruct.stub(:info, ->(log) { started_logs << log }) do
41+
PUMA.process_line("Use Ctrl-C to stop")
42+
end
43+
44+
started = started_logs.find { |log| log.is_a?(LogStruct::Log::Puma::Started) }
45+
46+
refute_nil started
47+
assert_includes started.listening_addresses, "tcp://127.0.0.1:4000"
48+
end
49+
50+
def test_process_line_emits_shutdown
51+
shutdown_logs = []
52+
53+
LogStruct.stub(:info, ->(log) { shutdown_logs << log }) do
54+
PUMA.process_line("=> Booting Puma")
55+
PUMA.process_line("- Gracefully stopping")
56+
PUMA.process_line("- Goodbye!")
57+
end
58+
59+
shutdown = shutdown_logs.find { |log| log.is_a?(LogStruct::Log::Puma::Shutdown) }
60+
61+
refute_nil shutdown
62+
end
63+
64+
def test_process_line_returns_false_for_unhandled_input
65+
refute PUMA.process_line("unrecognized line")
66+
end
67+
68+
private
69+
70+
def feed_boot_lines
71+
lines = [
72+
"Puma starting in single mode...",
73+
"Puma version: 6.6.1 (\"Return to Forever\")",
74+
"* Ruby version: ruby 3.4.5 (revision)",
75+
"* Min threads: 5",
76+
"* Max threads: 7",
77+
"* Environment: test",
78+
"* PID: 12345",
79+
"* Listening on http://127.0.0.1:3000"
80+
]
81+
82+
lines.each { |line| PUMA.process_line(line) }
83+
end
84+
end
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
class SorbetIntegrationTest < Minitest::Test
7+
def setup
8+
LogStruct::Integrations::Sorbet.instance_variable_set(:@installed, false)
9+
end
10+
11+
def test_patch_clears_existing_sig_error_handler
12+
LogStruct::Integrations::Sorbet.stub(:clear_sig_error_handler!, -> { @cleared = true }) do
13+
LogStruct::Integrations::Sorbet.stub(:install_error_handler!, -> { @installed = true }) do
14+
LogStruct::Integrations::Sorbet.setup(LogStruct.config)
15+
end
16+
end
17+
18+
assert @cleared
19+
assert @installed
20+
end
21+
22+
def test_clear_sig_error_handler_when_defined
23+
handler = proc {}
24+
T::Configuration.sig_builder_error_handler = handler
25+
26+
LogStruct::Integrations::Sorbet.send(:clear_sig_error_handler!)
27+
28+
assert_nil current_sig_builder_error_handler
29+
ensure
30+
T::Configuration.sig_builder_error_handler = nil
31+
end
32+
33+
def test_clear_sig_error_handler_when_nil
34+
T::Configuration.sig_builder_error_handler = nil
35+
36+
assert_nil LogStruct::Integrations::Sorbet.send(:clear_sig_error_handler!)
37+
end
38+
39+
def test_install_error_handler_sets_handler_once
40+
called = []
41+
LogStruct::Integrations::Sorbet.send(:clear_sig_error_handler!)
42+
43+
T::Configuration.sig_builder_error_handler = nil
44+
45+
LogStruct.stub(
46+
:handle_exception,
47+
->(error, source:, context: nil) { called << [error.class, source] }
48+
) do
49+
LogStruct::Integrations::Sorbet.send(:install_error_handler!)
50+
51+
handler = current_sig_builder_error_handler
52+
raise "handler not installed" unless handler
53+
54+
handler.call(StandardError.new("boom"), :example)
55+
end
56+
57+
assert_equal [[StandardError, :example]], called
58+
ensure
59+
T::Configuration.sig_builder_error_handler = nil
60+
LogStruct::Integrations::Sorbet.instance_variable_set(:@installed, false)
61+
end
62+
63+
private
64+
65+
def current_sig_builder_error_handler
66+
T::Configuration.instance_variable_get(:@sig_builder_error_handler)
67+
end
68+
end

0 commit comments

Comments
 (0)