appsignal 3.0.15-java → 3.0.19-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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -3
  3. data/.semaphore/semaphore.yml +513 -104
  4. data/CHANGELOG.md +57 -0
  5. data/appsignal.gemspec +0 -2
  6. data/build_matrix.yml +45 -17
  7. data/ext/agent.yml +25 -25
  8. data/ext/appsignal_extension.c +201 -0
  9. data/ext/base.rb +2 -1
  10. data/gemfiles/rails-6.1.gemfile +7 -0
  11. data/gemfiles/rails-7.0.gemfile +7 -0
  12. data/lib/appsignal/cli/diagnose/utils.rb +0 -14
  13. data/lib/appsignal/cli/diagnose.rb +19 -8
  14. data/lib/appsignal/config.rb +89 -57
  15. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +24 -0
  16. data/lib/appsignal/extension/jruby.rb +147 -0
  17. data/lib/appsignal/extension.rb +5 -0
  18. data/lib/appsignal/integrations/sidekiq.rb +5 -1
  19. data/lib/appsignal/span.rb +92 -0
  20. data/lib/appsignal/system.rb +0 -4
  21. data/lib/appsignal/transaction.rb +12 -1
  22. data/lib/appsignal/version.rb +1 -1
  23. data/lib/appsignal.rb +4 -10
  24. data/script/lint_git +1 -1
  25. data/spec/lib/appsignal/cli/diagnose_spec.rb +18 -13
  26. data/spec/lib/appsignal/config_spec.rb +104 -20
  27. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +2 -2
  28. data/spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb +30 -0
  29. data/spec/lib/appsignal/event_formatter_spec.rb +2 -2
  30. data/spec/lib/appsignal/hooks/activejob_spec.rb +17 -6
  31. data/spec/lib/appsignal/hooks/sequel_spec.rb +1 -1
  32. data/spec/lib/appsignal/integrations/padrino_spec.rb +8 -2
  33. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +23 -5
  34. data/spec/lib/appsignal/span_spec.rb +141 -0
  35. data/spec/lib/appsignal/transaction_spec.rb +25 -0
  36. data/spec/lib/appsignal/utils/data_spec.rb +0 -2
  37. data/spec/lib/appsignal/utils/json_spec.rb +0 -2
  38. data/spec/lib/appsignal_spec.rb +2 -3
  39. data/spec/support/helpers/activejob_helpers.rb +27 -0
  40. data/spec/support/helpers/dependency_helper.rb +13 -1
  41. metadata +13 -3
@@ -251,7 +251,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
251
251
 
252
252
  it "adds the installation report to the diagnostics report" do
253
253
  run
254
- jruby = DependencyHelper.running_jruby?
254
+ jruby = Appsignal::System.jruby?
255
255
  expect(received_report["installation"]).to match(
256
256
  "result" => {
257
257
  "status" => "success"
@@ -321,7 +321,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
321
321
  expect(File).to receive(:read)
322
322
  .with(File.expand_path("../../../../../ext/install.report", __FILE__))
323
323
  .and_return(
324
- YAML.dump(
324
+ JSON.generate(
325
325
  "result" => {
326
326
  "status" => "error",
327
327
  "error" => "RuntimeError: some error",
@@ -384,8 +384,8 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
384
384
  end
385
385
  end
386
386
 
387
- context "when report is invalid YAML" do
388
- let(:raw_report) { "foo:\nbar" }
387
+ context "when report is invalid JSON" do
388
+ let(:raw_report) { "{}-" }
389
389
  before do
390
390
  allow(File).to receive(:read).and_call_original
391
391
  expect(File).to receive(:read)
@@ -750,13 +750,13 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
750
750
  end
751
751
 
752
752
  it "outputs a warning that no config is loaded" do
753
- expect(output).to include "Environment: \"\"\n#{warning_message}"
753
+ expect(output).to include "environment: \"\"\n#{warning_message}"
754
754
  expect(output).to_not have_color_markers
755
755
  end
756
756
 
757
757
  context "with color", :color => true do
758
758
  it "outputs a warning that no config is loaded in color" do
759
- expect(output).to include "Environment: \"\"\n"
759
+ expect(output).to include "environment: \"\"\n"
760
760
  expect(output).to have_colorized_text :red, warning_message
761
761
  end
762
762
  end
@@ -785,7 +785,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
785
785
  describe "environment" do
786
786
  it "outputs environment" do
787
787
  run
788
- expect(output).to include(%(Environment: "production"))
788
+ expect(output).to include(%(environment: "production"))
789
789
  end
790
790
 
791
791
  context "when the source is a single source" do
@@ -793,7 +793,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
793
793
 
794
794
  it "outputs the label source after the value" do
795
795
  expect(output).to include(
796
- %(Environment: "#{Appsignal.config.env}" (Loaded from: initial)\n)
796
+ %(environment: "#{Appsignal.config.env}" (Loaded from: initial)\n)
797
797
  )
798
798
  end
799
799
  end
@@ -810,7 +810,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
810
810
 
811
811
  it "outputs a list of sources with their values" do
812
812
  expect(output).to include(
813
- %( Environment: "production"\n) +
813
+ %( environment: "production"\n) +
814
814
  %( Sources:\n) +
815
815
  %( initial: "development"\n) +
816
816
  %( env: "production"\n)
@@ -904,7 +904,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
904
904
  before { run_within_dir tmp_dir }
905
905
 
906
906
  it "outputs environment" do
907
- expect(output).to include(%(Environment: "foobar"))
907
+ expect(output).to include(%(environment: "foobar"))
908
908
  end
909
909
 
910
910
  it "outputs config defaults" do
@@ -1007,7 +1007,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
1007
1007
 
1008
1008
  it "outputs failure with status code" do
1009
1009
  expect(output).to include "Validation",
1010
- "Validating Push API key: Failed with status 500\n" +
1010
+ "Validating Push API key: Failed to validate: status 500\n" +
1011
1011
  %("Could not confirm authorization: 500")
1012
1012
  end
1013
1013
 
@@ -1015,14 +1015,19 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
1015
1015
  it "outputs error in color" do
1016
1016
  expect(output).to include "Validation",
1017
1017
  "Validating Push API key: " +
1018
- colorize(%(Failed with status 500\n"Could not confirm authorization: 500"), :red)
1018
+ colorize(
1019
+ "Failed to validate: status 500\n" +
1020
+ %("Could not confirm authorization: 500"),
1021
+ :red
1022
+ )
1019
1023
  end
1020
1024
  end
1021
1025
 
1022
1026
  it "transmits validation in report" do
1023
1027
  expect(received_report).to include(
1024
1028
  "validation" => {
1025
- "push_api_key" => %(Failed with status 500\n\"Could not confirm authorization: 500")
1029
+ "push_api_key" => "Failed to validate: status 500\n" +
1030
+ %("Could not confirm authorization: 500")
1026
1031
  }
1027
1032
  )
1028
1033
  end
@@ -151,34 +151,34 @@ describe Appsignal::Config do
151
151
 
152
152
  it "merges with the default config" do
153
153
  expect(config.config_hash).to eq(
154
+ :active => true,
155
+ :ca_file_path => File.join(resources_dir, "cacert.pem"),
154
156
  :debug => false,
155
- :log => "file",
157
+ :dns_servers => [],
158
+ :enable_allocation_tracking => true,
159
+ :enable_gc_instrumentation => false,
160
+ :enable_host_metrics => true,
161
+ :enable_minutely_probes => true,
162
+ :enable_statsd => true,
163
+ :endpoint => "https://push.appsignal.com",
164
+ :files_world_accessible => true,
165
+ :filter_parameters => [],
166
+ :filter_session_data => [],
156
167
  :ignore_actions => [],
157
168
  :ignore_errors => [],
158
169
  :ignore_namespaces => [],
159
- :filter_parameters => [],
160
- :filter_session_data => [],
161
170
  :instrument_net_http => true,
162
171
  :instrument_redis => true,
163
172
  :instrument_sequel => true,
164
- :skip_session_data => false,
165
- :send_environment_metadata => true,
166
- :send_params => true,
167
- :endpoint => "https://push.appsignal.com",
168
- :push_api_key => "abc",
173
+ :log => "file",
169
174
  :name => "TestApp",
170
- :active => true,
171
- :enable_allocation_tracking => true,
172
- :enable_gc_instrumentation => false,
173
- :enable_host_metrics => true,
174
- :enable_minutely_probes => true,
175
- :enable_statsd => true,
176
- :ca_file_path => File.join(resources_dir, "cacert.pem"),
177
- :dns_servers => [],
178
- :files_world_accessible => true,
179
- :transaction_debug_mode => false,
175
+ :push_api_key => "abc",
176
+ :request_headers => [],
180
177
  :revision => "v2.5.1",
181
- :request_headers => []
178
+ :send_environment_metadata => true,
179
+ :send_params => true,
180
+ :skip_session_data => false,
181
+ :transaction_debug_mode => false
182
182
  )
183
183
  end
184
184
 
@@ -319,13 +319,15 @@ describe Appsignal::Config do
319
319
  "non-existing-path",
320
320
  "production",
321
321
  :running_in_container => true,
322
- :debug => true
322
+ :debug => true,
323
+ :log_level => "debug"
323
324
  )
324
325
  end
325
326
 
326
327
  it "overrides system detected and defaults config" do
327
328
  expect(config[:running_in_container]).to be_truthy
328
329
  expect(config[:debug]).to be_truthy
330
+ expect(config[:log_level]).to eq("debug")
329
331
  end
330
332
  end
331
333
 
@@ -502,6 +504,7 @@ describe Appsignal::Config do
502
504
  config[:log] = "stdout"
503
505
  config[:log_path] = "/tmp"
504
506
  config[:filter_parameters] = %w[password confirm_password]
507
+ config[:filter_session_data] = %w[key1 key2]
505
508
  config[:running_in_container] = false
506
509
  config[:dns_servers] = ["8.8.8.8", "8.8.4.4"]
507
510
  config[:transaction_debug_mode] = true
@@ -535,6 +538,8 @@ describe Appsignal::Config do
535
538
  expect(ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"]).to eq "true"
536
539
  expect(ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"]).to eq "true"
537
540
  expect(ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"]).to eq "false"
541
+ expect(ENV["_APPSIGNAL_FILTER_PARAMETERS"]).to eq "password,confirm_password"
542
+ expect(ENV["_APPSIGNAL_FILTER_SESSION_DATA"]).to eq "key1,key2"
538
543
  expect(ENV["_APP_REVISION"]).to eq "v2.5.1"
539
544
  expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIR_PATH")
540
545
  expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIRECTORY_PATH")
@@ -781,4 +786,83 @@ describe Appsignal::Config do
781
786
  end
782
787
  end
783
788
  end
789
+
790
+ describe "#log_level" do
791
+ let(:options) { {} }
792
+ let(:config) { described_class.new("", nil, options) }
793
+ subject { config.log_level }
794
+
795
+ context "without any config" do
796
+ it "returns info by default" do
797
+ is_expected.to eq(Logger::INFO)
798
+ end
799
+ end
800
+
801
+ context "with debug set to true" do
802
+ let(:options) { { :debug => true } }
803
+ it { is_expected.to eq(Logger::DEBUG) }
804
+ end
805
+
806
+ context "with transaction_debug_mode set to true" do
807
+ let(:options) { { :transaction_debug_mode => true } }
808
+ it { is_expected.to eq(Logger::DEBUG) }
809
+ end
810
+
811
+ context "with log_level set to error" do
812
+ let(:options) { { :log_level => "error" } }
813
+ it { is_expected.to eq(Logger::ERROR) }
814
+ end
815
+
816
+ context "with log_level set to warn" do
817
+ let(:options) { { :log_level => "warn" } }
818
+ it { is_expected.to eq(Logger::WARN) }
819
+ end
820
+
821
+ context "with log_level set to info" do
822
+ let(:options) { { :log_level => "info" } }
823
+ it { is_expected.to eq(Logger::INFO) }
824
+ end
825
+
826
+ context "with log_level set to debug" do
827
+ let(:options) { { :log_level => "debug" } }
828
+ it { is_expected.to eq(Logger::DEBUG) }
829
+ end
830
+
831
+ context "with log_level set to trace" do
832
+ let(:options) { { :log_level => "trace" } }
833
+ it { is_expected.to eq(Logger::DEBUG) }
834
+ end
835
+
836
+ context "with debug and log_level set" do
837
+ let(:options) { { :log_level => "error", :debug => true } }
838
+
839
+ it "the log_level option is leading" do
840
+ is_expected.to eq(Logger::ERROR)
841
+ end
842
+ end
843
+
844
+ context "with transaction_debug_mode and log_level set" do
845
+ let(:options) { { :log_level => "error", :transaction_debug_mode => true } }
846
+
847
+ it "the log_level option is leading" do
848
+ is_expected.to eq(Logger::ERROR)
849
+ end
850
+ end
851
+
852
+ context "with log level set to an unknown value" do
853
+ let(:options) { { :log_level => "fatal" } }
854
+
855
+ it "prints a warning and doesn't use the log_level" do
856
+ is_expected.to eql(Logger::INFO)
857
+ end
858
+
859
+ context "with debug option set to true" do
860
+ let(:options) { { :log_level => "fatal", :debug => true } }
861
+
862
+ it "prints a warning and sets it to debug" do
863
+ is_expected.to eql(Logger::DEBUG)
864
+ end
865
+ end
866
+ end
867
+ end
784
868
  end
@@ -1,5 +1,5 @@
1
- describe Appsignal::EventFormatter::ActiveRecord::InstantiationFormatter do
2
- let(:klass) { Appsignal::EventFormatter::ActiveRecord::SqlFormatter }
1
+ describe Appsignal::EventFormatter::ActiveRecord::SqlFormatter do
2
+ let(:klass) { described_class }
3
3
  let(:formatter) { klass.new }
4
4
 
5
5
  it "should register sql.active_record" do
@@ -0,0 +1,30 @@
1
+ describe Appsignal::EventFormatter::Sequel::SqlFormatter do
2
+ let(:klass) { described_class }
3
+ let(:formatter) { klass.new }
4
+
5
+ it "registers the sql.sequel event formatter" do
6
+ expect(Appsignal::EventFormatter.registered?("sql.sequel", klass)).to be_truthy
7
+ end
8
+
9
+ describe "#format" do
10
+ before do
11
+ stub_const(
12
+ "SequelDatabaseTypeClass",
13
+ Class.new do
14
+ def self.to_s
15
+ "SequelDatabaseTypeClassToString"
16
+ end
17
+ end
18
+ )
19
+ end
20
+ let(:payload) do
21
+ {
22
+ :name => SequelDatabaseTypeClass,
23
+ :sql => "SELECT * FROM users"
24
+ }
25
+ end
26
+ subject { formatter.format(payload) }
27
+
28
+ it { is_expected.to eq ["SequelDatabaseTypeClassToString", "SELECT * FROM users", 1] }
29
+ end
30
+ end
@@ -28,7 +28,7 @@ end
28
28
 
29
29
  class MockDependentFormatter < Appsignal::EventFormatter
30
30
  def initialize
31
- NonsenseDependency.something
31
+ raise "There is an error"
32
32
  end
33
33
 
34
34
  def format(_payload)
@@ -72,7 +72,7 @@ describe Appsignal::EventFormatter do
72
72
  end
73
73
  expect(klass.registered?("mock.dependent")).to be_falsy
74
74
  expect(logs).to contains_log :error, \
75
- "'uninitialized constant MockDependentFormatter::NonsenseDependency' " \
75
+ "'There is an error' " \
76
76
  "when initializing mock.dependent event formatter"
77
77
  end
78
78
  end
@@ -30,6 +30,7 @@ if DependencyHelper.active_job_present?
30
30
  end
31
31
 
32
32
  describe Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation do
33
+ include ActiveJobHelpers
33
34
  let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
34
35
  let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
35
36
  let(:queue) { "default" }
@@ -64,6 +65,13 @@ if DependencyHelper.active_job_present?
64
65
  parameterized_expected_args
65
66
  ]
66
67
  end
68
+ let(:expected_perform_events) do
69
+ if DependencyHelper.rails7_present?
70
+ ["perform.active_job", "perform_start.active_job"]
71
+ else
72
+ ["perform_start.active_job", "perform.active_job"]
73
+ end
74
+ end
67
75
  before do
68
76
  ActiveJob::Base.queue_adapter = :inline
69
77
 
@@ -119,7 +127,8 @@ if DependencyHelper.active_job_present?
119
127
  events = transaction_hash["events"]
120
128
  .sort_by { |e| e["start"] }
121
129
  .map { |event| event["name"] }
122
- expect(events).to eq(["perform_start.active_job", "perform.active_job"])
130
+
131
+ expect(events).to eq(expected_perform_events)
123
132
  end
124
133
 
125
134
  context "with custom queue" do
@@ -208,7 +217,8 @@ if DependencyHelper.active_job_present?
208
217
  events = transaction_hash["events"]
209
218
  .sort_by { |e| e["start"] }
210
219
  .map { |event| event["name"] }
211
- expect(events).to eq(["perform_start.active_job", "perform.active_job"])
220
+
221
+ expect(events).to eq(expected_perform_events)
212
222
  end
213
223
 
214
224
  if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
@@ -286,7 +296,8 @@ if DependencyHelper.active_job_present?
286
296
  .reject { |e| e["name"] == "enqueue.active_job" }
287
297
  .sort_by { |e| e["start"] }
288
298
  .map { |event| event["name"] }
289
- expect(events).to eq(["perform_start.active_job", "perform.active_job"])
299
+
300
+ expect(events).to eq(expected_perform_events)
290
301
  end
291
302
  end
292
303
 
@@ -414,7 +425,7 @@ if DependencyHelper.active_job_present?
414
425
  expect(transaction_hash).to include(
415
426
  "action" => "ActionMailerTestJob#welcome",
416
427
  "sample_data" => hash_including(
417
- "params" => ["ActionMailerTestJob", "welcome", "deliver_now"],
428
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + active_job_args_wrapper,
418
429
  "tags" => {
419
430
  "active_job_id" => kind_of(String),
420
431
  "queue" => "mailers"
@@ -433,7 +444,7 @@ if DependencyHelper.active_job_present?
433
444
  expect(transaction_hash).to include(
434
445
  "action" => "ActionMailerTestJob#welcome",
435
446
  "sample_data" => hash_including(
436
- "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + method_expected_args,
447
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + active_job_args_wrapper(:args => method_expected_args),
437
448
  "tags" => {
438
449
  "active_job_id" => kind_of(String),
439
450
  "queue" => "mailers"
@@ -453,7 +464,7 @@ if DependencyHelper.active_job_present?
453
464
  expect(transaction_hash).to include(
454
465
  "action" => "ActionMailerTestJob#welcome",
455
466
  "sample_data" => hash_including(
456
- "params" => ["ActionMailerTestJob", "welcome", "deliver_now", parameterized_expected_args],
467
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + active_job_args_wrapper(:params => parameterized_expected_args),
457
468
  "tags" => {
458
469
  "active_job_id" => kind_of(String),
459
470
  "queue" => "mailers"
@@ -1,7 +1,7 @@
1
1
  describe Appsignal::Hooks::SequelHook do
2
2
  if DependencyHelper.sequel_present?
3
3
  let(:db) do
4
- if Appsignal::System.jruby?
4
+ if DependencyHelper.running_jruby?
5
5
  Sequel.connect("jdbc:sqlite::memory:")
6
6
  else
7
7
  Sequel.sqlite
@@ -98,7 +98,13 @@ if DependencyHelper.padrino_present?
98
98
  RSpec::Matchers.define :match_response do |expected_status, expected_content|
99
99
  match do |response|
100
100
  status, _headers, content = response
101
- status == expected_status && content == [expected_content].compact
101
+ matches_content =
102
+ if expected_content.is_a?(Regexp)
103
+ content.join =~ expected_content
104
+ else
105
+ content == [expected_content].compact
106
+ end
107
+ status == expected_status && matches_content
102
108
  end
103
109
  end
104
110
 
@@ -142,7 +148,7 @@ if DependencyHelper.padrino_present?
142
148
  expect_a_transaction_to_be_created
143
149
  # Uses path for action name
144
150
  expect(transaction).to receive(:set_action_if_nil).with("PadrinoTestApp#unknown")
145
- expect(response).to match_response(404, "GET /404")
151
+ expect(response).to match_response(404, %r{^GET /404})
146
152
  end
147
153
  end
148
154
 
@@ -339,6 +339,7 @@ if DependencyHelper.active_job_present?
339
339
  require "sidekiq/testing"
340
340
 
341
341
  describe "Sidekiq ActiveJob integration" do
342
+ include ActiveJobHelpers
342
343
  let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
343
344
  let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
344
345
  let(:log) { StringIO.new }
@@ -366,6 +367,16 @@ if DependencyHelper.active_job_present?
366
367
  }
367
368
  ]
368
369
  end
370
+ let(:expected_wrapped_args) do
371
+ if (DependencyHelper.rails6_1_present? && DependencyHelper.ruby_3_1_or_newer?) || DependencyHelper.rails7_present?
372
+ [{
373
+ "_aj_ruby2_keywords" => ["args"],
374
+ "args" => expected_args
375
+ }]
376
+ else
377
+ expected_args
378
+ end
379
+ end
369
380
  let(:expected_tags) do
370
381
  {}.tap do |hash|
371
382
  hash["active_job_id"] = kind_of(String)
@@ -374,6 +385,13 @@ if DependencyHelper.active_job_present?
374
385
  end
375
386
  end
376
387
  end
388
+ let(:expected_perform_events) do
389
+ if DependencyHelper.rails7_present?
390
+ ["perform_job.sidekiq", "perform.active_job", "perform_start.active_job"]
391
+ else
392
+ ["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"]
393
+ end
394
+ end
377
395
  before do
378
396
  start_agent
379
397
  Appsignal.logger = test_logger(log)
@@ -434,8 +452,8 @@ if DependencyHelper.active_job_present?
434
452
  events = transaction_hash["events"]
435
453
  .sort_by { |e| e["start"] }
436
454
  .map { |event| event["name"] }
437
- expect(events)
438
- .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
455
+
456
+ expect(events).to eq(expected_perform_events)
439
457
  end
440
458
 
441
459
  context "with error" do
@@ -467,8 +485,8 @@ if DependencyHelper.active_job_present?
467
485
  events = transaction_hash["events"]
468
486
  .sort_by { |e| e["start"] }
469
487
  .map { |event| event["name"] }
470
- expect(events)
471
- .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
488
+
489
+ expect(events).to eq(expected_perform_events)
472
490
  end
473
491
  end
474
492
 
@@ -490,7 +508,7 @@ if DependencyHelper.active_job_present?
490
508
  expect(transaction_hash).to include(
491
509
  "action" => "ActionMailerSidekiqTestJob#welcome",
492
510
  "sample_data" => hash_including(
493
- "params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
511
+ "params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_wrapped_args
494
512
  )
495
513
  )
496
514
  end
@@ -0,0 +1,141 @@
1
+ require "appsignal/span"
2
+
3
+ describe Appsignal::Span do
4
+ before :context do
5
+ start_agent
6
+ end
7
+
8
+ let(:namespace) { "web" }
9
+ let(:root) { Appsignal::Span.new(namespace) }
10
+
11
+ describe "creating a span" do
12
+ it "creates an empty span" do
13
+ expect(root.to_h["namespace"]).to eq "web"
14
+ expect(root.to_h["trace_id"].length).to eq 16
15
+ expect(root.to_h["span_id"].length).to eq 8
16
+ expect(root.to_h["parent_span_id"]).to be_empty
17
+ expect(root.to_h["name"]).to be_empty
18
+ expect(root.to_h["start_time"]).to be > 1_600_000_000
19
+ expect(root.to_h["closed"]).to be false
20
+ end
21
+ end
22
+
23
+ describe "#child" do
24
+ let(:child) { root.child }
25
+
26
+ it "creates a child span" do
27
+ expect(child.to_h["namespace"]).to be_empty
28
+ expect(child.to_h["trace_id"].length).to eq 16
29
+ expect(child.to_h["span_id"].length).to eq 8
30
+ expect(child.to_h["parent_span_id"]).to eq root.to_h["span_id"]
31
+ expect(child.to_h["name"]).to be_empty
32
+ expect(child.to_h["start_time"]).to be > 1_600_000_000
33
+ expect(child.to_h["closed"]).to be false
34
+ end
35
+ end
36
+
37
+ describe "#add_error" do
38
+ it "adds an error" do
39
+ begin
40
+ raise "Error"
41
+ rescue => error
42
+ root.add_error(error)
43
+ end
44
+
45
+ error = root.to_h["error"]
46
+ expect(error["name"]).to eq "RuntimeError"
47
+ expect(error["message"]).to eq "Error"
48
+ expect(error["backtrace"]).not_to be_empty
49
+ end
50
+ end
51
+
52
+ describe "set_sample_data" do
53
+ it "sets sample data" do
54
+ root.set_sample_data(:params, "key" => "value")
55
+
56
+ sample_data = root.to_h["sample_data"]
57
+ expect(sample_data["params"]).to eq "{\"key\":\"value\"}"
58
+ end
59
+ end
60
+
61
+ describe "#name=" do
62
+ it "sets the name" do
63
+ root.name = "Span name"
64
+
65
+ expect(root.to_h["name"]).to eq "Span name"
66
+ end
67
+ end
68
+
69
+ describe "#[]=" do
70
+ let(:attributes) { root.to_h["attributes"] }
71
+
72
+ it "sets a string attribute" do
73
+ root["string"] = "attribute"
74
+
75
+ expect(attributes["string"]).to eq "attribute"
76
+ end
77
+
78
+ it "sets an integer attribute" do
79
+ root["integer"] = 1001
80
+
81
+ expect(attributes["integer"]).to eq 1001
82
+ end
83
+
84
+ it "sets a bigint attribute" do
85
+ root["bigint"] = 1 << 64
86
+
87
+ expect(attributes["bigint"]).to eq "bigint:#{1 << 64}"
88
+ end
89
+
90
+ it "sets a boolean attribute" do
91
+ root["true"] = true
92
+ root["false"] = false
93
+
94
+ expect(attributes["true"]).to eq true
95
+ expect(attributes["false"]).to eq false
96
+ end
97
+
98
+ it "sets a float attribute" do
99
+ root["float"] = 10.01
100
+
101
+ expect(attributes["float"]).to eq 10.01
102
+ end
103
+
104
+ it "raises an error for other types" do
105
+ expect do
106
+ root["something"] = Object.new
107
+ end.to raise_error TypeError
108
+ end
109
+ end
110
+
111
+ describe "#instrument" do
112
+ it "closes the span after yielding" do
113
+ root.instrument do
114
+ # Nothing happening
115
+ end
116
+ expect(root.closed?).to eq true
117
+ end
118
+
119
+ context "with an error raised in the passed block" do
120
+ it "closes the span after yielding" do
121
+ expect do
122
+ root.instrument do
123
+ raise ExampleException, "foo"
124
+ end
125
+ end.to raise_error(ExampleException, "foo")
126
+ expect(root.closed?).to eq true
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#close" do
132
+ it "closes a span" do
133
+ expect(root.closed?).to eq false
134
+
135
+ root.close
136
+
137
+ expect(root.to_h).to be_nil
138
+ expect(root.closed?).to eq true
139
+ end
140
+ end
141
+ end