appsignal 2.5.0.alpha.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +66 -0
  5. data/.rubocop_todo.yml +124 -0
  6. data/.travis.yml +72 -0
  7. data/.yardopts +8 -0
  8. data/CHANGELOG.md +639 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +20 -0
  11. data/README.md +264 -0
  12. data/Rakefile +214 -0
  13. data/appsignal.gemspec +42 -0
  14. data/benchmark.rake +77 -0
  15. data/bin/appsignal +13 -0
  16. data/ext/Rakefile +27 -0
  17. data/ext/agent.yml +64 -0
  18. data/ext/appsignal_extension.c +692 -0
  19. data/ext/base.rb +79 -0
  20. data/ext/extconf.rb +35 -0
  21. data/gemfiles/capistrano2.gemfile +7 -0
  22. data/gemfiles/capistrano3.gemfile +7 -0
  23. data/gemfiles/grape.gemfile +7 -0
  24. data/gemfiles/no_dependencies.gemfile +5 -0
  25. data/gemfiles/padrino.gemfile +7 -0
  26. data/gemfiles/que.gemfile +5 -0
  27. data/gemfiles/rails-3.2.gemfile +6 -0
  28. data/gemfiles/rails-4.0.gemfile +6 -0
  29. data/gemfiles/rails-4.1.gemfile +6 -0
  30. data/gemfiles/rails-4.2.gemfile +10 -0
  31. data/gemfiles/rails-5.0.gemfile +5 -0
  32. data/gemfiles/rails-5.1.gemfile +5 -0
  33. data/gemfiles/resque.gemfile +12 -0
  34. data/gemfiles/sequel-435.gemfile +11 -0
  35. data/gemfiles/sequel.gemfile +11 -0
  36. data/gemfiles/sinatra.gemfile +6 -0
  37. data/gemfiles/webmachine.gemfile +5 -0
  38. data/lib/appsignal.rb +804 -0
  39. data/lib/appsignal/auth_check.rb +65 -0
  40. data/lib/appsignal/capistrano.rb +10 -0
  41. data/lib/appsignal/cli.rb +108 -0
  42. data/lib/appsignal/cli/demo.rb +63 -0
  43. data/lib/appsignal/cli/diagnose.rb +500 -0
  44. data/lib/appsignal/cli/helpers.rb +72 -0
  45. data/lib/appsignal/cli/install.rb +277 -0
  46. data/lib/appsignal/cli/notify_of_deploy.rb +113 -0
  47. data/lib/appsignal/config.rb +287 -0
  48. data/lib/appsignal/demo.rb +107 -0
  49. data/lib/appsignal/event_formatter.rb +74 -0
  50. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +24 -0
  51. data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +14 -0
  52. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +14 -0
  53. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +32 -0
  54. data/lib/appsignal/event_formatter/faraday/request_formatter.rb +19 -0
  55. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +89 -0
  56. data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
  57. data/lib/appsignal/extension.rb +63 -0
  58. data/lib/appsignal/extension/jruby.rb +460 -0
  59. data/lib/appsignal/garbage_collection_profiler.rb +48 -0
  60. data/lib/appsignal/hooks.rb +105 -0
  61. data/lib/appsignal/hooks/action_cable.rb +113 -0
  62. data/lib/appsignal/hooks/active_support_notifications.rb +52 -0
  63. data/lib/appsignal/hooks/celluloid.rb +30 -0
  64. data/lib/appsignal/hooks/data_mapper.rb +18 -0
  65. data/lib/appsignal/hooks/delayed_job.rb +19 -0
  66. data/lib/appsignal/hooks/mongo_ruby_driver.rb +21 -0
  67. data/lib/appsignal/hooks/net_http.rb +29 -0
  68. data/lib/appsignal/hooks/passenger.rb +22 -0
  69. data/lib/appsignal/hooks/puma.rb +35 -0
  70. data/lib/appsignal/hooks/que.rb +21 -0
  71. data/lib/appsignal/hooks/rake.rb +39 -0
  72. data/lib/appsignal/hooks/redis.rb +30 -0
  73. data/lib/appsignal/hooks/sequel.rb +60 -0
  74. data/lib/appsignal/hooks/shoryuken.rb +43 -0
  75. data/lib/appsignal/hooks/sidekiq.rb +144 -0
  76. data/lib/appsignal/hooks/unicorn.rb +40 -0
  77. data/lib/appsignal/hooks/webmachine.rb +23 -0
  78. data/lib/appsignal/integrations/capistrano/appsignal.cap +39 -0
  79. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +52 -0
  80. data/lib/appsignal/integrations/data_mapper.rb +33 -0
  81. data/lib/appsignal/integrations/delayed_job_plugin.rb +54 -0
  82. data/lib/appsignal/integrations/grape.rb +53 -0
  83. data/lib/appsignal/integrations/mongo_ruby_driver.rb +55 -0
  84. data/lib/appsignal/integrations/object.rb +35 -0
  85. data/lib/appsignal/integrations/padrino.rb +84 -0
  86. data/lib/appsignal/integrations/que.rb +43 -0
  87. data/lib/appsignal/integrations/railtie.rb +41 -0
  88. data/lib/appsignal/integrations/rake.rb +2 -0
  89. data/lib/appsignal/integrations/resque.rb +20 -0
  90. data/lib/appsignal/integrations/resque_active_job.rb +30 -0
  91. data/lib/appsignal/integrations/sinatra.rb +17 -0
  92. data/lib/appsignal/integrations/webmachine.rb +38 -0
  93. data/lib/appsignal/js_exception_transaction.rb +54 -0
  94. data/lib/appsignal/marker.rb +63 -0
  95. data/lib/appsignal/minutely.rb +42 -0
  96. data/lib/appsignal/rack/generic_instrumentation.rb +49 -0
  97. data/lib/appsignal/rack/js_exception_catcher.rb +70 -0
  98. data/lib/appsignal/rack/rails_instrumentation.rb +51 -0
  99. data/lib/appsignal/rack/sinatra_instrumentation.rb +99 -0
  100. data/lib/appsignal/rack/streaming_listener.rb +73 -0
  101. data/lib/appsignal/system.rb +81 -0
  102. data/lib/appsignal/transaction.rb +498 -0
  103. data/lib/appsignal/transmitter.rb +107 -0
  104. data/lib/appsignal/utils.rb +127 -0
  105. data/lib/appsignal/utils/params_sanitizer.rb +59 -0
  106. data/lib/appsignal/utils/query_params_sanitizer.rb +55 -0
  107. data/lib/appsignal/version.rb +3 -0
  108. data/lib/sequel/extensions/appsignal_integration.rb +3 -0
  109. data/resources/appsignal.yml.erb +39 -0
  110. data/resources/cacert.pem +3866 -0
  111. data/spec/.rubocop.yml +7 -0
  112. data/spec/lib/appsignal/auth_check_spec.rb +80 -0
  113. data/spec/lib/appsignal/capistrano2_spec.rb +224 -0
  114. data/spec/lib/appsignal/capistrano3_spec.rb +237 -0
  115. data/spec/lib/appsignal/cli/demo_spec.rb +67 -0
  116. data/spec/lib/appsignal/cli/diagnose_spec.rb +988 -0
  117. data/spec/lib/appsignal/cli/helpers_spec.rb +171 -0
  118. data/spec/lib/appsignal/cli/install_spec.rb +632 -0
  119. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +168 -0
  120. data/spec/lib/appsignal/cli_spec.rb +56 -0
  121. data/spec/lib/appsignal/config_spec.rb +637 -0
  122. data/spec/lib/appsignal/demo_spec.rb +87 -0
  123. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +44 -0
  124. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +21 -0
  125. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +21 -0
  126. data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +52 -0
  127. data/spec/lib/appsignal/event_formatter/faraday/request_formatter_spec.rb +21 -0
  128. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +113 -0
  129. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +112 -0
  130. data/spec/lib/appsignal/event_formatter_spec.rb +100 -0
  131. data/spec/lib/appsignal/extension/jruby_spec.rb +43 -0
  132. data/spec/lib/appsignal/extension_spec.rb +137 -0
  133. data/spec/lib/appsignal/garbage_collection_profiler_spec.rb +66 -0
  134. data/spec/lib/appsignal/hooks/action_cable_spec.rb +370 -0
  135. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +92 -0
  136. data/spec/lib/appsignal/hooks/celluloid_spec.rb +35 -0
  137. data/spec/lib/appsignal/hooks/data_mapper_spec.rb +39 -0
  138. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +358 -0
  139. data/spec/lib/appsignal/hooks/mongo_ruby_driver_spec.rb +44 -0
  140. data/spec/lib/appsignal/hooks/net_http_spec.rb +53 -0
  141. data/spec/lib/appsignal/hooks/passenger_spec.rb +30 -0
  142. data/spec/lib/appsignal/hooks/puma_spec.rb +80 -0
  143. data/spec/lib/appsignal/hooks/que_spec.rb +19 -0
  144. data/spec/lib/appsignal/hooks/rake_spec.rb +73 -0
  145. data/spec/lib/appsignal/hooks/redis_spec.rb +55 -0
  146. data/spec/lib/appsignal/hooks/sequel_spec.rb +46 -0
  147. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +192 -0
  148. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +419 -0
  149. data/spec/lib/appsignal/hooks/unicorn_spec.rb +52 -0
  150. data/spec/lib/appsignal/hooks/webmachine_spec.rb +35 -0
  151. data/spec/lib/appsignal/hooks_spec.rb +195 -0
  152. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +65 -0
  153. data/spec/lib/appsignal/integrations/grape_spec.rb +225 -0
  154. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +127 -0
  155. data/spec/lib/appsignal/integrations/object_spec.rb +249 -0
  156. data/spec/lib/appsignal/integrations/padrino_spec.rb +323 -0
  157. data/spec/lib/appsignal/integrations/que_spec.rb +174 -0
  158. data/spec/lib/appsignal/integrations/railtie_spec.rb +129 -0
  159. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +83 -0
  160. data/spec/lib/appsignal/integrations/resque_spec.rb +92 -0
  161. data/spec/lib/appsignal/integrations/sinatra_spec.rb +73 -0
  162. data/spec/lib/appsignal/integrations/webmachine_spec.rb +69 -0
  163. data/spec/lib/appsignal/js_exception_transaction_spec.rb +128 -0
  164. data/spec/lib/appsignal/marker_spec.rb +51 -0
  165. data/spec/lib/appsignal/minutely_spec.rb +50 -0
  166. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +90 -0
  167. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +147 -0
  168. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +117 -0
  169. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +213 -0
  170. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +161 -0
  171. data/spec/lib/appsignal/system_spec.rb +131 -0
  172. data/spec/lib/appsignal/transaction_spec.rb +1146 -0
  173. data/spec/lib/appsignal/transmitter_spec.rb +152 -0
  174. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +136 -0
  175. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +192 -0
  176. data/spec/lib/appsignal/utils_spec.rb +150 -0
  177. data/spec/lib/appsignal_spec.rb +1049 -0
  178. data/spec/spec_helper.rb +116 -0
  179. data/spec/support/fixtures/containers/cgroups/docker +14 -0
  180. data/spec/support/fixtures/containers/cgroups/docker_systemd +8 -0
  181. data/spec/support/fixtures/containers/cgroups/lxc +10 -0
  182. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  183. data/spec/support/fixtures/containers/cgroups/none +1 -0
  184. data/spec/support/fixtures/generated_config.yml +24 -0
  185. data/spec/support/fixtures/uploaded_file.txt +0 -0
  186. data/spec/support/helpers/api_request_helper.rb +19 -0
  187. data/spec/support/helpers/cli_helpers.rb +26 -0
  188. data/spec/support/helpers/config_helpers.rb +21 -0
  189. data/spec/support/helpers/dependency_helper.rb +73 -0
  190. data/spec/support/helpers/directory_helper.rb +27 -0
  191. data/spec/support/helpers/env_helpers.rb +33 -0
  192. data/spec/support/helpers/example_exception.rb +13 -0
  193. data/spec/support/helpers/example_standard_error.rb +13 -0
  194. data/spec/support/helpers/log_helpers.rb +22 -0
  195. data/spec/support/helpers/std_streams_helper.rb +66 -0
  196. data/spec/support/helpers/system_helpers.rb +8 -0
  197. data/spec/support/helpers/time_helpers.rb +11 -0
  198. data/spec/support/helpers/transaction_helpers.rb +37 -0
  199. data/spec/support/matchers/contains_log.rb +7 -0
  200. data/spec/support/mocks/fake_gc_profiler.rb +19 -0
  201. data/spec/support/mocks/mock_extension.rb +6 -0
  202. data/spec/support/project_fixture/config/application.rb +0 -0
  203. data/spec/support/project_fixture/config/appsignal.yml +32 -0
  204. data/spec/support/project_fixture/config/environments/development.rb +0 -0
  205. data/spec/support/project_fixture/config/environments/production.rb +0 -0
  206. data/spec/support/project_fixture/config/environments/test.rb +0 -0
  207. data/spec/support/project_fixture/log/.gitkeep +0 -0
  208. data/spec/support/rails/my_app.rb +6 -0
  209. data/spec/support/shared_examples/instrument.rb +43 -0
  210. data/spec/support/stubs/delayed_job.rb +0 -0
  211. metadata +483 -0
@@ -0,0 +1,161 @@
1
+ require "appsignal/rack/streaming_listener"
2
+
3
+ describe Appsignal::Rack::StreamingListener do
4
+ let(:headers) { {} }
5
+ let(:env) do
6
+ {
7
+ "rack.input" => StringIO.new,
8
+ "REQUEST_METHOD" => "GET",
9
+ "PATH_INFO" => "/homepage",
10
+ "QUERY_STRING" => "param=something"
11
+ }
12
+ end
13
+ let(:app) { double(:call => [200, headers, "body"]) }
14
+ let(:listener) { Appsignal::Rack::StreamingListener.new(app, {}) }
15
+
16
+ describe "#call" do
17
+ context "when Appsignal is active" do
18
+ before { allow(Appsignal).to receive(:active?).and_return(true) }
19
+
20
+ it "should call `call_with_appsignal_monitoring`" do
21
+ expect(listener).to receive(:call_with_appsignal_monitoring)
22
+ end
23
+ end
24
+
25
+ context "when Appsignal is not active" do
26
+ before { allow(Appsignal).to receive(:active?).and_return(false) }
27
+
28
+ it "should not call `call_with_appsignal_monitoring`" do
29
+ expect(listener).to_not receive(:call_with_appsignal_monitoring)
30
+ end
31
+ end
32
+
33
+ after { listener.call(env) }
34
+ end
35
+
36
+ describe "#call_with_appsignal_monitoring" do
37
+ let!(:transaction) do
38
+ Appsignal::Transaction.create(
39
+ SecureRandom.uuid,
40
+ Appsignal::Transaction::HTTP_REQUEST,
41
+ ::Rack::Request.new(env)
42
+ )
43
+ end
44
+ let(:wrapper) { Appsignal::StreamWrapper.new("body", transaction) }
45
+ let(:raw_payload) { { :foo => :bar } }
46
+
47
+ before do
48
+ allow(SecureRandom).to receive(:uuid).and_return("123")
49
+ allow(listener).to receive(:raw_payload).and_return(raw_payload)
50
+ allow(Appsignal::Transaction).to receive(:create).and_return(transaction)
51
+ end
52
+
53
+ it "should create a transaction" do
54
+ expect(Appsignal::Transaction).to receive(:create)
55
+ .with("123", Appsignal::Transaction::HTTP_REQUEST, instance_of(Rack::Request))
56
+ .and_return(transaction)
57
+
58
+ listener.call_with_appsignal_monitoring(env)
59
+ end
60
+
61
+ it "should instrument the call" do
62
+ expect(Appsignal).to receive(:instrument)
63
+ .with("process_action.rack")
64
+ .and_yield
65
+
66
+ listener.call_with_appsignal_monitoring(env)
67
+ end
68
+
69
+ it "should add `appsignal.action` to the transaction" do
70
+ allow(Appsignal).to receive(:instrument).and_yield
71
+
72
+ env["appsignal.action"] = "Action"
73
+
74
+ expect(transaction).to receive(:set_action_if_nil).with("Action")
75
+
76
+ listener.call_with_appsignal_monitoring(env)
77
+ end
78
+
79
+ it "should add the path, method and queue start to the transaction" do
80
+ allow(Appsignal).to receive(:instrument).and_yield
81
+
82
+ expect(transaction).to receive(:set_metadata).with("path", "/homepage")
83
+ expect(transaction).to receive(:set_metadata).with("method", "GET")
84
+ expect(transaction).to receive(:set_http_or_background_queue_start)
85
+
86
+ listener.call_with_appsignal_monitoring(env)
87
+ end
88
+
89
+ context "with an exception in the instrumentation call" do
90
+ let(:error) { ExampleException }
91
+
92
+ it "should add the exception to the transaction" do
93
+ allow(app).to receive(:call).and_raise(error)
94
+
95
+ expect(transaction).to receive(:set_error).with(error)
96
+
97
+ expect do
98
+ listener.call_with_appsignal_monitoring(env)
99
+ end.to raise_error(error)
100
+ end
101
+ end
102
+
103
+ it "should wrap the body in a wrapper" do
104
+ expect(Appsignal::StreamWrapper).to receive(:new)
105
+ .with("body", transaction)
106
+ .and_return(wrapper)
107
+
108
+ body = listener.call_with_appsignal_monitoring(env)[2]
109
+
110
+ expect(body).to be_a(Appsignal::StreamWrapper)
111
+ end
112
+ end
113
+ end
114
+
115
+ describe Appsignal::StreamWrapper do
116
+ let(:stream) { double }
117
+ let(:transaction) { Appsignal::Transaction.create(SecureRandom.uuid, Appsignal::Transaction::HTTP_REQUEST, {}) }
118
+ let(:wrapper) { Appsignal::StreamWrapper.new(stream, transaction) }
119
+
120
+ describe "#each" do
121
+ it "calls the original stream" do
122
+ expect(stream).to receive(:each)
123
+
124
+ wrapper.each
125
+ end
126
+
127
+ context "when #each raises an error" do
128
+ let(:error) { ExampleException }
129
+
130
+ it "records the exception" do
131
+ allow(stream).to receive(:each).and_raise(error)
132
+
133
+ expect(transaction).to receive(:set_error).with(error)
134
+
135
+ expect { wrapper.send(:each) }.to raise_error(error)
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "#close" do
141
+ it "closes the original stream and completes the transaction" do
142
+ expect(stream).to receive(:close)
143
+ expect(Appsignal::Transaction).to receive(:complete_current!)
144
+
145
+ wrapper.close
146
+ end
147
+
148
+ context "when #close raises an error" do
149
+ let(:error) { ExampleException }
150
+
151
+ it "records the exception and completes the transaction" do
152
+ allow(stream).to receive(:close).and_raise(error)
153
+
154
+ expect(transaction).to receive(:set_error).with(error)
155
+ expect(transaction).to receive(:complete)
156
+
157
+ expect { wrapper.send(:close) }.to raise_error(error)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,131 @@
1
+ require "appsignal/system"
2
+
3
+ describe Appsignal::System do
4
+ describe ".heroku?" do
5
+ subject { described_class.heroku? }
6
+
7
+ context "when on Heroku" do
8
+ around { |example| recognize_as_heroku { example.run } }
9
+
10
+ it "returns true" do
11
+ is_expected.to eq(true)
12
+ end
13
+ end
14
+
15
+ context "when not on Heroku" do
16
+ it "returns false" do
17
+ is_expected.to eq(false)
18
+ end
19
+ end
20
+ end
21
+
22
+ describe ".installed_agent_architecture" do
23
+ let(:const_name) { "GEM_EXT_PATH".freeze }
24
+ let(:tmp_ext_dir) { File.join(tmp_dir, "ext") }
25
+ let(:architecture_file) { File.join(Appsignal::System::GEM_EXT_PATH, "appsignal.architecture") }
26
+ around do |example|
27
+ original_gem_ext_path = Appsignal::System.const_get(const_name)
28
+ Appsignal::System.send(:remove_const, const_name)
29
+ Appsignal::System.const_set(const_name, tmp_ext_dir)
30
+ example.run
31
+ Appsignal::System.send(:remove_const, const_name)
32
+ Appsignal::System.const_set(const_name, original_gem_ext_path)
33
+ end
34
+ after { FileUtils.rm_rf(tmp_ext_dir) }
35
+ subject { described_class.installed_agent_architecture }
36
+
37
+ context "with an ext/appsignal.architecture file" do
38
+ before do
39
+ FileUtils.mkdir_p(Appsignal::System::GEM_EXT_PATH)
40
+ File.open(architecture_file, "w") do |file|
41
+ file.write "foo"
42
+ end
43
+ end
44
+
45
+ it "returns the contents of the file" do
46
+ expect(subject).to eq("foo")
47
+ end
48
+ end
49
+
50
+ context "without an ext/appsignal.architecture file" do
51
+ it "returns nil" do
52
+ expect(subject).to be_nil
53
+ end
54
+ end
55
+ end
56
+
57
+ describe ".agent_platform" do
58
+ let(:os) { "linux-gnu" }
59
+ let(:ldd_output) { "" }
60
+ before do
61
+ allow(described_class).to receive(:ldd_version_output).and_return(ldd_output)
62
+ allow(RbConfig::CONFIG).to receive(:[])
63
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return(os)
64
+ end
65
+ subject { described_class.agent_platform }
66
+
67
+ context "when the system detection doesn't work" do
68
+ it "returns the libc build" do
69
+ is_expected.to eq("linux")
70
+ end
71
+ end
72
+
73
+ context "when using the APPSIGNAL_BUILD_FOR_MUSL env var" do
74
+ it "returns the musl build" do
75
+ ENV["APPSIGNAL_BUILD_FOR_MUSL"] = "1"
76
+ is_expected.to eq("linux-musl")
77
+ ENV.delete("APPSIGNAL_BUILD_FOR_MUSL")
78
+ end
79
+ end
80
+
81
+ context "when on a musl system" do
82
+ let(:ldd_output) { "musl libc (x86_64)\nVersion 1.1.16" }
83
+
84
+ it "returns the musl build" do
85
+ is_expected.to eq("linux-musl")
86
+ end
87
+ end
88
+
89
+ context "when on a libc system" do
90
+ let(:ldd_output) { "ldd (Debian GLIBC 2.15-18+deb8u7) 2.15" }
91
+
92
+ it "returns the libc build" do
93
+ is_expected.to eq("linux")
94
+ end
95
+
96
+ context "when on an old libc system" do
97
+ let(:ldd_output) { "ldd (Debian GLIBC 2.14-18+deb8u7) 2.14" }
98
+
99
+ it "returns the musl build" do
100
+ is_expected.to eq("linux-musl")
101
+ end
102
+ end
103
+
104
+ context "when on a very old libc system" do
105
+ let(:ldd_output) { "ldd (Debian GLIBC 2.5-18+deb8u7) 2.5" }
106
+
107
+ it "returns the musl build" do
108
+ is_expected.to eq("linux-musl")
109
+ end
110
+ end
111
+ end
112
+
113
+ context "when on macOS" do
114
+ let(:os) { "darwin16.7.0" }
115
+ let(:ldd_output) { "ldd: command not found" }
116
+
117
+ it "returns the darwin build" do
118
+ is_expected.to eq("darwin")
119
+ end
120
+ end
121
+
122
+ context "when on FreeBSD" do
123
+ let(:os) { "freebsd11" }
124
+ let(:ldd_output) { "ldd: illegal option -- -" }
125
+
126
+ it "returns the darwin build" do
127
+ is_expected.to eq("freebsd")
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,1146 @@
1
+ describe Appsignal::Transaction do
2
+ before :context do
3
+ start_agent
4
+ end
5
+
6
+ let(:transaction_id) { "1" }
7
+ let(:time) { Time.at(fixed_time) }
8
+ let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
9
+ let(:env) { {} }
10
+ let(:merged_env) { http_request_env_with_data(env) }
11
+ let(:options) { {} }
12
+ let(:request) { Rack::Request.new(merged_env) }
13
+ let(:transaction) { Appsignal::Transaction.new(transaction_id, namespace, request, options) }
14
+ let(:log) { StringIO.new }
15
+
16
+ before { Timecop.freeze(time) }
17
+ after { Timecop.return }
18
+ around do |example|
19
+ use_logger_with log do
20
+ example.run
21
+ end
22
+ end
23
+
24
+ describe "class methods" do
25
+ def current_transaction
26
+ Appsignal::Transaction.current
27
+ end
28
+
29
+ describe ".create" do
30
+ def create_transaction(id = transaction_id)
31
+ Appsignal::Transaction.create(id, namespace, request, options)
32
+ end
33
+
34
+ context "when no transaction is running" do
35
+ let!(:transaction) { create_transaction }
36
+
37
+ it "returns the created transaction" do
38
+ expect(transaction).to be_a Appsignal::Transaction
39
+ expect(transaction.transaction_id).to eq transaction_id
40
+ expect(transaction.namespace).to eq namespace
41
+ expect(transaction.request).to eq request
42
+
43
+ expect(transaction.to_h).to include(
44
+ "id" => transaction_id,
45
+ "namespace" => namespace
46
+ )
47
+ end
48
+
49
+ it "assigns the transaction to current" do
50
+ expect(transaction).to eq current_transaction
51
+ end
52
+ end
53
+
54
+ context "when a transaction is already running" do
55
+ before { create_transaction }
56
+
57
+ it "does not create a new transaction, but returns the current transaction" do
58
+ expect do
59
+ new_transaction = create_transaction("2")
60
+ expect(new_transaction).to eq(current_transaction)
61
+ expect(new_transaction.transaction_id).to eq(transaction_id)
62
+ end.to_not(change { current_transaction })
63
+ end
64
+
65
+ it "logs a debug message" do
66
+ create_transaction("2")
67
+ expect(log_contents(log)).to contains_log :debug,
68
+ "Trying to start new transaction with id '2', but a " \
69
+ "transaction with id '#{transaction_id}' is already " \
70
+ "running. Using transaction '#{transaction_id}'."
71
+ end
72
+
73
+ context "with option :force => true" do
74
+ it "returns the newly created (and current) transaction" do
75
+ original_transaction = current_transaction
76
+ expect(original_transaction).to_not be_nil
77
+ expect(current_transaction.transaction_id).to eq transaction_id
78
+
79
+ options[:force] = true
80
+ expect(create_transaction("2")).to_not eq original_transaction
81
+ expect(current_transaction.transaction_id).to eq "2"
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe ".current" do
88
+ subject { Appsignal::Transaction.current }
89
+
90
+ context "when there is a current transaction" do
91
+ let!(:transaction) do
92
+ Appsignal::Transaction.create(transaction_id, namespace, request, options)
93
+ end
94
+
95
+ it "reads :appsignal_transaction from the current Thread" do
96
+ expect(subject).to eq Thread.current[:appsignal_transaction]
97
+ expect(subject).to eq transaction
98
+ end
99
+
100
+ it "is not a NilTransaction" do
101
+ expect(subject.nil_transaction?).to eq false
102
+ expect(subject).to be_a Appsignal::Transaction
103
+ end
104
+ end
105
+
106
+ context "when there is no current transaction" do
107
+ it "has no :appsignal_transaction registered on the current Thread" do
108
+ expect(Thread.current[:appsignal_transaction]).to be_nil
109
+ end
110
+
111
+ it "returns a NilTransaction stub" do
112
+ expect(subject.nil_transaction?).to eq true
113
+ expect(subject).to be_a Appsignal::Transaction::NilTransaction
114
+ end
115
+ end
116
+ end
117
+
118
+ describe ".complete_current!" do
119
+ let!(:transaction) { Appsignal::Transaction.create(transaction_id, namespace, options) }
120
+
121
+ it "completes the current transaction" do
122
+ expect(transaction).to eq current_transaction
123
+ expect(transaction).to receive(:complete).and_call_original
124
+
125
+ Appsignal::Transaction.complete_current!
126
+ end
127
+
128
+ it "unsets the current transaction on the current Thread" do
129
+ expect do
130
+ Appsignal::Transaction.complete_current!
131
+ end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
132
+ end
133
+
134
+ context "when encountering an error while completing" do
135
+ before do
136
+ expect(transaction).to receive(:complete).and_raise ExampleStandardError
137
+ end
138
+
139
+ it "logs an error message" do
140
+ Appsignal::Transaction.complete_current!
141
+ expect(log_contents(log)).to contains_log :error,
142
+ "Failed to complete transaction ##{transaction.transaction_id}. ExampleStandardError"
143
+ end
144
+
145
+ it "clears the current transaction" do
146
+ expect do
147
+ Appsignal::Transaction.complete_current!
148
+ end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "#complete" do
155
+ context "when transaction is being sampled" do
156
+ it "samples data" do
157
+ expect(transaction.ext).to receive(:finish).and_return(true)
158
+ # Stub call to extension, because that would remove the transaction
159
+ # from the extension.
160
+ expect(transaction.ext).to receive(:complete)
161
+
162
+ transaction.set_tags(:foo => "bar")
163
+ transaction.complete
164
+ expect(transaction.to_h["sample_data"]).to include(
165
+ "tags" => { "foo" => "bar" }
166
+ )
167
+ end
168
+ end
169
+
170
+ context "when transaction is not being sampled" do
171
+ it "does not sample data" do
172
+ expect(transaction).to_not receive(:sample_data)
173
+ expect(transaction.ext).to receive(:finish).and_return(false)
174
+ expect(transaction.ext).to receive(:complete).and_call_original
175
+
176
+ transaction.complete
177
+ end
178
+ end
179
+
180
+ context "when a transaction is marked as discarded" do
181
+ it "does not complete the transaction" do
182
+ expect(transaction.ext).to_not receive(:complete)
183
+
184
+ expect do
185
+ transaction.discard!
186
+ end.to change { transaction.discarded? }.from(false).to(true)
187
+
188
+ transaction.complete
189
+ end
190
+
191
+ it "logs a debug message" do
192
+ transaction.discard!
193
+ transaction.complete
194
+
195
+ expect(log_contents(log)).to contains_log :debug,
196
+ "Skipping transaction '#{transaction_id}' because it was manually discarded."
197
+ end
198
+
199
+ context "when a discarded transaction is restored" do
200
+ before { transaction.discard! }
201
+
202
+ it "completes the transaction" do
203
+ expect(transaction.ext).to receive(:complete).and_call_original
204
+
205
+ expect do
206
+ transaction.restore!
207
+ end.to change { transaction.discarded? }.from(true).to(false)
208
+
209
+ transaction.complete
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ context "pausing" do
216
+ describe "#pause!" do
217
+ it "should change the pause flag to true" do
218
+ expect do
219
+ transaction.pause!
220
+ end.to change(transaction, :paused).from(false).to(true)
221
+ end
222
+ end
223
+
224
+ describe "#resume!" do
225
+ before { transaction.pause! }
226
+
227
+ it "should change the pause flag to false" do
228
+ expect do
229
+ transaction.resume!
230
+ end.to change(transaction, :paused).from(true).to(false)
231
+ end
232
+ end
233
+
234
+ describe "#paused?" do
235
+ it "should return the pause state" do
236
+ expect(transaction.paused?).to be_falsy
237
+ end
238
+
239
+ context "when paused" do
240
+ before { transaction.pause! }
241
+
242
+ it "should return the pause state" do
243
+ expect(transaction.paused?).to be_truthy
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ context "with transaction instance" do
250
+ context "initialization" do
251
+ it "loads the AppSignal extension" do
252
+ expect(transaction.ext).to_not be_nil
253
+ end
254
+
255
+ it "sets the transaction id" do
256
+ expect(transaction.transaction_id).to eq "1"
257
+ end
258
+
259
+ it "sets the namespace to http_request" do
260
+ expect(transaction.namespace).to eq "http_request"
261
+ end
262
+
263
+ it "sets the request" do
264
+ expect(transaction.request).to_not be_nil
265
+ end
266
+
267
+ it "sets the request not to paused" do
268
+ expect(transaction.paused).to be_falsy
269
+ end
270
+
271
+ it "sets no tags by default" do
272
+ expect(transaction.tags).to eq({})
273
+ end
274
+
275
+ describe "#options" do
276
+ subject { transaction.options }
277
+
278
+ it "sets the default :params_method" do
279
+ expect(subject[:params_method]).to eq :params
280
+ end
281
+
282
+ context "with overridden options" do
283
+ let(:options) { { :params_method => :filtered_params } }
284
+
285
+ it "sets the overriden :params_method" do
286
+ expect(subject[:params_method]).to eq :filtered_params
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ describe "#store" do
293
+ it "should return an empty store when it's not already present" do
294
+ expect(transaction.store("test")).to eql({})
295
+ end
296
+
297
+ it "should store changes to the store" do
298
+ transaction_store = transaction.store("test")
299
+ transaction_store["transaction"] = "value"
300
+
301
+ expect(transaction.store("test")).to eql("transaction" => "value")
302
+ end
303
+ end
304
+
305
+ describe "#params" do
306
+ subject { transaction.params }
307
+
308
+ context "with custom params set on transaction" do
309
+ before do
310
+ transaction.params = { :foo => "bar" }
311
+ end
312
+
313
+ it "returns custom parameters" do
314
+ expect(subject).to eq(:foo => "bar")
315
+ end
316
+ end
317
+
318
+ context "without custom params set on transaction" do
319
+ it "returns parameters from request" do
320
+ expect(subject).to eq(
321
+ "action" => "show",
322
+ "controller" => "blog_posts",
323
+ "id" => "1"
324
+ )
325
+ end
326
+ end
327
+ end
328
+
329
+ describe "#params=" do
330
+ it "sets params on the transaction" do
331
+ transaction.params = { :foo => "bar" }
332
+ expect(transaction.params).to eq(:foo => "bar")
333
+ end
334
+ end
335
+
336
+ describe "#set_tags" do
337
+ it "should add tags to transaction" do
338
+ expect do
339
+ transaction.set_tags("a" => "b")
340
+ end.to change(transaction, :tags).to("a" => "b")
341
+ end
342
+ end
343
+
344
+ describe "set_action" do
345
+ it "should set the action in extension" do
346
+ expect(transaction.ext).to receive(:set_action).with(
347
+ "PagesController#show"
348
+ ).once
349
+
350
+ transaction.set_action("PagesController#show")
351
+
352
+ expect(transaction.action).to eq "PagesController#show"
353
+ end
354
+
355
+ it "should not set the action in extension when value is nil" do
356
+ expect(Appsignal::Extension).to_not receive(:set_action)
357
+
358
+ transaction.set_action(nil)
359
+ end
360
+ end
361
+
362
+ describe "set_action_if_nil" do
363
+ context "if action is currently nil" do
364
+ it "should set the action" do
365
+ expect(transaction.ext).to receive(:set_action).with(
366
+ "PagesController#show"
367
+ ).once
368
+
369
+ transaction.set_action_if_nil("PagesController#show")
370
+ end
371
+ end
372
+
373
+ context "if action is currently set" do
374
+ it "should not set the action" do
375
+ transaction.set_action("something")
376
+
377
+ expect(transaction.ext).not_to receive(:set_action)
378
+
379
+ transaction.set_action_if_nil("PagesController#show")
380
+ end
381
+ end
382
+ end
383
+
384
+ describe "set_namespace" do
385
+ it "should set the action in extension" do
386
+ expect(transaction.ext).to receive(:set_namespace).with(
387
+ "custom"
388
+ ).once
389
+
390
+ transaction.set_namespace("custom")
391
+
392
+ expect(transaction.namespace).to eq "custom"
393
+ end
394
+
395
+ it "should not set the action in extension when value is nil" do
396
+ expect(Appsignal::Extension).to_not receive(:set_namespace)
397
+
398
+ transaction.set_action(nil)
399
+ end
400
+ end
401
+
402
+ describe "#set_http_or_background_action" do
403
+ context "for a hash with controller and action" do
404
+ let(:from) { { :controller => "HomeController", :action => "show" } }
405
+
406
+ it "should set the action" do
407
+ expect(transaction).to receive(:set_action).with("HomeController#show")
408
+ end
409
+ end
410
+
411
+ context "for a hash with just action" do
412
+ let(:from) { { :action => "show" } }
413
+
414
+ it "should set the action" do
415
+ expect(transaction).to receive(:set_action).with("show")
416
+ end
417
+ end
418
+
419
+ context "for a hash with class and method" do
420
+ let(:from) { { :class => "Worker", :method => "perform" } }
421
+
422
+ it "should set the action" do
423
+ expect(transaction).to receive(:set_action).with("Worker#perform")
424
+ end
425
+ end
426
+
427
+ after { transaction.set_http_or_background_action(from) }
428
+ end
429
+
430
+ describe "set_queue_start" do
431
+ it "should set the queue start in extension" do
432
+ expect(transaction.ext).to receive(:set_queue_start).with(
433
+ 10.0
434
+ ).once
435
+
436
+ transaction.set_queue_start(10.0)
437
+ end
438
+
439
+ it "should not set the queue start in extension when value is nil" do
440
+ expect(transaction.ext).to_not receive(:set_queue_start)
441
+
442
+ transaction.set_queue_start(nil)
443
+ end
444
+
445
+ it "should not raise an error when the queue start is too big" do
446
+ expect(transaction.ext).to receive(:set_queue_start).and_raise(RangeError)
447
+
448
+ expect(Appsignal.logger).to receive(:warn).with("Queue start value 10 is too big")
449
+
450
+ expect do
451
+ transaction.set_queue_start(10)
452
+ end.to_not raise_error
453
+ end
454
+ end
455
+
456
+ describe "#set_http_or_background_queue_start" do
457
+ context "for a http transaction" do
458
+ let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
459
+ let(:env) { { "HTTP_X_REQUEST_START" => (fixed_time * 1000).to_s } }
460
+
461
+ it "should set the queue start on the transaction" do
462
+ expect(transaction).to receive(:set_queue_start).with(13_897_836_000)
463
+
464
+ transaction.set_http_or_background_queue_start
465
+ end
466
+ end
467
+
468
+ context "for a background transaction" do
469
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
470
+ let(:env) { { :queue_start => fixed_time } }
471
+
472
+ it "should set the queue start on the transaction" do
473
+ expect(transaction).to receive(:set_queue_start).with(1_389_783_600_000)
474
+
475
+ transaction.set_http_or_background_queue_start
476
+ end
477
+ end
478
+ end
479
+
480
+ describe "#set_metadata" do
481
+ it "should set the metdata in extension" do
482
+ expect(transaction.ext).to receive(:set_metadata).with(
483
+ "request_method",
484
+ "GET"
485
+ ).once
486
+
487
+ transaction.set_metadata("request_method", "GET")
488
+ end
489
+
490
+ it "should not set the metdata in extension when value is nil" do
491
+ expect(transaction.ext).to_not receive(:set_metadata)
492
+
493
+ transaction.set_metadata("request_method", nil)
494
+ end
495
+ end
496
+
497
+ describe "set_sample_data" do
498
+ it "should set the data" do
499
+ expect(transaction.ext).to receive(:set_sample_data).with(
500
+ "params",
501
+ Appsignal::Utils.data_generate("controller" => "blog_posts", "action" => "show", "id" => "1")
502
+ ).once
503
+
504
+ transaction.set_sample_data(
505
+ "params",
506
+ :controller => "blog_posts",
507
+ :action => "show",
508
+ :id => "1"
509
+ )
510
+ end
511
+
512
+ it "should do nothing if the data cannot be converted to json" do
513
+ expect(transaction.ext).to_not receive(:set_sample_data).with(
514
+ "params",
515
+ kind_of(String)
516
+ )
517
+
518
+ transaction.set_sample_data("params", "string")
519
+ end
520
+ end
521
+
522
+ describe "#sample_data" do
523
+ it "should sample data" do
524
+ expect(transaction.ext).to receive(:set_sample_data).with(
525
+ "environment",
526
+ Appsignal::Utils.data_generate(
527
+ "CONTENT_LENGTH" => "0",
528
+ "REQUEST_METHOD" => "GET",
529
+ "SERVER_NAME" => "example.org",
530
+ "SERVER_PORT" => "80",
531
+ "PATH_INFO" => "/blog"
532
+ )
533
+ ).once
534
+ expect(transaction.ext).to receive(:set_sample_data).with(
535
+ "session_data",
536
+ Appsignal::Utils.data_generate({})
537
+ ).once
538
+ expect(transaction.ext).to receive(:set_sample_data).with(
539
+ "params",
540
+ Appsignal::Utils.data_generate("controller" => "blog_posts", "action" => "show", "id" => "1")
541
+ ).once
542
+ expect(transaction.ext).to receive(:set_sample_data).with(
543
+ "metadata",
544
+ Appsignal::Utils.data_generate("key" => "value")
545
+ ).once
546
+ expect(transaction.ext).to receive(:set_sample_data).with(
547
+ "tags",
548
+ Appsignal::Utils.data_generate({})
549
+ ).once
550
+
551
+ transaction.sample_data
552
+ end
553
+ end
554
+
555
+ describe "#set_error" do
556
+ let(:env) { http_request_env_with_data }
557
+ let(:error) { double(:error, :message => "test message", :backtrace => ["line 1"]) }
558
+
559
+ it "should also respond to add_exception for backwords compatibility" do
560
+ expect(transaction).to respond_to(:add_exception)
561
+ end
562
+
563
+ it "should not add the error if appsignal is not active" do
564
+ allow(Appsignal).to receive(:active?).and_return(false)
565
+ expect(transaction.ext).to_not receive(:set_error)
566
+
567
+ transaction.set_error(error)
568
+ end
569
+
570
+ context "for a http request" do
571
+ it "should set an error in the extension" do
572
+ expect(transaction.ext).to receive(:set_error).with(
573
+ "RSpec::Mocks::Double",
574
+ "test message",
575
+ Appsignal::Utils.data_generate(["line 1"])
576
+ )
577
+
578
+ transaction.set_error(error)
579
+ end
580
+ end
581
+
582
+ context "when error message is nil" do
583
+ let(:error) { double(:error, :message => nil, :backtrace => ["line 1"]) }
584
+
585
+ it "should not raise an error" do
586
+ expect { transaction.set_error(error) }.to_not raise_error
587
+ end
588
+
589
+ it "should set an error in the extension" do
590
+ expect(transaction.ext).to receive(:set_error).with(
591
+ "RSpec::Mocks::Double",
592
+ "",
593
+ Appsignal::Utils.data_generate(["line 1"])
594
+ )
595
+
596
+ transaction.set_error(error)
597
+ end
598
+ end
599
+ end
600
+
601
+ describe "#start_event" do
602
+ it "should start the event in the extension" do
603
+ expect(transaction.ext).to receive(:start_event).with(0).and_call_original
604
+
605
+ transaction.start_event
606
+ end
607
+ end
608
+
609
+ describe "#finish_event" do
610
+ let(:fake_gc_time) { 123 }
611
+ before do
612
+ expect(described_class.garbage_collection_profiler)
613
+ .to receive(:total_time).at_least(:once).and_return(fake_gc_time)
614
+ end
615
+
616
+ it "should finish the event in the extension" do
617
+ expect(transaction.ext).to receive(:finish_event).with(
618
+ "name",
619
+ "title",
620
+ "body",
621
+ 1,
622
+ fake_gc_time
623
+ ).and_call_original
624
+
625
+ transaction.finish_event(
626
+ "name",
627
+ "title",
628
+ "body",
629
+ 1
630
+ )
631
+ end
632
+
633
+ it "should finish the event in the extension with nil arguments" do
634
+ expect(transaction.ext).to receive(:finish_event).with(
635
+ "name",
636
+ "",
637
+ "",
638
+ 0,
639
+ fake_gc_time
640
+ ).and_call_original
641
+
642
+ transaction.finish_event(
643
+ "name",
644
+ nil,
645
+ nil,
646
+ nil
647
+ )
648
+ end
649
+ end
650
+
651
+ describe "#record_event" do
652
+ let(:fake_gc_time) { 123 }
653
+ before do
654
+ expect(described_class.garbage_collection_profiler)
655
+ .to receive(:total_time).at_least(:once).and_return(fake_gc_time)
656
+ end
657
+
658
+ it "should record the event in the extension" do
659
+ expect(transaction.ext).to receive(:record_event).with(
660
+ "name",
661
+ "title",
662
+ "body",
663
+ 1,
664
+ 1000,
665
+ fake_gc_time
666
+ ).and_call_original
667
+
668
+ transaction.record_event(
669
+ "name",
670
+ "title",
671
+ "body",
672
+ 1000,
673
+ 1
674
+ )
675
+ end
676
+
677
+ it "should finish the event in the extension with nil arguments" do
678
+ expect(transaction.ext).to receive(:record_event).with(
679
+ "name",
680
+ "",
681
+ "",
682
+ 0,
683
+ 1000,
684
+ fake_gc_time
685
+ ).and_call_original
686
+
687
+ transaction.record_event(
688
+ "name",
689
+ nil,
690
+ nil,
691
+ 1000,
692
+ nil
693
+ )
694
+ end
695
+ end
696
+
697
+ describe "#instrument" do
698
+ it_behaves_like "instrument helper" do
699
+ let(:instrumenter) { transaction }
700
+ end
701
+ end
702
+
703
+ context "generic request" do
704
+ let(:env) { {} }
705
+ subject { Appsignal::Transaction::GenericRequest.new(env) }
706
+
707
+ it "initializes with an empty env" do
708
+ expect(subject.env).to be_empty
709
+ end
710
+
711
+ context "when given an env" do
712
+ let(:env) do
713
+ {
714
+ :params => { :id => 1 },
715
+ :queue_start => 10
716
+ }
717
+ end
718
+
719
+ it "sets the given env" do
720
+ expect(subject.env).to eq env
721
+ end
722
+
723
+ it "sets the params present in the env" do
724
+ expect(subject.params).to eq(:id => 1)
725
+ end
726
+ end
727
+ end
728
+
729
+ # private
730
+
731
+ describe "#background_queue_start" do
732
+ subject { transaction.send(:background_queue_start) }
733
+
734
+ context "when request is nil" do
735
+ let(:request) { nil }
736
+
737
+ it { is_expected.to eq nil }
738
+ end
739
+
740
+ context "when env is nil" do
741
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
742
+
743
+ it { is_expected.to eq nil }
744
+ end
745
+
746
+ context "when queue start is nil" do
747
+ it { is_expected.to eq nil }
748
+ end
749
+
750
+ context "when queue start is set" do
751
+ let(:env) { background_env_with_data }
752
+
753
+ it { is_expected.to eq 1_389_783_590_000 }
754
+ end
755
+ end
756
+
757
+ describe "#http_queue_start" do
758
+ let(:slightly_earlier_time) { fixed_time - 0.4 }
759
+ let(:slightly_earlier_time_value) { (slightly_earlier_time * factor).to_i }
760
+ subject { transaction.send(:http_queue_start) }
761
+
762
+ shared_examples "http queue start" do
763
+ context "when request is nil" do
764
+ let(:request) { nil }
765
+
766
+ it { is_expected.to be_nil }
767
+ end
768
+
769
+ context "when env is nil" do
770
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
771
+
772
+ it { is_expected.to be_nil }
773
+ end
774
+
775
+ context "with no relevant header set" do
776
+ let(:env) { {} }
777
+
778
+ it { is_expected.to be_nil }
779
+ end
780
+
781
+ context "with the HTTP_X_REQUEST_START header set" do
782
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=#{slightly_earlier_time_value}" } }
783
+
784
+ it { is_expected.to eq 1_389_783_599_600 }
785
+
786
+ context "with unparsable content" do
787
+ let(:env) { { "HTTP_X_REQUEST_START" => "something" } }
788
+
789
+ it { is_expected.to be_nil }
790
+ end
791
+
792
+ context "with some cruft" do
793
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=#{slightly_earlier_time_value}aaaa" } }
794
+
795
+ it { is_expected.to eq 1_389_783_599_600 }
796
+ end
797
+
798
+ context "with a really low number" do
799
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=100" } }
800
+
801
+ it { is_expected.to be_nil }
802
+ end
803
+
804
+ context "with the alternate HTTP_X_QUEUE_START header set" do
805
+ let(:env) { { "HTTP_X_QUEUE_START" => "t=#{slightly_earlier_time_value}" } }
806
+
807
+ it { is_expected.to eq 1_389_783_599_600 }
808
+ end
809
+ end
810
+ end
811
+
812
+ context "time in miliseconds" do
813
+ let(:factor) { 1_000 }
814
+
815
+ it_should_behave_like "http queue start"
816
+ end
817
+
818
+ context "time in microseconds" do
819
+ let(:factor) { 1_000_000 }
820
+
821
+ it_should_behave_like "http queue start"
822
+ end
823
+ end
824
+
825
+ describe "#sanitized_params" do
826
+ subject { transaction.send(:sanitized_params) }
827
+
828
+ context "with custom params" do
829
+ before do
830
+ transaction.params = { :foo => "bar", :baz => :bat }
831
+ end
832
+
833
+ it "returns custom params" do
834
+ is_expected.to eq(:foo => "bar", :baz => :bat)
835
+ end
836
+
837
+ context "with AppSignal filtering" do
838
+ before { Appsignal.config.config_hash[:filter_parameters] = %w[foo] }
839
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
840
+
841
+ it "returns sanitized custom params" do
842
+ expect(subject).to eq(:foo => "[FILTERED]", :baz => :bat)
843
+ end
844
+ end
845
+ end
846
+
847
+ context "without request params" do
848
+ before { allow(transaction.request).to receive(:params).and_return(nil) }
849
+
850
+ it { is_expected.to be_nil }
851
+ end
852
+
853
+ context "when request params crashes" do
854
+ before { allow(transaction.request).to receive(:params).and_raise(NoMethodError) }
855
+
856
+ it { is_expected.to be_nil }
857
+ end
858
+
859
+ context "when request params method does not exist" do
860
+ let(:options) { { :params_method => :nonsense } }
861
+
862
+ it { is_expected.to be_nil }
863
+ end
864
+
865
+ context "when not sending params" do
866
+ before { Appsignal.config.config_hash[:send_params] = false }
867
+ after { Appsignal.config.config_hash[:send_params] = true }
868
+
869
+ it { is_expected.to be_nil }
870
+ end
871
+
872
+ context "with an array" do
873
+ let(:request) do
874
+ Appsignal::Transaction::GenericRequest.new(background_env_with_data(:params => %w[arg1 arg2]))
875
+ end
876
+
877
+ it { is_expected.to eq %w[arg1 arg2] }
878
+
879
+ context "with AppSignal filtering" do
880
+ before { Appsignal.config.config_hash[:filter_parameters] = %w[foo] }
881
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
882
+
883
+ it { is_expected.to eq %w[arg1 arg2] }
884
+ end
885
+ end
886
+
887
+ context "with env" do
888
+ context "with sanitization" do
889
+ let(:request) do
890
+ Appsignal::Transaction::GenericRequest.new \
891
+ http_request_env_with_data(:params => { :foo => :bar })
892
+ end
893
+
894
+ it "should call the params sanitizer" do
895
+ expect(subject).to eq(:foo => :bar)
896
+ end
897
+ end
898
+
899
+ context "with AppSignal filtering" do
900
+ let(:request) do
901
+ Appsignal::Transaction::GenericRequest.new \
902
+ http_request_env_with_data(:params => { :foo => :bar, :baz => :bat })
903
+ end
904
+ before { Appsignal.config.config_hash[:filter_parameters] = %w[foo] }
905
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
906
+
907
+ it "should call the params sanitizer with filtering" do
908
+ expect(subject).to eq(:foo => "[FILTERED]", :baz => :bat)
909
+ end
910
+ end
911
+ end
912
+ end
913
+
914
+ describe "#sanitized_environment" do
915
+ let(:whitelisted_keys) { Appsignal::Transaction::ENV_METHODS }
916
+
917
+ subject { transaction.send(:sanitized_environment) }
918
+
919
+ context "when request is nil" do
920
+ let(:request) { nil }
921
+
922
+ it { is_expected.to be_nil }
923
+ end
924
+
925
+ context "when env is nil" do
926
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
927
+
928
+ it { is_expected.to be_nil }
929
+ end
930
+
931
+ context "when env is present" do
932
+ let(:env) do
933
+ {}.tap do |hash|
934
+ whitelisted_keys.each { |o| hash[o] = 1 } # use all whitelisted keys
935
+ hash[whitelisted_keys] = nil # don't add if nil
936
+ hash[:not_whitelisted] = "I will be sanitized"
937
+ end
938
+ end
939
+
940
+ it "only sets whitelisted keys" do
941
+ expect(subject.keys).to match_array(whitelisted_keys)
942
+ end
943
+ end
944
+ end
945
+
946
+ describe "#sanitized_session_data" do
947
+ subject { transaction.send(:sanitized_session_data) }
948
+
949
+ context "when request is nil" do
950
+ let(:request) { nil }
951
+
952
+ it { is_expected.to be_nil }
953
+ end
954
+
955
+ context "when session is nil" do
956
+ before { expect(transaction.request).to receive(:session).and_return(nil) }
957
+
958
+ it { is_expected.to be_nil }
959
+ end
960
+
961
+ context "when session is empty" do
962
+ before { expect(transaction.request).to receive(:session).and_return({}) }
963
+
964
+ it { is_expected.to eq({}) }
965
+ end
966
+
967
+ context "when request class does not have a session method" do
968
+ let(:request) { Appsignal::Transaction::GenericRequest.new({}) }
969
+
970
+ it { is_expected.to be_nil }
971
+ end
972
+
973
+ context "when there is a session" do
974
+ before do
975
+ expect(transaction).to respond_to(:request)
976
+ allow(transaction).to receive_message_chain(:request, :session => { :foo => :bar })
977
+ allow(transaction).to receive_message_chain(:request, :fullpath => :bar)
978
+ end
979
+
980
+ it "passes the session data into the params sanitizer" do
981
+ expect(Appsignal::Utils::ParamsSanitizer).to receive(:sanitize).with(:foo => :bar)
982
+ .and_return(:sanitized_foo)
983
+ expect(subject).to eq :sanitized_foo
984
+ end
985
+
986
+ if defined? ActionDispatch::Request::Session
987
+ context "with ActionDispatch::Request::Session" do
988
+ before do
989
+ expect(transaction).to respond_to(:request)
990
+ allow(transaction).to receive_message_chain(:request, :session => action_dispatch_session)
991
+ allow(transaction).to receive_message_chain(:request, :fullpath => :bar)
992
+ end
993
+
994
+ it "should return an session hash" do
995
+ expect(Appsignal::Utils::ParamsSanitizer).to receive(:sanitize).with("foo" => :bar)
996
+ .and_return(:sanitized_foo)
997
+ subject
998
+ end
999
+
1000
+ def action_dispatch_session
1001
+ store = Class.new do
1002
+ def load_session(_env)
1003
+ [1, { :foo => :bar }]
1004
+ end
1005
+
1006
+ def session_exists?(_env)
1007
+ true
1008
+ end
1009
+ end.new
1010
+ ActionDispatch::Request::Session.create(store, ActionDispatch::Request.new("rack.input" => StringIO.new), {})
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ context "when skipping session data" do
1016
+ before do
1017
+ Appsignal.config = { :skip_session_data => true }
1018
+ end
1019
+
1020
+ it "does not pass the session data into the params sanitizer" do
1021
+ expect(Appsignal::Utils::ParamsSanitizer).to_not receive(:sanitize)
1022
+ expect(subject).to be_nil
1023
+ end
1024
+ end
1025
+ end
1026
+ end
1027
+
1028
+ describe "#metadata" do
1029
+ subject { transaction.send(:metadata) }
1030
+
1031
+ context "when request is nil" do
1032
+ let(:request) { nil }
1033
+
1034
+ it { is_expected.to be_nil }
1035
+ end
1036
+
1037
+ context "when env is nil" do
1038
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
1039
+
1040
+ it { is_expected.to be_nil }
1041
+ end
1042
+
1043
+ context "when env is present" do
1044
+ let(:env) { { :metadata => { :key => "value" } } }
1045
+
1046
+ it { is_expected.to eq env[:metadata] }
1047
+ end
1048
+ end
1049
+
1050
+ describe "#sanitized_tags" do
1051
+ before do
1052
+ transaction.set_tags(
1053
+ :valid_key => "valid_value",
1054
+ "valid_string_key" => "valid_value",
1055
+ :both_symbols => :valid_value,
1056
+ :integer_value => 1,
1057
+ :hash_value => { "invalid" => "hash" },
1058
+ :array_value => %w[invalid array],
1059
+ :to_long_value => SecureRandom.urlsafe_base64(101),
1060
+ :object => Object.new,
1061
+ SecureRandom.urlsafe_base64(101) => "to_long_key"
1062
+ )
1063
+ end
1064
+ subject { transaction.send(:sanitized_tags).keys }
1065
+
1066
+ it "should only return whitelisted data" do
1067
+ is_expected.to match_array([
1068
+ :valid_key,
1069
+ "valid_string_key",
1070
+ :both_symbols,
1071
+ :integer_value
1072
+ ])
1073
+ end
1074
+ end
1075
+
1076
+ describe "#cleaned_backtrace" do
1077
+ subject { transaction.send(:cleaned_backtrace, ["line 1", "line 2"]) }
1078
+
1079
+ it "returns the backtrace" do
1080
+ expect(subject).to eq ["line 1", "line 2"]
1081
+ end
1082
+
1083
+ if rails_present?
1084
+ context "with rails" do
1085
+ it "cleans the backtrace with the Rails backtrace cleaner" do
1086
+ ::Rails.backtrace_cleaner.add_filter do |line|
1087
+ line.tr("2", "?")
1088
+ end
1089
+ expect(subject).to eq ["line 1", "line ?"]
1090
+ end
1091
+ end
1092
+ end
1093
+ end
1094
+ end
1095
+
1096
+ describe ".to_hash / .to_h" do
1097
+ subject { transaction.to_hash }
1098
+
1099
+ context "when extension returns serialized JSON" do
1100
+ it "parses the result and returns a Hash" do
1101
+ expect(subject).to include(
1102
+ "action" => nil,
1103
+ "error" => nil,
1104
+ "events" => [],
1105
+ "id" => transaction_id,
1106
+ "metadata" => {},
1107
+ "namespace" => namespace,
1108
+ "sample_data" => {}
1109
+ )
1110
+ end
1111
+ end
1112
+
1113
+ context "when the extension returns invalid serialized JSON" do
1114
+ before do
1115
+ expect(transaction.ext).to receive(:to_json).and_return("foo")
1116
+ end
1117
+
1118
+ it "raises a JSON parse error" do
1119
+ expect { subject }.to raise_error(JSON::ParserError)
1120
+ end
1121
+ end
1122
+ end
1123
+
1124
+ describe Appsignal::Transaction::NilTransaction do
1125
+ subject { Appsignal::Transaction::NilTransaction.new }
1126
+
1127
+ it "should have method stubs" do
1128
+ expect do
1129
+ subject.complete
1130
+ subject.pause!
1131
+ subject.resume!
1132
+ subject.paused?
1133
+ subject.store(:key)
1134
+ subject.set_tags(:tag => 1)
1135
+ subject.set_action("action")
1136
+ subject.set_http_or_background_action
1137
+ subject.set_queue_start(1)
1138
+ subject.set_http_or_background_queue_start
1139
+ subject.set_metadata("key", "value")
1140
+ subject.set_sample_data("key", "data")
1141
+ subject.sample_data
1142
+ subject.set_error("a")
1143
+ end.to_not raise_error
1144
+ end
1145
+ end
1146
+ end