Skip to content

Commit e49df43

Browse files
committed
fixed test coverage and updated commands
1 parent fcc20ba commit e49df43

7 files changed

Lines changed: 253 additions & 18 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
fi
8585
8686
- name: RuboCop (Linting/Formatting)
87-
run: scripts/rubocop.rb
87+
run: bin/rubocop
8888

8989
- name: Sorbet (Typecheck)
9090
run: scripts/typecheck.sh

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232

3333
- Ruby typecheck: `scripts/typecheck.sh`
3434
- Next.js typecheck: `cd site && pnpm exec tsc --noEmit`
35-
- Lint Ruby: `scripts/rubocop.rb`
36-
- Format Ruby: `scripts/rubocop.rb -A`
35+
- Lint Ruby: `bin/rubocop`
36+
- Format Ruby: `bin/rubocop -A`
3737
- Format JS/TS/JSON: `scripts/prettier.sh --write`
3838
- Lint JS/TS/JSON: `scripts/prettier.sh --check`
3939
- Spellcheck: `scripts/spellcheck.sh`

Taskfile.ruby.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ tasks:
1414
lint:
1515
desc: Run RuboCop
1616
cmds:
17-
- bundle exec ruby scripts/rubocop.rb
17+
- bundle exec ruby bin/rubocop
1818

1919
lint:fix:
2020
desc: Auto-correct RuboCop offenses
2121
cmds:
22-
- bundle exec ruby scripts/rubocop.rb -A
22+
- bundle exec ruby bin/rubocop -A
2323

2424
check-generated:
2525
desc: Ensure generated Ruby structs are up to date (CI safe)

bin/rubocop

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env ruby
2+
# typed: true
23
# frozen_string_literal: true
34

45
#
@@ -24,4 +25,21 @@ end
2425
require "rubygems"
2526
require "bundler/setup"
2627

28+
# In restricted environments, RuboCop cache writes may fail. Disable or redirect cache.
29+
begin
30+
cache_root = File.expand_path("../tmp/rubocop_cache", __dir__)
31+
Dir.mkdir(File.expand_path("../tmp", __dir__)) unless Dir.exist?(File.expand_path("../tmp", __dir__))
32+
Dir.mkdir(cache_root) unless Dir.exist?(cache_root)
33+
rescue
34+
cache_root = nil
35+
end
36+
37+
unless ARGV.any? { |a| a == "--no-cache" || a.start_with?("--cache") }
38+
if cache_root
39+
ARGV.unshift("--cache-root", cache_root)
40+
else
41+
ARGV.unshift("--no-cache")
42+
end
43+
end
44+
2745
load Gem.bin_path("rubocop", "rubocop")

lefthook.yml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,5 @@ pre-commit:
8282

8383
- name: rubocop
8484
glob: '*.rb'
85-
run: scripts/rubocop.rb -A {staged_files}
85+
run: bin/rubocop -A {staged_files}
8686
stage_fixed: true
87-
88-
# Optional: Run full test suite manually with lefthook run full-tests
89-
full-tests:
90-
parallel: false
91-
92-
# Run complete test suite including Rails integration tests
93-
all-tests:
94-
run: scripts/all_tests.sh
95-
fail_text: 'Full test suite failed'

lib/log_struct/rails_boot_banner_silencer.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ def self.install!
1818
patch!
1919
end
2020

21-
sig { void }
21+
sig { returns(T::Boolean) }
2222
def self.patch!
2323
begin
2424
require "rails/command"
2525
require "rails/commands/server/server_command"
2626
rescue LoadError
2727
# Best-effort – if Rails isn't available yet we'll try again later
28-
return
28+
return false
2929
end
3030

3131
server_command = T.let(nil, T.untyped)
@@ -36,9 +36,10 @@ def self.patch!
3636
server_command = nil
3737
end
3838
# rubocop:enable Sorbet/ConstantsFromStrings
39-
return unless server_command
39+
return false unless server_command
4040

4141
patch_server_command(server_command)
42+
true
4243
end
4344

4445
sig { params(server_command: T.untyped).void }
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
class RailsBootBannerSilencerTest < Minitest::Test
7+
SILENCER = LogStruct::RailsBootBannerSilencer
8+
SERVER_MODULE = LogStruct::RailsBootBannerSilencer::ServerCommandSilencer
9+
10+
def setup
11+
@original_argv = ARGV.dup
12+
@installed_flag = SILENCER.instance_variable_get(:@installed)
13+
SILENCER.instance_variable_set(:@installed, false)
14+
store_server_mode
15+
end
16+
17+
def teardown
18+
ARGV.replace(@original_argv)
19+
SILENCER.instance_variable_set(:@installed, @installed_flag)
20+
restore_server_mode
21+
end
22+
23+
def test_install_skips_patch_when_not_running_server
24+
ARGV.replace(["console"])
25+
26+
SILENCER.stub(:patch!, proc { flunk("patch! should not run without server arg") }) do
27+
SILENCER.install!
28+
end
29+
end
30+
31+
def test_patch_handles_missing_rails_gracefully
32+
with_kernel_require_raising_load_error do
33+
SILENCER.instance_variable_set(:@installed, false)
34+
ARGV.replace(["server"])
35+
36+
refute(SILENCER.patch!)
37+
end
38+
end
39+
40+
def test_patch_patches_when_server_command_is_available
41+
stub_class = build_server_command_stub
42+
ARGV.replace(["server"])
43+
44+
stub_const_get(stub_class) do
45+
assert(SILENCER.patch!)
46+
end
47+
48+
assert_includes(stub_class.ancestors, SERVER_MODULE)
49+
end
50+
51+
def test_patch_server_command_is_idempotent
52+
stub_class = build_server_command_stub
53+
SILENCER.patch_server_command(stub_class)
54+
first_count = count_silencer_modules(stub_class)
55+
SILENCER.patch_server_command(stub_class)
56+
57+
assert_equal(first_count, count_silencer_modules(stub_class))
58+
end
59+
60+
def test_perform_marks_server_mode_and_delegates
61+
stub_class = build_server_command_stub
62+
SILENCER.patch_server_command(stub_class)
63+
64+
instance = stub_class.new
65+
result = instance.perform(:foo, :bar)
66+
67+
assert_equal(:original_perform, result)
68+
assert_equal([:foo, :bar], instance.performed_args)
69+
assert(LogStruct.instance_variable_get(:@server_mode))
70+
end
71+
72+
def test_print_boot_information_consumes_banner_lines
73+
stub_class = build_server_command_stub
74+
SILENCER.patch_server_command(stub_class)
75+
instance = stub_class.new
76+
77+
processed = []
78+
boot_called = false
79+
80+
LogStruct::Integrations::Puma.stub(:emit_boot_if_needed!, proc { boot_called = true }) do
81+
LogStruct::Integrations::Puma.stub(:process_line, ->(line) { processed << line }) do
82+
Rails.stub(:version, "8.0.1") do
83+
Rails.stub(:env, "test") do
84+
instance.print_boot_information("Stub::Server", "http://localhost:3000")
85+
end
86+
end
87+
end
88+
end
89+
90+
assert(boot_called)
91+
assert_includes(processed, "=> Booting Server")
92+
assert_includes(processed, "=> Rails 8.0.1 application starting in test http://localhost:3000")
93+
assert_includes(processed, "=> Run `task-cli --help` for more startup options")
94+
end
95+
96+
def test_print_boot_information_uses_fallbacks
97+
stub_class = build_server_command_stub(executable: proc { raise "missing" })
98+
SILENCER.patch_server_command(stub_class)
99+
instance = stub_class.new
100+
processed = []
101+
102+
LogStruct::Integrations::Puma.stub(:emit_boot_if_needed!, proc {}) do
103+
LogStruct::Integrations::Puma.stub(:process_line, ->(line) { processed << line }) do
104+
ActiveSupport::Inflector.stub(:demodulize, proc { raise "boom" }) do
105+
Rails.stub(:version, proc { raise "no version" }) do
106+
instance.print_boot_information(Object.new, nil)
107+
end
108+
end
109+
end
110+
end
111+
112+
assert_includes(processed, "=> Booting Puma")
113+
assert_includes(processed, "=> Rails application starting")
114+
assert_includes(processed, "=> Run `rails --help` for more startup options")
115+
end
116+
117+
def test_lookup_executable_returns_default_when_missing
118+
klass = Class.new do
119+
prepend SERVER_MODULE
120+
121+
def perform(*)
122+
end
123+
124+
def print_boot_information(_server, _url)
125+
end
126+
end
127+
128+
value = klass.new.send(:lookup_executable)
129+
130+
assert_equal("rails", value)
131+
end
132+
133+
private
134+
135+
def store_server_mode
136+
@had_server_mode = LogStruct.instance_variable_defined?(:@server_mode)
137+
@stored_server_mode = LogStruct.instance_variable_get(:@server_mode) if @had_server_mode
138+
LogStruct.instance_variable_set(:@server_mode, false)
139+
end
140+
141+
def restore_server_mode
142+
if @had_server_mode
143+
LogStruct.instance_variable_set(:@server_mode, @stored_server_mode)
144+
elsif LogStruct.instance_variable_defined?(:@server_mode)
145+
LogStruct.remove_instance_variable(:@server_mode)
146+
end
147+
end
148+
149+
def build_server_command_stub(executable: "task-cli")
150+
Class.new do
151+
class << self
152+
attr_reader :prepended_modules
153+
154+
def prepend(mod)
155+
(@prepended_modules ||= []) << mod
156+
super
157+
end
158+
end
159+
160+
attr_reader :performed_args
161+
162+
define_method(:perform) do |*args|
163+
@performed_args = args
164+
:original_perform
165+
end
166+
167+
define_method(:print_boot_information) do |_server, _url|
168+
:original_print
169+
end
170+
171+
define_method(:executable) do
172+
executable.is_a?(Proc) ? executable.call : executable
173+
end
174+
end
175+
end
176+
177+
def stub_const_get(stub_class)
178+
original_const_get = Object.method(:const_get)
179+
180+
Object.stub(:const_get,
181+
->(name, inherit = true) do
182+
if name == "Rails::Command::ServerCommand"
183+
stub_class
184+
else
185+
original_const_get.call(name, inherit)
186+
end
187+
end) do
188+
yield
189+
end
190+
end
191+
192+
def count_silencer_modules(stub_class)
193+
stub_class.singleton_class.ancestors.count { |ancestor| ancestor == SERVER_MODULE }
194+
end
195+
196+
def with_kernel_require_raising_load_error
197+
kernel_singleton = Kernel.singleton_class
198+
kernel_module = Kernel
199+
200+
kernel_module.class_eval do
201+
alias_method :__logstruct_original_instance_require, :require
202+
define_method(:require) do |*_args|
203+
raise LoadError, "missing"
204+
end
205+
end
206+
kernel_singleton.class_eval do
207+
alias_method :__logstruct_original_module_require, :require
208+
define_method(:require) do |*_args|
209+
raise LoadError, "missing"
210+
end
211+
end
212+
yield
213+
ensure
214+
kernel_module.class_eval do
215+
remove_method :require
216+
alias_method :require, :__logstruct_original_instance_require
217+
remove_method :__logstruct_original_instance_require
218+
end
219+
kernel_singleton.class_eval do
220+
remove_method :require
221+
alias_method :require, :__logstruct_original_module_require
222+
remove_method :__logstruct_original_module_require
223+
end
224+
end
225+
end

0 commit comments

Comments
 (0)