Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flaky specs #2561

Merged
merged 7 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sentry-ruby/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ gem "yard", github: "lsegal/yard"
gem "webrick"
gem "faraday"
gem "excon"
gem "webmock"
1 change: 0 additions & 1 deletion sentry-ruby/lib/sentry/transport/http_transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class HTTPTransport < Transport
Zlib::BufError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
].freeze


def initialize(*args)
super
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}") if @dsn
Expand Down
12 changes: 9 additions & 3 deletions sentry-ruby/spec/contexts/with_request_mock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ def setsockopt(*args); end
allow(TCPSocket).to receive(:open).and_return(FakeSocket.new)
end

def stub_request(fake_response, &block)
around do |example|
WebMock.disable!
example.run
WebMock.enable!
end

def sentry_stub_request(fake_response, &block)
allow_any_instance_of(Net::HTTP).to receive(:connect)
allow_any_instance_of(Net::HTTP).to receive(:transport_request) do |http_obj, request|
block.call(request, http_obj) if block
Expand All @@ -32,10 +38,10 @@ def build_fake_response(status, body: {}, headers: {})

def stub_sentry_response
# use bad request as an example is easier for verifying with error messages
stub_request(build_fake_response("400", body: { data: "bad sentry DSN public key" }))
sentry_stub_request(build_fake_response("400", body: { data: "bad sentry DSN public key" }))
end

def stub_normal_response(code: "200", &block)
stub_request(build_fake_response(code), &block)
sentry_stub_request(build_fake_response(code), &block)
end
end
25 changes: 21 additions & 4 deletions sentry-ruby/spec/sentry/client/event_sending_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
config.transport.transport_class = Sentry::DummyTransport
end
end

before do
stub_request(:post, Sentry::TestHelper::DUMMY_DSN)
end

subject(:client) { Sentry::Client.new(configuration) }

let(:hub) do
Expand Down Expand Up @@ -470,13 +475,16 @@
before do
configuration.background_worker_threads = 0
Sentry.background_worker = Sentry::BackgroundWorker.new(configuration)

stub_request(:post, "http://sentry.localdomain/sentry/api/42/envelope/")
.to_raise(Timeout::Error)
end

it "swallows and logs Sentry::ExternalError (caused by transport's networking error)" do
expect(client.capture_event(event, scope)).to be_nil

expect(string_io.string).to match(/Event sending failed: Failed to open TCP connection/)
expect(string_io.string).to match(/Event capturing failed: Failed to open TCP connection/)
expect(string_io.string).to match(/Event sending failed: Exception from WebMock/)
expect(string_io.string).to match(/Event capturing failed: Exception from WebMock/)
end

it "swallows and logs errors caused by the user (like in before_send)" do
Expand All @@ -502,13 +510,16 @@
context "when sending events in background causes error", retry: 3 do
before do
Sentry.background_worker = Sentry::BackgroundWorker.new(configuration)

stub_request(:post, "http://sentry.localdomain/sentry/api/42/envelope/")
.to_raise(Timeout::Error)
end

it "swallows and logs Sentry::ExternalError (caused by transport's networking error)" do
expect(client.capture_event(event, scope)).to be_a(Sentry::ErrorEvent)
sleep(0.2)

expect(string_io.string).to match(/Event sending failed: Failed to open TCP connection/)
expect(string_io.string).to match(/Event sending failed: Exception from WebMock/)
end

it "swallows and logs errors caused by the user (like in before_send)" do
Expand Down Expand Up @@ -560,11 +571,14 @@
describe "#send_event" do
context "error happens when sending the event" do
it "raises the error" do
stub_request(:post, "http://sentry.localdomain/sentry/api/42/envelope/")
.to_raise(Timeout::Error)

expect do
client.send_event(event)
end.to raise_error(Sentry::ExternalError)

expect(string_io.string).to match(/Event sending failed: Failed to open TCP connection/)
expect(string_io.string).to match(/Event sending failed: Exception from WebMock/)
end
end

Expand Down Expand Up @@ -635,6 +649,9 @@
end

it "records lost span delta client reports" do
stub_request(:post, "http://sentry.localdomain/sentry/api/42/envelope/")
.to_raise(Timeout::Error)

expect { client.send_event(transaction_event) }.to raise_error(Sentry::ExternalError)
expect(client.transport).to have_recorded_lost_event(:before_send, 'span', num: 2)
end
Expand Down
2 changes: 1 addition & 1 deletion sentry-ruby/spec/sentry/faraday_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@

let(:url) { "http://example.com" }

it "skips instrumentation" do
it "skips instrumentation", webmock: false do
transaction = Sentry.start_transaction
Sentry.get_current_scope.set_span(transaction)

Expand Down
38 changes: 16 additions & 22 deletions sentry-ruby/spec/sentry/net/http_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,32 @@
::Logger.new(string_io)
end

context "with IPv6 addresses" do
context "with tracing enabled" do
before do
perform_basic_setup do |config|
config.traces_sample_rate = 1.0
config.transport.transport_class = Sentry::HTTPTransport
config.logger = logger
# the dsn needs to have a real host so we can make a real connection before sending a failed request
config.dsn = 'http://foobarbaz@o447951.ingest.sentry.io/5434472'
end
end

it "correctly parses the short-hand IPv6 addresses" do
stub_normal_response

transaction = Sentry.start_transaction
Sentry.get_current_scope.set_span(transaction)
context "with IPv6 addresses" do
it "correctly parses the short-hand IPv6 addresses" do
stub_normal_response

_ = Net::HTTP.get("::1", "/path", 8080)
transaction = Sentry.start_transaction
Sentry.get_current_scope.set_span(transaction)

expect(transaction.span_recorder.spans.count).to eq(2)
_ = Net::HTTP.get("::1", "/path", 8080)

request_span = transaction.span_recorder.spans.last
expect(request_span.data).to eq(
{ "url" => "http://[::1]/path", "http.request.method" => "GET", "http.response.status_code" => 200 }
)
end
end
expect(transaction.span_recorder.spans.count).to eq(2)

context "with tracing enabled" do
before do
perform_basic_setup do |config|
config.traces_sample_rate = 1.0
config.transport.transport_class = Sentry::HTTPTransport
config.logger = logger
# the dsn needs to have a real host so we can make a real connection before sending a failed request
config.dsn = 'http://foobarbaz@o447951.ingest.sentry.io/5434472'
request_span = transaction.span_recorder.spans.last
expect(request_span.data).to eq(
{ "url" => "http://[::1]/path", "http.request.method" => "GET", "http.response.status_code" => 200 }
)
end
end

Expand Down
1 change: 1 addition & 0 deletions sentry-ruby/spec/sentry/rspec/matchers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
config.dsn = 'https://2fb45f003d054a7ea47feb45898f7649@o447951.ingest.sentry.io/5434472'
config.enabled_environments = ["production"]
config.environment = :test
config.transport.transport_class = Sentry::DummyTransport
end

setup_sentry_test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@

describe "rate limit header processing" do
before do
stub_request(fake_response)
sentry_stub_request(fake_response)
end

shared_examples "rate limiting headers handling" do
Expand Down
38 changes: 19 additions & 19 deletions sentry-ruby/spec/sentry/transport/http_transport_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
subject { client.transport }

it "logs a debug message only during initialization" do
stub_request(build_fake_response("200"))
sentry_stub_request(build_fake_response("200"))
string_io = StringIO.new
configuration.logger = Logger.new(string_io)

Expand All @@ -39,7 +39,7 @@
end

it "initializes new Net::HTTP instance for every request" do
stub_request(build_fake_response("200")) do |request|
sentry_stub_request(build_fake_response("200")) do |request|
expect(request["User-Agent"]).to eq("sentry-ruby/#{Sentry::VERSION}")
end

Expand Down Expand Up @@ -87,7 +87,7 @@
let(:fake_response) { build_fake_response("200") }

it 'sets default User-Agent' do
stub_request(fake_response) do |request|
sentry_stub_request(fake_response) do |request|
expect(request["User-Agent"]).to eq("sentry-ruby/#{Sentry::VERSION}")
end

Expand All @@ -97,7 +97,7 @@
it "accepts custom proxy" do
configuration.transport.proxy = { uri: URI("https://example.com"), user: "stan", password: "foobar" }

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.proxy_address).to eq("example.com")
expect(http_obj.proxy_user).to eq("stan")
expect(http_obj.proxy_pass).to eq("foobar")
Expand All @@ -109,7 +109,7 @@
it "accepts a custom proxy string" do
configuration.transport.proxy = "https://stan:foobar@example.com:8080"

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.proxy_address).to eq("example.com")
expect(http_obj.proxy_user).to eq("stan")
expect(http_obj.proxy_pass).to eq("foobar")
Expand All @@ -122,7 +122,7 @@
it "accepts a custom proxy URI" do
configuration.transport.proxy = URI("https://stan:foobar@example.com:8080")

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.proxy_address).to eq("example.com")
expect(http_obj.proxy_user).to eq("stan")
expect(http_obj.proxy_pass).to eq("foobar")
Expand All @@ -136,7 +136,7 @@
begin
ENV["http_proxy"] = "https://stan:foobar@example.com:8080"

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.proxy_address).to eq("example.com")
expect(http_obj.proxy_port).to eq(8080)

Expand All @@ -155,7 +155,7 @@
it "accepts custom timeout" do
configuration.transport.timeout = 10

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.read_timeout).to eq(10)

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6")
Expand All @@ -169,7 +169,7 @@
it "accepts custom open_timeout" do
configuration.transport.open_timeout = 10

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.open_timeout).to eq(10)
end

Expand All @@ -178,7 +178,7 @@

describe "ssl configurations" do
it "has the corrent default" do
stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.verify_mode).to eq(1)
expect(http_obj.ca_file).to eq(nil)
end
Expand All @@ -189,7 +189,7 @@
it "accepts custom ssl_verification configuration" do
configuration.transport.ssl_verification = false

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.verify_mode).to eq(0)
expect(http_obj.ca_file).to eq(nil)
end
Expand All @@ -200,7 +200,7 @@
it "accepts custom ssl_ca_file configuration" do
configuration.transport.ssl_ca_file = "/tmp/foo"

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.verify_mode).to eq(1)
expect(http_obj.ca_file).to eq("/tmp/foo")
end
Expand All @@ -211,7 +211,7 @@
it "accepts custom ssl configuration" do
configuration.transport.ssl = { verify: false, ca_file: "/tmp/foo" }

stub_request(fake_response) do |_, http_obj|
sentry_stub_request(fake_response) do |_, http_obj|
expect(http_obj.verify_mode).to eq(0)
expect(http_obj.ca_file).to eq("/tmp/foo")
end
Expand All @@ -225,7 +225,7 @@
let(:fake_response) { build_fake_response("200") }

it "compresses data by default" do
stub_request(fake_response) do |request|
sentry_stub_request(fake_response) do |request|
expect(request["Content-Type"]).to eq("application/x-sentry-envelope")
expect(request["Content-Encoding"]).to eq("gzip")

Expand All @@ -238,7 +238,7 @@
end

it "doesn't compress small event" do
stub_request(fake_response) do |request|
sentry_stub_request(fake_response) do |request|
expect(request["Content-Type"]).to eq("application/x-sentry-envelope")
expect(request["Content-Encoding"]).to eq("")

Expand All @@ -255,7 +255,7 @@
it "doesn't compress data if the encoding is not gzip" do
configuration.transport.encoding = "json"

stub_request(fake_response) do |request|
sentry_stub_request(fake_response) do |request|
expect(request["Content-Type"]).to eq("application/x-sentry-envelope")
expect(request["Content-Encoding"]).to eq("")

Expand Down Expand Up @@ -296,7 +296,7 @@
let(:fake_response) { build_fake_response("404") }

it "raises an error" do
stub_request(fake_response)
sentry_stub_request(fake_response)

expect { subject.send_data(data) }.to raise_error(Sentry::ExternalError, /the server responded with status 404/)
end
Expand All @@ -306,7 +306,7 @@
let(:fake_response) { build_fake_response("500") }

it "raises an error" do
stub_request(fake_response)
sentry_stub_request(fake_response)

expect { subject.send_data(data) }.to raise_error(Sentry::ExternalError, /the server responded with status 500/)
end
Expand All @@ -318,7 +318,7 @@
end

it "raises an error with header" do
stub_request(error_response)
sentry_stub_request(error_response)

expect { subject.send_data(data) }.to raise_error(Sentry::ExternalError, /error_in_header/)
end
Expand Down
13 changes: 8 additions & 5 deletions sentry-ruby/spec/sentry/transport_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -531,11 +531,14 @@
end
end

context "with Faraday::Error" do
it "raises the error" do
expect do
subject.send_event(event)
end.to raise_error(Sentry::ExternalError)
context "with an HTTP exception" do
before do
stub_request(:post, "http://sentry.localdomain:80/sentry/api/42/envelope/").
to_raise(Timeout::Error)
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know what the intention of this spec was originally since Faraday was NOT configured here and the spec was passing by an accident since there was a socket timeout raised under the hood.

I can add a faraday spec too. Just lemme know if it'd make sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its a leftover from the legacy Raven SDK, ignore

rescue Faraday::Error => e

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we may just remove the entire test case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna leave it. It does verify specific error handling - HTTPTransport defines a list of exceptions that are rescued in send_data and now this spec checks that it works as expected that the exception is wrapped in Sentry::ExternalError.


it "raises Sentry::ExternalError" do
expect { subject.send_event(event) }.to raise_error(Sentry::ExternalError)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions sentry-ruby/spec/sentry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@
context "with spotlight" do
before { perform_basic_setup { |c| c.spotlight = true } }

it "sends the event to spotlight too" do
stub_request(build_fake_response("200")) do |request, http_obj|
it "sends the event to spotlight too", webmock: false do
sentry_stub_request(build_fake_response("200")) do |request, http_obj|
expect(request["Content-Type"]).to eq("application/x-sentry-envelope")
expect(request["Content-Encoding"]).to eq("gzip")
expect(http_obj.address).to eq("localhost")
Expand Down
Loading
Loading