appsignal 0.4.0
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.
- data/.gitignore +19 -0
- data/.rvmrc +1 -0
- data/.travis.yml +30 -0
- data/Gemfile +3 -0
- data/LICENCE +20 -0
- data/README.md +48 -0
- data/Rakefile +52 -0
- data/appsignal.gemspec +33 -0
- data/bin/appsignal +13 -0
- data/config/appsignal.yml +8 -0
- data/gemfiles/3.0.gemfile +16 -0
- data/gemfiles/3.1.gemfile +16 -0
- data/gemfiles/3.2.gemfile +16 -0
- data/gemfiles/edge.gemfile +16 -0
- data/lib/appsignal.rb +45 -0
- data/lib/appsignal/agent.rb +104 -0
- data/lib/appsignal/auth_check.rb +19 -0
- data/lib/appsignal/capistrano.rb +41 -0
- data/lib/appsignal/cli.rb +118 -0
- data/lib/appsignal/config.rb +30 -0
- data/lib/appsignal/exception_notification.rb +25 -0
- data/lib/appsignal/marker.rb +35 -0
- data/lib/appsignal/middleware.rb +30 -0
- data/lib/appsignal/railtie.rb +19 -0
- data/lib/appsignal/transaction.rb +77 -0
- data/lib/appsignal/transaction/faulty_request_formatter.rb +30 -0
- data/lib/appsignal/transaction/params_sanitizer.rb +36 -0
- data/lib/appsignal/transaction/regular_request_formatter.rb +11 -0
- data/lib/appsignal/transaction/slow_request_formatter.rb +34 -0
- data/lib/appsignal/transaction/transaction_formatter.rb +93 -0
- data/lib/appsignal/transmitter.rb +53 -0
- data/lib/appsignal/version.rb +3 -0
- data/lib/generators/appsignal/USAGE +8 -0
- data/lib/generators/appsignal/appsignal_generator.rb +70 -0
- data/lib/generators/appsignal/templates/appsignal.yml +4 -0
- data/log/.gitkeep +0 -0
- data/resources/cacert.pem +3849 -0
- data/spec/appsignal/agent_spec.rb +259 -0
- data/spec/appsignal/auth_check_spec.rb +36 -0
- data/spec/appsignal/capistrano_spec.rb +81 -0
- data/spec/appsignal/cli_spec.rb +124 -0
- data/spec/appsignal/config_spec.rb +40 -0
- data/spec/appsignal/exception_notification_spec.rb +12 -0
- data/spec/appsignal/inactive_railtie_spec.rb +30 -0
- data/spec/appsignal/marker_spec.rb +83 -0
- data/spec/appsignal/middleware_spec.rb +73 -0
- data/spec/appsignal/railtie_spec.rb +54 -0
- data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +49 -0
- data/spec/appsignal/transaction/params_sanitizer_spec.rb +68 -0
- data/spec/appsignal/transaction/regular_request_formatter_spec.rb +14 -0
- data/spec/appsignal/transaction/slow_request_formatter_spec.rb +76 -0
- data/spec/appsignal/transaction/transaction_formatter_spec.rb +178 -0
- data/spec/appsignal/transaction_spec.rb +191 -0
- data/spec/appsignal/transmitter_spec.rb +64 -0
- data/spec/appsignal_spec.rb +66 -0
- data/spec/generators/appsignal/appsignal_generator_spec.rb +222 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/delegate_matcher.rb +39 -0
- metadata +247 -0
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Appsignal::Agent do
|
4
|
+
let(:transaction) { stub(
|
5
|
+
:name => 'transaction',
|
6
|
+
:exception? => false,
|
7
|
+
:action => 'something#else'
|
8
|
+
) }
|
9
|
+
|
10
|
+
describe '#add_to_queue' do
|
11
|
+
before do
|
12
|
+
@agent = Appsignal::Agent.new
|
13
|
+
@exception_transaction = stub(
|
14
|
+
:name => 'exception',
|
15
|
+
:exception? => true,
|
16
|
+
:action => 'controller#action1'
|
17
|
+
)
|
18
|
+
@slow_transaction = stub(
|
19
|
+
:name => 'slow',
|
20
|
+
:action => 'controller#action1',
|
21
|
+
:exception? => false,
|
22
|
+
:process_action_event => stub(
|
23
|
+
:duration => 250.0
|
24
|
+
)
|
25
|
+
)
|
26
|
+
@slower_transaction = stub(
|
27
|
+
:name => 'slow',
|
28
|
+
:action => 'controller#action1',
|
29
|
+
:exception? => false,
|
30
|
+
:process_action_event => stub(
|
31
|
+
:duration => 300.0
|
32
|
+
)
|
33
|
+
)
|
34
|
+
@other_slow_transaction = stub(
|
35
|
+
:name => 'slow',
|
36
|
+
:action => 'controller#action1',
|
37
|
+
:exception? => false,
|
38
|
+
:process_action_event => stub(
|
39
|
+
:duration => 260.0
|
40
|
+
)
|
41
|
+
)
|
42
|
+
@slow_transaction_in_other_action = stub(
|
43
|
+
:name => 'slow',
|
44
|
+
:action => 'controller#action2',
|
45
|
+
:exception? => false,
|
46
|
+
:process_action_event => stub(
|
47
|
+
:duration => 400.0
|
48
|
+
)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
subject { @agent }
|
52
|
+
|
53
|
+
context "an exception transaction" do
|
54
|
+
before do
|
55
|
+
@exception_transaction.should_not_receive(:clear_payload_and_events!)
|
56
|
+
subject.add_to_queue(@exception_transaction)
|
57
|
+
end
|
58
|
+
|
59
|
+
its(:queue) { should include(@exception_transaction) }
|
60
|
+
its(:slowest_transactions) { should be_empty }
|
61
|
+
|
62
|
+
context "a slow transaction" do
|
63
|
+
before do
|
64
|
+
subject.add_to_queue(@slow_transaction)
|
65
|
+
end
|
66
|
+
|
67
|
+
its(:queue) { should include(@slow_transaction) }
|
68
|
+
its(:slowest_transactions) { should == {
|
69
|
+
'controller#action1' => @slow_transaction
|
70
|
+
} }
|
71
|
+
|
72
|
+
context "a slower transaction in the same action" do
|
73
|
+
before do
|
74
|
+
@slow_transaction.should_receive(:clear_payload_and_events!)
|
75
|
+
@slower_transaction.should_not_receive(:clear_payload_and_events!)
|
76
|
+
subject.add_to_queue(@slower_transaction)
|
77
|
+
end
|
78
|
+
|
79
|
+
its(:queue) { should include(@slower_transaction) }
|
80
|
+
its(:slowest_transactions) { should == {
|
81
|
+
'controller#action1' => @slower_transaction
|
82
|
+
} }
|
83
|
+
|
84
|
+
context "a slow but not the slowest transaction in the same action" do
|
85
|
+
before do
|
86
|
+
@other_slow_transaction.should_receive(:clear_payload_and_events!)
|
87
|
+
subject.add_to_queue(@other_slow_transaction)
|
88
|
+
end
|
89
|
+
|
90
|
+
its(:queue) { should include(@other_slow_transaction) }
|
91
|
+
its(:slowest_transactions) { should == {
|
92
|
+
'controller#action1' => @slower_transaction
|
93
|
+
} }
|
94
|
+
end
|
95
|
+
|
96
|
+
context "an even slower transaction in a different action" do
|
97
|
+
before do
|
98
|
+
@slow_transaction_in_other_action.should_not_receive(:clear_payload_and_events!)
|
99
|
+
subject.add_to_queue(@slow_transaction_in_other_action)
|
100
|
+
end
|
101
|
+
|
102
|
+
its(:queue) { should include(@slow_transaction_in_other_action) }
|
103
|
+
its(:slowest_transactions) { should == {
|
104
|
+
'controller#action1' => @slower_transaction,
|
105
|
+
'controller#action2' => @slow_transaction_in_other_action
|
106
|
+
} }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#send_queue" do
|
114
|
+
it "transmits" do
|
115
|
+
subject.stub(:queue => [stub(:to_hash => 'foo')])
|
116
|
+
subject.transmitter.should_receive(:transmit).with(['foo'])
|
117
|
+
end
|
118
|
+
|
119
|
+
it "handles the return code" do
|
120
|
+
subject.transmitter.stub(:transmit => '200')
|
121
|
+
subject.should_receive(:handle_result).with('200')
|
122
|
+
end
|
123
|
+
|
124
|
+
it "handles exceptions in transmit" do
|
125
|
+
subject.transmitter.stub(:transmit).and_raise(Exception.new)
|
126
|
+
subject.should_receive(:handle_result).with(nil)
|
127
|
+
Appsignal.logger.should_receive(:error).with('Exception while communicating with AppSignal: Exception')
|
128
|
+
end
|
129
|
+
|
130
|
+
after { subject.send_queue }
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#handle_result' do
|
134
|
+
before { subject.add_to_queue(transaction) }
|
135
|
+
before { subject.instance_variable_set(:@sleep_time, 3.0) }
|
136
|
+
|
137
|
+
context "good responses" do
|
138
|
+
before { subject.handle_result(code) }
|
139
|
+
|
140
|
+
context "with 200" do
|
141
|
+
let(:code) { '200' }
|
142
|
+
|
143
|
+
its(:queue) { should be_empty }
|
144
|
+
its(:slowest_transactions) { should be_empty }
|
145
|
+
end
|
146
|
+
|
147
|
+
context "with 420" do
|
148
|
+
let(:code) { '420' }
|
149
|
+
|
150
|
+
its(:queue) { should be_empty }
|
151
|
+
its(:slowest_transactions) { should be_empty }
|
152
|
+
its(:sleep_time) { should == 4.5 }
|
153
|
+
end
|
154
|
+
|
155
|
+
context "with 413" do
|
156
|
+
let(:code) { '413' }
|
157
|
+
|
158
|
+
its(:queue) { should be_empty }
|
159
|
+
its(:slowest_transactions) { should be_empty }
|
160
|
+
its(:sleep_time) { should == 2.0 }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "bad responses" do
|
165
|
+
context "with 429" do
|
166
|
+
let(:code) { '429' }
|
167
|
+
|
168
|
+
it "calls a stop to logging" do
|
169
|
+
subject.should_receive :stop_logging
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "with 406" do
|
174
|
+
let(:code) { '406' }
|
175
|
+
|
176
|
+
it "calls a stop to logging" do
|
177
|
+
subject.should_receive :stop_logging
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "with 402" do
|
182
|
+
let(:code) { '402' }
|
183
|
+
|
184
|
+
it "calls a stop to logging" do
|
185
|
+
subject.should_receive :stop_logging
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "with 401" do
|
190
|
+
let(:code) { '401' }
|
191
|
+
|
192
|
+
it "calls a stop to logging" do
|
193
|
+
subject.should_receive :stop_logging
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "any other response" do
|
198
|
+
let(:code) { 'any other response' }
|
199
|
+
|
200
|
+
it "calls retry_once" do
|
201
|
+
subject.should_receive :retry_once
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
after { subject.handle_result(code) }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "#good_response" do
|
210
|
+
before do
|
211
|
+
subject.instance_variable_set(:@retry_once, false)
|
212
|
+
subject.add_to_queue(transaction)
|
213
|
+
subject.send :good_response
|
214
|
+
end
|
215
|
+
|
216
|
+
its(:queue) { should be_empty }
|
217
|
+
its(:slowest_transactions) { should be_empty }
|
218
|
+
|
219
|
+
it "allows the next request to be retried" do
|
220
|
+
subject.instance_variable_get(:@retry_request).should be_true
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
describe "#retry_once" do
|
225
|
+
before do
|
226
|
+
subject.add_to_queue(transaction)
|
227
|
+
subject.send :retry_once
|
228
|
+
end
|
229
|
+
|
230
|
+
context "on time," do
|
231
|
+
its(:queue) { should == [transaction] }
|
232
|
+
its(:slowest_transactions) { should == {
|
233
|
+
'something#else' => transaction
|
234
|
+
} }
|
235
|
+
|
236
|
+
context "two times" do
|
237
|
+
before { subject.send :retry_once }
|
238
|
+
|
239
|
+
its(:queue) { should be_empty }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "#stop_logging" do
|
245
|
+
it "does not raise exceptions" do
|
246
|
+
expect { subject.send :stop_logging }.not_to raise_error
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe "when inactive" do
|
251
|
+
before { Appsignal.stub(:active? => false) }
|
252
|
+
|
253
|
+
it "should not start a new thread" do
|
254
|
+
Thread.should_not_receive(:new)
|
255
|
+
end
|
256
|
+
|
257
|
+
after { Appsignal::Agent.new }
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Appsignal::AuthCheck do
|
4
|
+
let(:auth_check) { Appsignal::AuthCheck.new('production') }
|
5
|
+
before do
|
6
|
+
@transmitter = mock
|
7
|
+
Appsignal::Transmitter.should_receive(:new).
|
8
|
+
with('http://localhost:3000/1', 'auth', 'abc').
|
9
|
+
and_return(@transmitter)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#perform" do
|
13
|
+
it "should not transmit any extra data" do
|
14
|
+
@transmitter.should_receive(:transmit).with({}).and_return({})
|
15
|
+
auth_check.perform
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#uri" do
|
20
|
+
before do
|
21
|
+
@transmitter.should_receive(:transmit)
|
22
|
+
auth_check.perform
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should delegate to transmitter" do
|
26
|
+
@transmitter.should_receive(:uri)
|
27
|
+
auth_check.uri
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return uri" do
|
31
|
+
@transmitter.should_receive(:uri).
|
32
|
+
and_return('http://appsignal.com/1/auth')
|
33
|
+
auth_check.uri.should == 'http://appsignal.com/1/auth'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'appsignal/capistrano'
|
3
|
+
require 'capistrano/configuration'
|
4
|
+
|
5
|
+
describe Appsignal::Capistrano do
|
6
|
+
before :all do
|
7
|
+
@config = Capistrano::Configuration.new
|
8
|
+
Appsignal::Capistrano.tasks(@config)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should have a deploy task" do
|
12
|
+
@config.find_task('appsignal:deploy').should_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "appsignal:deploy task" do
|
16
|
+
before :all do
|
17
|
+
@config.set(:rails_env, 'development')
|
18
|
+
@config.set(:repository, 'master')
|
19
|
+
@config.set(:deploy_to, '/home/username/app')
|
20
|
+
@config.set(:current_release, '')
|
21
|
+
@config.set(:current_revision, '503ce0923ed177a3ce000005')
|
22
|
+
ENV['USER'] = 'batman'
|
23
|
+
end
|
24
|
+
|
25
|
+
context "send marker" do
|
26
|
+
let(:marker_data) {
|
27
|
+
{
|
28
|
+
:revision => "503ce0923ed177a3ce000005",
|
29
|
+
:repository => "master",
|
30
|
+
:user => "batman"
|
31
|
+
}
|
32
|
+
}
|
33
|
+
before :all do
|
34
|
+
@io = StringIO.new
|
35
|
+
@logger = Capistrano::Logger.new(:output => @io)
|
36
|
+
@logger.level = Capistrano::Logger::MAX_LEVEL
|
37
|
+
@config.logger = @logger
|
38
|
+
end
|
39
|
+
before do
|
40
|
+
@marker = Appsignal::Marker.new(marker_data, Rails.root.to_s,
|
41
|
+
'development', @logger)
|
42
|
+
Appsignal::Marker.should_receive(:new).
|
43
|
+
with(marker_data, Rails.root.to_s, 'development', anything()).
|
44
|
+
and_return(@marker)
|
45
|
+
end
|
46
|
+
|
47
|
+
context "proper setup" do
|
48
|
+
before do
|
49
|
+
@transmitter = mock()
|
50
|
+
Appsignal::Transmitter.should_receive(:new).and_return(@transmitter)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should transmit data" do
|
54
|
+
@transmitter.should_receive(:transmit).and_return('200')
|
55
|
+
@config.find_and_execute_task('appsignal:deploy')
|
56
|
+
@io.string.should include('** Notifying Appsignal of deploy...')
|
57
|
+
@io.string.should include('** Appsignal has been notified of this '\
|
58
|
+
'deploy!')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not transmit data" do
|
63
|
+
@config.find_and_execute_task('appsignal:deploy')
|
64
|
+
@io.string.should include('** Notifying Appsignal of deploy...')
|
65
|
+
@io.string.should include('** Something went wrong while trying to '\
|
66
|
+
'notify Appsignal:')
|
67
|
+
end
|
68
|
+
|
69
|
+
context "dry run" do
|
70
|
+
before(:all) { @config.dry_run = true }
|
71
|
+
|
72
|
+
it "should not send deploy marker" do
|
73
|
+
@marker.should_not_receive(:transmit)
|
74
|
+
@config.find_and_execute_task('appsignal:deploy')
|
75
|
+
@io.string.should include('** Dry run: Deploy marker not actually '\
|
76
|
+
'sent.')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Appsignal::CLI do
|
4
|
+
let(:out_stream) { StringIO.new }
|
5
|
+
let(:error_stream) { StringIO.new }
|
6
|
+
let(:cli) { Appsignal::CLI }
|
7
|
+
before :each do
|
8
|
+
$stdout, $stderr = out_stream, error_stream
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#logger" do
|
12
|
+
it "should be a logger" do
|
13
|
+
cli.logger.should be_instance_of(Logger)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should print a message if there is no config file" do
|
18
|
+
File.stub(:exists? => false)
|
19
|
+
lambda {
|
20
|
+
cli.run([])
|
21
|
+
}.should raise_error(SystemExit)
|
22
|
+
out_stream.string.should include 'No config file present at config/appsignal.yml'
|
23
|
+
out_stream.string.should include 'Log in to https://appsignal.com to get instructions on how to generate the config file.'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should print the help with no arguments, -h and --help" do
|
27
|
+
[nil, '-h', '--help'].each do |arg|
|
28
|
+
lambda {
|
29
|
+
cli.run([arg].compact)
|
30
|
+
}.should raise_error(SystemExit)
|
31
|
+
|
32
|
+
out_stream.string.should include 'appsignal <command> [options]'
|
33
|
+
out_stream.string.should include 'Available commands: notify_of_deploy'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should print the version with -v and --version" do
|
38
|
+
['-v', '--version'].each do |arg|
|
39
|
+
lambda {
|
40
|
+
cli.run([arg])
|
41
|
+
}.should raise_error(SystemExit)
|
42
|
+
|
43
|
+
out_stream.string.should include 'Appsignal'
|
44
|
+
out_stream.string.should include '.'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should print a notice if a command does not exist" do
|
49
|
+
lambda {
|
50
|
+
cli.run(['nonsense'])
|
51
|
+
}.should raise_error(SystemExit)
|
52
|
+
|
53
|
+
out_stream.string.should include "Command 'nonsense' does not exist, run appsignal -h to see the help"
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#validate_required_options" do
|
57
|
+
let(:required_options) { [:option_1, :option_2, :option_3] }
|
58
|
+
|
59
|
+
it "should do nothing with all options supplied" do
|
60
|
+
cli.validate_required_options(
|
61
|
+
required_options,
|
62
|
+
:option_1 => 1,
|
63
|
+
:option_2 => 2,
|
64
|
+
:option_3 => 3
|
65
|
+
)
|
66
|
+
out_stream.string.should be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should print a message with one option missing" do
|
70
|
+
lambda {
|
71
|
+
cli.validate_required_options(
|
72
|
+
required_options,
|
73
|
+
:option_1 => 1,
|
74
|
+
:option_2 => 2
|
75
|
+
)
|
76
|
+
}.should raise_error(SystemExit)
|
77
|
+
out_stream.string.should include("Missing options: option_3")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should print a message with multiple options missing" do
|
81
|
+
lambda {
|
82
|
+
cli.validate_required_options(
|
83
|
+
required_options,
|
84
|
+
:option_1 => 1,
|
85
|
+
:option_2 => ''
|
86
|
+
)
|
87
|
+
}.should raise_error(SystemExit)
|
88
|
+
out_stream.string.should include("Missing options: option_2, option_3")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "notify_of_deploy" do
|
93
|
+
it "should validate that all options have been supplied" do
|
94
|
+
options = {}
|
95
|
+
cli.should_receive(:validate_required_options).with(
|
96
|
+
[:revision, :repository, :user, :environment],
|
97
|
+
options
|
98
|
+
)
|
99
|
+
cli.notify_of_deploy(options)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should notify of a deploy" do
|
103
|
+
transmitter = double
|
104
|
+
Appsignal::Transmitter.should_receive(:new).with(
|
105
|
+
'http://localhost:3000/1',
|
106
|
+
'markers',
|
107
|
+
'abc'
|
108
|
+
).and_return(transmitter)
|
109
|
+
transmitter.should_receive(:transmit).with(
|
110
|
+
:revision => 'aaaaa',
|
111
|
+
:repository => 'git@github.com:our/project.git',
|
112
|
+
:user => 'thijs'
|
113
|
+
)
|
114
|
+
|
115
|
+
cli.run([
|
116
|
+
'notify_of_deploy',
|
117
|
+
'--revision=aaaaa',
|
118
|
+
'--repository=git@github.com:our/project.git',
|
119
|
+
'--user=thijs',
|
120
|
+
'--environment=production'
|
121
|
+
])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|