airbrake-ruby 6.1.0-java → 6.1.1-java
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/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -1
- data/lib/airbrake-ruby/nested_exception.rb +10 -1
- data/lib/airbrake-ruby/notice.rb +5 -3
- data/lib/airbrake-ruby/version.rb +1 -1
- metadata +4 -122
- data/spec/airbrake_spec.rb +0 -522
- data/spec/async_sender_spec.rb +0 -65
- data/spec/backtrace_spec.rb +0 -430
- data/spec/benchmark_spec.rb +0 -35
- data/spec/code_hunk_spec.rb +0 -124
- data/spec/config/processor_spec.rb +0 -167
- data/spec/config/validator_spec.rb +0 -204
- data/spec/config_spec.rb +0 -188
- data/spec/context_spec.rb +0 -54
- data/spec/deploy_notifier_spec.rb +0 -50
- data/spec/file_cache_spec.rb +0 -35
- data/spec/filter_chain_spec.rb +0 -124
- data/spec/filters/context_filter_spec.rb +0 -32
- data/spec/filters/dependency_filter_spec.rb +0 -14
- data/spec/filters/exception_attributes_filter_spec.rb +0 -52
- data/spec/filters/gem_root_filter_spec.rb +0 -44
- data/spec/filters/git_last_checkout_filter_spec.rb +0 -61
- data/spec/filters/git_repository_filter_spec.rb +0 -72
- data/spec/filters/git_revision_filter_spec.rb +0 -126
- data/spec/filters/keys_allowlist_spec.rb +0 -204
- data/spec/filters/keys_blocklist_spec.rb +0 -242
- data/spec/filters/root_directory_filter_spec.rb +0 -39
- data/spec/filters/sql_filter_spec.rb +0 -274
- data/spec/filters/system_exit_filter_spec.rb +0 -16
- data/spec/filters/thread_filter_spec.rb +0 -281
- data/spec/fixtures/notroot.txt +0 -7
- data/spec/fixtures/project_root/code.rb +0 -221
- data/spec/fixtures/project_root/empty_file.rb +0 -0
- data/spec/fixtures/project_root/long_line.txt +0 -1
- data/spec/fixtures/project_root/short_file.rb +0 -3
- data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +0 -5
- data/spec/helpers.rb +0 -9
- data/spec/ignorable_spec.rb +0 -14
- data/spec/inspectable_spec.rb +0 -45
- data/spec/loggable_spec.rb +0 -17
- data/spec/monotonic_time_spec.rb +0 -25
- data/spec/nested_exception_spec.rb +0 -73
- data/spec/notice_notifier/options_spec.rb +0 -269
- data/spec/notice_notifier_spec.rb +0 -361
- data/spec/notice_spec.rb +0 -300
- data/spec/performance_breakdown_spec.rb +0 -11
- data/spec/performance_notifier_spec.rb +0 -645
- data/spec/promise_spec.rb +0 -203
- data/spec/query_spec.rb +0 -11
- data/spec/queue_spec.rb +0 -18
- data/spec/remote_settings/callback_spec.rb +0 -162
- data/spec/remote_settings/settings_data_spec.rb +0 -348
- data/spec/remote_settings_spec.rb +0 -201
- data/spec/request_spec.rb +0 -9
- data/spec/response_spec.rb +0 -110
- data/spec/spec_helper.rb +0 -100
- data/spec/stashable_spec.rb +0 -23
- data/spec/stat_spec.rb +0 -39
- data/spec/sync_sender_spec.rb +0 -168
- data/spec/tdigest_spec.rb +0 -235
- data/spec/thread_pool_spec.rb +0 -196
- data/spec/time_truncate_spec.rb +0 -30
- data/spec/timed_trace_spec.rb +0 -127
- data/spec/truncator_spec.rb +0 -267
data/spec/spec_helper.rb
DELETED
@@ -1,100 +0,0 @@
|
|
1
|
-
require 'simplecov'
|
2
|
-
SimpleCov.start if ENV['COVERAGE']
|
3
|
-
|
4
|
-
require 'airbrake-ruby'
|
5
|
-
|
6
|
-
require 'rspec/its'
|
7
|
-
|
8
|
-
require 'webmock'
|
9
|
-
require 'webmock/rspec'
|
10
|
-
require 'pry'
|
11
|
-
|
12
|
-
require 'pathname'
|
13
|
-
require 'webrick'
|
14
|
-
require 'English'
|
15
|
-
require 'base64'
|
16
|
-
require 'pp'
|
17
|
-
|
18
|
-
require 'helpers'
|
19
|
-
|
20
|
-
RSpec.configure do |c|
|
21
|
-
c.order = 'random'
|
22
|
-
c.color = true
|
23
|
-
c.disable_monkey_patching!
|
24
|
-
c.include Helpers
|
25
|
-
end
|
26
|
-
|
27
|
-
Thread.abort_on_exception = true
|
28
|
-
|
29
|
-
WebMock.disable_net_connect!(allow_localhost: true)
|
30
|
-
|
31
|
-
class AirbrakeTestError < RuntimeError
|
32
|
-
attr_reader :backtrace
|
33
|
-
|
34
|
-
def initialize(*)
|
35
|
-
super
|
36
|
-
# rubocop:disable Layout/LineLength
|
37
|
-
@backtrace = [
|
38
|
-
"/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
|
39
|
-
"/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
|
40
|
-
"/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
|
41
|
-
"/home/kyrylo/code/airbrake/ruby/spec/airbrake_spec.rb:1:in `<top (required)>'",
|
42
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'",
|
43
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `block in load_spec_files'",
|
44
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `each'",
|
45
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `load_spec_files'",
|
46
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:102:in `setup'",
|
47
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:88:in `run'",
|
48
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:73:in `run'",
|
49
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'",
|
50
|
-
"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'",
|
51
|
-
]
|
52
|
-
# rubocop:enable Layout/LineLength
|
53
|
-
end
|
54
|
-
|
55
|
-
# rubocop:disable Naming/AccessorMethodName
|
56
|
-
def set_backtrace(backtrace)
|
57
|
-
@backtrace = backtrace
|
58
|
-
end
|
59
|
-
# rubocop:enable Naming/AccessorMethodName
|
60
|
-
|
61
|
-
def message
|
62
|
-
'App crashed!'
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
class JavaAirbrakeTestError < AirbrakeTestError
|
67
|
-
def initialize(*)
|
68
|
-
super
|
69
|
-
# rubocop:disable Layout/LineLength
|
70
|
-
@backtrace = [
|
71
|
-
"org.jruby.java.invokers.InstanceMethodInvoker.call(InstanceMethodInvoker.java:26)",
|
72
|
-
"org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL(Interpreter.java:126)",
|
73
|
-
"org.jruby.RubyKernel$INVOKER$s$0$3$eval19.call(RubyKernel$INVOKER$s$0$3$eval19.gen)",
|
74
|
-
"org.jruby.RubyKernel$INVOKER$s$0$0$loop.call(RubyKernel$INVOKER$s$0$0$loop.gen)",
|
75
|
-
"org.jruby.runtime.IRBlockBody.doYield(IRBlockBody.java:139)",
|
76
|
-
"org.jruby.RubyKernel$INVOKER$s$rbCatch19.call(RubyKernel$INVOKER$s$rbCatch19.gen)",
|
77
|
-
"opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.invokeOther4:start(/opt/rubies/jruby-9.0.0.0/bin/irb)",
|
78
|
-
"opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.RUBY$script(/opt/rubies/jruby-9.0.0.0/bin/irb:13)",
|
79
|
-
"org.jruby.ir.Compiler$1.load(Compiler.java:111)",
|
80
|
-
"org.jruby.Main.run(Main.java:225)",
|
81
|
-
"org.jruby.Main.main(Main.java:197)",
|
82
|
-
]
|
83
|
-
# rubocop:enable Layout/LineLength
|
84
|
-
end
|
85
|
-
|
86
|
-
def is_a?(*)
|
87
|
-
true
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
class Ruby21Error < RuntimeError
|
92
|
-
attr_accessor :cause
|
93
|
-
|
94
|
-
def self.raise_error(msg)
|
95
|
-
ex = new(msg)
|
96
|
-
ex.cause = $ERROR_INFO
|
97
|
-
|
98
|
-
raise ex
|
99
|
-
end
|
100
|
-
end
|
data/spec/stashable_spec.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Stashable do
|
2
|
-
let(:klass) do
|
3
|
-
mod = described_class
|
4
|
-
Class.new { include(mod) }
|
5
|
-
end
|
6
|
-
|
7
|
-
describe "#stash" do
|
8
|
-
subject(:instance) { klass.new }
|
9
|
-
|
10
|
-
it "returns a hash" do
|
11
|
-
expect(instance.stash).to be_a(Hash)
|
12
|
-
end
|
13
|
-
|
14
|
-
it "returns an empty hash" do
|
15
|
-
expect(instance.stash).to be_empty
|
16
|
-
end
|
17
|
-
|
18
|
-
it "remembers what was put in the stash" do
|
19
|
-
instance.stash[:foo] = 1
|
20
|
-
expect(instance.stash[:foo]).to eq(1)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/spec/stat_spec.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Stat do
|
2
|
-
subject(:stat) { described_class.new }
|
3
|
-
|
4
|
-
describe "#to_h" do
|
5
|
-
it "converts to a hash" do
|
6
|
-
expect(stat.to_h).to eq(
|
7
|
-
'count' => 0,
|
8
|
-
'sum' => 0.0,
|
9
|
-
'sumsq' => 0.0,
|
10
|
-
'tdigest' => 'AAAAAkA0AAAAAAAAAAAAAA==',
|
11
|
-
)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
describe "#increment_ms" do
|
16
|
-
before { stat.increment_ms(1000) }
|
17
|
-
|
18
|
-
its(:sum) { is_expected.to eq(1000) }
|
19
|
-
its(:sumsq) { is_expected.to eq(1000000) }
|
20
|
-
|
21
|
-
it "updates tdigest" do
|
22
|
-
expect(stat.tdigest.size).to eq(1)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe "#inspect" do
|
27
|
-
it "provides custom inspect output" do
|
28
|
-
expect(stat.inspect).to eq(
|
29
|
-
'#<struct Airbrake::Stat count=0, sum=0.0, sumsq=0.0>',
|
30
|
-
)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe "#pretty_print" do
|
35
|
-
it "is an alias of #inspect" do
|
36
|
-
expect(stat.method(:pretty_print)).to eql(stat.method(:inspect))
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/spec/sync_sender_spec.rb
DELETED
@@ -1,168 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::SyncSender do
|
2
|
-
subject(:sync_sender) { described_class.new }
|
3
|
-
|
4
|
-
before do
|
5
|
-
Airbrake::Config.instance = Airbrake::Config.new(
|
6
|
-
project_id: 1, project_key: 'banana',
|
7
|
-
)
|
8
|
-
end
|
9
|
-
|
10
|
-
describe "#send" do
|
11
|
-
let(:promise) { Airbrake::Promise.new }
|
12
|
-
|
13
|
-
let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
|
14
|
-
let(:endpoint) { 'https://api.airbrake.io/api/v3/projects/1/notices' }
|
15
|
-
|
16
|
-
before { stub_request(:post, endpoint).to_return(body: '{}') }
|
17
|
-
|
18
|
-
it "sets the Content-Type header to JSON" do
|
19
|
-
sync_sender.send({}, promise)
|
20
|
-
expect(
|
21
|
-
a_request(:post, endpoint).with(
|
22
|
-
headers: { 'Content-Type' => 'application/json' },
|
23
|
-
),
|
24
|
-
).to have_been_made.once
|
25
|
-
end
|
26
|
-
|
27
|
-
it "sets the User-Agent header to the notifier slug" do
|
28
|
-
sync_sender.send({}, promise)
|
29
|
-
expect(
|
30
|
-
a_request(:post, endpoint).with(
|
31
|
-
headers: {
|
32
|
-
'User-Agent' => %r{
|
33
|
-
airbrake-ruby/\d+\.\d+\.\d+(\.rc\.\d+)?\sRuby/\d+\.\d+\.\d+
|
34
|
-
}x,
|
35
|
-
},
|
36
|
-
),
|
37
|
-
).to have_been_made.once
|
38
|
-
end
|
39
|
-
|
40
|
-
it "sets the Authorization header to the project key" do
|
41
|
-
sync_sender.send({}, promise)
|
42
|
-
expect(
|
43
|
-
a_request(:post, endpoint).with(
|
44
|
-
headers: { 'Authorization' => 'Bearer banana' },
|
45
|
-
),
|
46
|
-
).to have_been_made.once
|
47
|
-
end
|
48
|
-
|
49
|
-
it "catches exceptions raised while sending" do
|
50
|
-
# rubocop:disable RSpec/VerifiedDoubles
|
51
|
-
https = double("foo")
|
52
|
-
# rubocop:enable RSpec/VerifiedDoubles
|
53
|
-
|
54
|
-
# rubocop:disable RSpec/SubjectStub
|
55
|
-
allow(sync_sender).to receive(:build_https).and_return(https)
|
56
|
-
# rubocop:enable RSpec/SubjectStub
|
57
|
-
|
58
|
-
allow(https).to receive(:request).and_raise(StandardError.new('foo'))
|
59
|
-
|
60
|
-
expect(sync_sender.send({}, promise)).to be_an(Airbrake::Promise)
|
61
|
-
expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
|
62
|
-
end
|
63
|
-
|
64
|
-
it "logs exceptions raised while sending" do
|
65
|
-
allow(Airbrake::Loggable.instance).to receive(:error)
|
66
|
-
|
67
|
-
# rubocop:disable RSpec/VerifiedDoubles
|
68
|
-
https = double("foo")
|
69
|
-
# rubocop:enable RSpec/VerifiedDoubles
|
70
|
-
|
71
|
-
# rubocop:disable RSpec/SubjectStub
|
72
|
-
allow(sync_sender).to receive(:build_https).and_return(https)
|
73
|
-
# rubocop:enable RSpec/SubjectStub
|
74
|
-
|
75
|
-
allow(https).to receive(:request).and_raise(StandardError.new('foo'))
|
76
|
-
|
77
|
-
sync_sender.send({}, promise)
|
78
|
-
|
79
|
-
expect(Airbrake::Loggable.instance).to have_received(:error).with(
|
80
|
-
/HTTP error: foo/,
|
81
|
-
)
|
82
|
-
end
|
83
|
-
|
84
|
-
context "when request body is nil" do
|
85
|
-
# rubocop:disable RSpec/MultipleExpectations
|
86
|
-
it "doesn't send data" do
|
87
|
-
allow(Airbrake::Loggable.instance).to receive(:error)
|
88
|
-
|
89
|
-
allow_any_instance_of(Airbrake::Truncator)
|
90
|
-
.to receive(:reduce_max_size).and_return(0)
|
91
|
-
|
92
|
-
encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
|
93
|
-
bad_string = Base64.decode64(encoded)
|
94
|
-
|
95
|
-
ex = AirbrakeTestError.new
|
96
|
-
backtrace = []
|
97
|
-
10.times { backtrace << "bin/rails:3:in `<#{bad_string}>'" }
|
98
|
-
ex.set_backtrace(backtrace)
|
99
|
-
|
100
|
-
notice = Airbrake::Notice.new(ex)
|
101
|
-
|
102
|
-
expect(sync_sender.send(notice, promise)).to be_an(Airbrake::Promise)
|
103
|
-
expect(promise.value)
|
104
|
-
.to match('error' => '**Airbrake: data was not sent because of missing body')
|
105
|
-
|
106
|
-
expect(Airbrake::Loggable.instance).to have_received(:error).with(
|
107
|
-
/data was not sent/,
|
108
|
-
)
|
109
|
-
expect(Airbrake::Loggable.instance).to have_received(:error).with(
|
110
|
-
/truncation failed/,
|
111
|
-
)
|
112
|
-
end
|
113
|
-
# rubocop:enable RSpec/MultipleExpectations
|
114
|
-
end
|
115
|
-
|
116
|
-
context "when IP is rate limited" do
|
117
|
-
let(:endpoint) { %r{https://api.airbrake.io/api/v3/projects/1/notices} }
|
118
|
-
|
119
|
-
before do
|
120
|
-
stub_request(:post, endpoint).to_return(
|
121
|
-
status: 429,
|
122
|
-
body: '{"message":"IP is rate limited"}',
|
123
|
-
headers: { 'X-RateLimit-Delay' => '1' },
|
124
|
-
)
|
125
|
-
end
|
126
|
-
|
127
|
-
# rubocop:disable RSpec/MultipleExpectations
|
128
|
-
it "returns error" do
|
129
|
-
p1 = Airbrake::Promise.new
|
130
|
-
sync_sender.send({}, p1)
|
131
|
-
expect(p1.value).to match('error' => '**Airbrake: IP is rate limited')
|
132
|
-
|
133
|
-
p2 = Airbrake::Promise.new
|
134
|
-
sync_sender.send({}, p2)
|
135
|
-
expect(p2.value).to match('error' => '**Airbrake: IP is rate limited')
|
136
|
-
|
137
|
-
# Wait for X-RateLimit-Delay and then make a new request to make sure p2
|
138
|
-
# was ignored (no request made for it).
|
139
|
-
sleep 1
|
140
|
-
|
141
|
-
p3 = Airbrake::Promise.new
|
142
|
-
sync_sender.send({}, p3)
|
143
|
-
expect(p3.value).to match('error' => '**Airbrake: IP is rate limited')
|
144
|
-
|
145
|
-
expect(a_request(:post, endpoint)).to have_been_made.twice
|
146
|
-
end
|
147
|
-
# rubocop:enable RSpec/MultipleExpectations
|
148
|
-
end
|
149
|
-
|
150
|
-
context "when the provided method is :put" do
|
151
|
-
before { stub_request(:put, endpoint).to_return(status: 200, body: '') }
|
152
|
-
|
153
|
-
it "PUTs the request" do
|
154
|
-
sender = described_class.new(:put)
|
155
|
-
sender.send({}, promise)
|
156
|
-
expect(a_request(:put, endpoint)).to have_been_made
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
context "when the provided method is :post" do
|
161
|
-
it "POSTs the request" do
|
162
|
-
sender = described_class.new(:post)
|
163
|
-
sender.send({}, promise)
|
164
|
-
expect(a_request(:post, endpoint)).to have_been_made
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
data/spec/tdigest_spec.rb
DELETED
@@ -1,235 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::TDigest do
|
2
|
-
subject(:tdigest) { described_class.new }
|
3
|
-
|
4
|
-
describe "byte serialization" do
|
5
|
-
it "loads serialized data" do
|
6
|
-
tdigest.push(60, 100)
|
7
|
-
10.times { tdigest.push(rand * 100) }
|
8
|
-
bytes = tdigest.as_bytes
|
9
|
-
new_tdigest = described_class.from_bytes(bytes)
|
10
|
-
expect(new_tdigest.percentile(0.9)).to eq(tdigest.percentile(0.9))
|
11
|
-
expect(new_tdigest.as_bytes).to eq(bytes)
|
12
|
-
end
|
13
|
-
|
14
|
-
it "handles zero size" do
|
15
|
-
bytes = tdigest.as_bytes
|
16
|
-
expect(described_class.from_bytes(bytes).size).to be_zero
|
17
|
-
end
|
18
|
-
|
19
|
-
it "preserves compression" do
|
20
|
-
td = described_class.new(0.001)
|
21
|
-
bytes = td.as_bytes
|
22
|
-
new_tdigest = described_class.from_bytes(bytes)
|
23
|
-
expect(new_tdigest.compression).to eq(td.compression)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "small byte serialization" do
|
28
|
-
it "loads serialized data" do
|
29
|
-
10.times { tdigest.push(10) }
|
30
|
-
bytes = tdigest.as_small_bytes
|
31
|
-
new_tdigest = described_class.from_bytes(bytes)
|
32
|
-
# Expect some rounding error due to compression
|
33
|
-
expect(new_tdigest.percentile(0.9).round(5)).to eq(
|
34
|
-
tdigest.percentile(0.9).round(5),
|
35
|
-
)
|
36
|
-
expect(new_tdigest.as_small_bytes).to eq(bytes)
|
37
|
-
end
|
38
|
-
|
39
|
-
it "handles zero size" do
|
40
|
-
bytes = tdigest.as_small_bytes
|
41
|
-
expect(described_class.from_bytes(bytes).size).to be_zero
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe "JSON serialization" do
|
46
|
-
it "loads serialized data" do
|
47
|
-
tdigest.push(60, 100)
|
48
|
-
json = tdigest.as_json
|
49
|
-
new_tdigest = described_class.from_json(json)
|
50
|
-
expect(new_tdigest.percentile(0.9)).to eq(tdigest.percentile(0.9))
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
describe "#percentile" do
|
55
|
-
it "returns nil if empty" do
|
56
|
-
expect(tdigest.percentile(0.90)).to be_nil # This should not crash
|
57
|
-
end
|
58
|
-
|
59
|
-
it "raises ArgumentError of input not between 0 and 1" do
|
60
|
-
expect { tdigest.percentile(1.1) }.to raise_error(ArgumentError)
|
61
|
-
end
|
62
|
-
|
63
|
-
describe "with only single value" do
|
64
|
-
it "returns the value" do
|
65
|
-
tdigest.push(60, 100)
|
66
|
-
expect(tdigest.percentile(0.90)).to eq(60)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "returns 0 for all percentiles when only 0 present" do
|
70
|
-
tdigest.push(0)
|
71
|
-
expect(tdigest.percentile([0.0, 0.5, 1.0])).to eq([0, 0, 0])
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe "with alot of uniformly distributed points" do
|
76
|
-
it "has minimal error" do
|
77
|
-
seed = srand(1234) # Makes the values a proper fixture
|
78
|
-
maxerr = 0
|
79
|
-
values = Array.new(100_000).map { rand }
|
80
|
-
srand(seed)
|
81
|
-
|
82
|
-
tdigest.push(values)
|
83
|
-
tdigest.compress!
|
84
|
-
|
85
|
-
0.step(1, 0.1).each do |i|
|
86
|
-
q = tdigest.percentile(i)
|
87
|
-
maxerr = [maxerr, (i - q).abs].max
|
88
|
-
end
|
89
|
-
|
90
|
-
expect(maxerr).to be < 0.02
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
describe "#push" do
|
96
|
-
it "calls _cumulate so won't crash because of uninitialized mean_cumn" do
|
97
|
-
tdigest.push(
|
98
|
-
[
|
99
|
-
125000000.0,
|
100
|
-
104166666.66666666,
|
101
|
-
135416666.66666666,
|
102
|
-
104166666.66666666,
|
103
|
-
104166666.66666666,
|
104
|
-
93750000.0,
|
105
|
-
125000000.0,
|
106
|
-
62500000.0,
|
107
|
-
114583333.33333333,
|
108
|
-
156250000.0,
|
109
|
-
124909090.90909092,
|
110
|
-
104090909.0909091,
|
111
|
-
135318181.81818184,
|
112
|
-
104090909.0909091,
|
113
|
-
104090909.0909091,
|
114
|
-
93681818.18181819,
|
115
|
-
124909090.90909092,
|
116
|
-
62454545.45454546,
|
117
|
-
114500000.00000001,
|
118
|
-
156136363.63636366,
|
119
|
-
123567567.56756756,
|
120
|
-
102972972.97297296,
|
121
|
-
133864864.86486486,
|
122
|
-
102972972.97297296,
|
123
|
-
102972972.97297296,
|
124
|
-
92675675.67567568,
|
125
|
-
123567567.56756756,
|
126
|
-
61783783.78378378,
|
127
|
-
113270270.27027026,
|
128
|
-
154459459.45945945,
|
129
|
-
123829787.23404256,
|
130
|
-
103191489.36170213,
|
131
|
-
],
|
132
|
-
)
|
133
|
-
end
|
134
|
-
|
135
|
-
it "does not blow up if data comes in sorted" do
|
136
|
-
tdigest.push(0..10_000)
|
137
|
-
expect(tdigest.centroids.size).to be < 5_000
|
138
|
-
tdigest.compress!
|
139
|
-
expect(tdigest.centroids.size).to be < 1_000
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
describe "#size" do
|
144
|
-
it "reports the number of observations" do
|
145
|
-
n = 10_000
|
146
|
-
n.times { tdigest.push(rand) }
|
147
|
-
tdigest.compress!
|
148
|
-
expect(tdigest.size).to eq(n)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
describe "#+" do
|
153
|
-
it "works with empty tdigests" do
|
154
|
-
other = described_class.new(0.001, 50, 1.2)
|
155
|
-
expect((tdigest + other).centroids.size).to eq(0)
|
156
|
-
end
|
157
|
-
|
158
|
-
describe "adding two tdigests" do
|
159
|
-
let(:other) { described_class.new(0.001, 50, 1.2) }
|
160
|
-
|
161
|
-
before do
|
162
|
-
[tdigest, other].each do |td|
|
163
|
-
td.push(60, 100)
|
164
|
-
10.times { td.push(rand * 100) }
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# rubocop:disable RSpec/MultipleExpectations
|
169
|
-
it "has the parameters of the left argument (the calling tdigest)" do
|
170
|
-
new_tdigest = tdigest + other
|
171
|
-
expect(new_tdigest.instance_variable_get(:@delta)).to eq(
|
172
|
-
tdigest.instance_variable_get(:@delta),
|
173
|
-
)
|
174
|
-
expect(new_tdigest.instance_variable_get(:@k)).to eq(
|
175
|
-
tdigest.instance_variable_get(:@k),
|
176
|
-
)
|
177
|
-
expect(new_tdigest.instance_variable_get(:@cx)).to eq(
|
178
|
-
tdigest.instance_variable_get(:@cx),
|
179
|
-
)
|
180
|
-
end
|
181
|
-
# rubocop:enable RSpec/MultipleExpectations
|
182
|
-
|
183
|
-
it "returns a tdigest with less than or equal centroids" do
|
184
|
-
new_tdigest = tdigest + other
|
185
|
-
expect(new_tdigest.centroids.size)
|
186
|
-
.to be <= tdigest.centroids.size + other.centroids.size
|
187
|
-
end
|
188
|
-
|
189
|
-
it "has the size of the two digests combined" do
|
190
|
-
new_tdigest = tdigest + other
|
191
|
-
expect(new_tdigest.size).to eq(tdigest.size + other.size)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
describe "#merge!" do
|
197
|
-
it "works with empty tdigests" do
|
198
|
-
other = described_class.new(0.001, 50, 1.2)
|
199
|
-
tdigest.merge!(other)
|
200
|
-
expect(tdigest.centroids.size).to be_zero
|
201
|
-
end
|
202
|
-
|
203
|
-
describe "with populated tdigests" do
|
204
|
-
let(:other) { described_class.new(0.001, 50, 1.2) }
|
205
|
-
|
206
|
-
before do
|
207
|
-
[tdigest, other].each do |td|
|
208
|
-
td.push(60, 100)
|
209
|
-
10.times { td.push(rand * 100) }
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
it "has the parameters of the calling tdigest" do
|
214
|
-
vars = %i[@delta @k @cx]
|
215
|
-
expected = vars.map { |v| [v, tdigest.instance_variable_get(v)] }.to_h
|
216
|
-
tdigest.merge!(other)
|
217
|
-
vars.each do |v|
|
218
|
-
expect(tdigest.instance_variable_get(v)).to eq(expected[v])
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
it "returns a tdigest with less than or equal centroids" do
|
223
|
-
combined_size = tdigest.centroids.size + other.centroids.size
|
224
|
-
tdigest.merge!(other)
|
225
|
-
expect(tdigest.centroids.size).to be <= combined_size
|
226
|
-
end
|
227
|
-
|
228
|
-
it "has the size of the two digests combined" do
|
229
|
-
combined_size = tdigest.size + other.size
|
230
|
-
tdigest.merge!(other)
|
231
|
-
expect(tdigest.size).to eq(combined_size)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|