spec/elastic_apm_spec.rb (339 lines of code) (raw):

# Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright # ownership. Elasticsearch B.V. licenses this file to you under # the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # frozen_string_literal: true require 'spec_helper' RSpec.describe ElasticAPM do describe 'life cycle' do it 'starts and stops the agent', :mock_intake do MockIntake.instance.stub! ElasticAPM.start expect(ElasticAPM::Agent).to be_running ElasticAPM.stop expect(ElasticAPM::Agent).to_not be_running end end describe '.restart', :mock_intake do before { MockIntake.instance.stub! } after { ElasticAPM.stop } context 'when the agent is not running' do it 'starts the agent' do ElasticAPM.restart expect(ElasticAPM::Agent).to be_running end end context 'when the agent is already running' do before { ElasticAPM.start } it 'restarts the agent' do expect(ElasticAPM::Agent).to receive(:stop) .at_least(:once).and_call_original expect(ElasticAPM::Agent).to receive(:start) .once.and_call_original ElasticAPM.restart expect(ElasticAPM::Agent).to be_running end end context 'when a new config is passed' do before { ElasticAPM.start } it 'restarts the agent with the new config' do ElasticAPM.restart(api_buffer_size: 10) expect(ElasticAPM::Agent).to be_running expect(ElasticAPM.agent.config.api_buffer_size).to be(10) end end context 'when no new config is passed' do before { ElasticAPM.start(api_buffer_size: 10) } it 'restarts the agent with the same config' do ElasticAPM.restart expect(ElasticAPM::Agent).to be_running expect(ElasticAPM.agent.config.api_buffer_size).to be(10) end end end context 'when running', :mock_intake do before do MockIntake.instance.stub! ElasticAPM.start config end let(:agent) { ElasticAPM.agent } let(:config) { ElasticAPM::Config.new } describe '.log_ids' do context 'with no current_transaction' do it 'returns empty string' do expect(ElasticAPM.log_ids).to eq('') end end context 'with a current transaction' do it 'includes transaction and trace ids' do transaction = ElasticAPM.start_transaction 'Test' expect(ElasticAPM.log_ids).to eq( "transaction.id=#{transaction.id} trace.id=#{transaction.trace_id}" ) end end context 'with a current_span' do it 'includes transaction, span and trace ids' do trans = ElasticAPM.start_transaction span = ElasticAPM.start_span 'Test' expect(ElasticAPM.log_ids).to eq( "transaction.id=#{trans.id} span.id=#{span.id} " \ "trace.id=#{trans.trace_id}" ) end end context 'when passed a block' do it 'yields each id' do transaction = ElasticAPM.start_transaction span = ElasticAPM.start_span 'Test' ElasticAPM.log_ids do |transaction_id, span_id, trace_id| expect(transaction_id).to eq(transaction.id) expect(span_id).to eq(span.id) expect(trace_id).to eq(transaction.trace_id) end end end end describe '.start_transaction' do it 'delegates to agent' do expect(ElasticAPM.agent).to receive(:start_transaction) ElasticAPM.start_transaction end context 'when recording is false' do let(:config) { ElasticAPM::Config.new(recording: false) } it 'does not start a transaction' do ElasticAPM.start_transaction expect(ElasticAPM.current_transaction).to be nil end end end describe '.report' do context 'when recording is false' do let(:config) { ElasticAPM::Config.new(recording: false) } it 'does not report the exception' do ElasticAPM.report(Exception.new) expect(ElasticAPM.agent).not_to receive(:report) end end end describe '.report_message' do context 'when recording is false' do let(:config) { ElasticAPM::Config.new(recording: false) } it 'does not report the message' do ElasticAPM.report_message('this should not be reported') expect(ElasticAPM.agent).not_to receive(:report_message) end end end describe '.end_transaction' do it 'delegates to agent' do expect(ElasticAPM.agent).to receive(:end_transaction) ElasticAPM.end_transaction end end describe '.with_transaction' do subject do ElasticAPM.with_transaction do 'original result' end end it 'delegates to agent' do expect(ElasticAPM.agent).to receive(:start_transaction) expect(ElasticAPM.agent).to receive(:end_transaction) subject end it { should eq 'original result' } end describe '.start_span' do it 'starts a span' do expect(ElasticAPM.agent).to receive(:start_span) ElasticAPM.start_span 'Test' end context 'when recording is false after the transaction is started' do it 'it creates a span' do ElasticAPM.start_transaction ElasticAPM.agent.config.recording = false ElasticAPM.start_span('Test') expect(ElasticAPM.current_transaction).not_to be nil expect(ElasticAPM.current_span).not_to be nil end end context 'when recording is false before the transaction is started' do it 'it does not create a span' do ElasticAPM.agent.config.recording = false ElasticAPM.start_transaction ElasticAPM.start_span('should not exist') expect(ElasticAPM.current_transaction).to be nil expect(ElasticAPM.current_span).to be nil end end end describe '.end_span' do it 'ends current span' do expect(ElasticAPM.agent).to receive(:end_span) ElasticAPM.end_span end end describe '.with_span' do subject do ElasticAPM.with_span('Block test') do 'original result' end end it 'wraps block in span' do expect(ElasticAPM.agent).to receive(:start_span) expect(ElasticAPM.agent).to receive(:end_span) subject end it { should eq 'original result' } end it { should delegate :current_transaction, to: agent } it do should delegate :report, to: agent, args: ['E', { context: nil, handled: nil }] end it do should delegate :report_message, to: agent, args: ['NOT OK', { backtrace: Array, context: nil }] end it { should delegate :set_label, to: agent, args: [nil, nil] } it { should delegate :set_custom_context, to: agent, args: [nil] } it { should delegate :set_user, to: agent, args: [nil] } it do should delegate :set_destination, to: agent, args: [{address: nil, cloud: nil, port: nil, service: nil}] end describe '#add_filter' do it { should delegate :add_filter, to: agent, args: [nil, -> {}] } it 'needs either callback or block' do expect { subject.add_filter(:key) }.to raise_error(ArgumentError) expect do subject.add_filter(:key) { 'ok' } end.to_not raise_error end end after { ElasticAPM.stop } end context 'async spans', :intercept do context 'transaction parent' do it 'allows async spans' do with_agent do transaction = ElasticAPM.start_transaction span1 = Thread.new do ElasticAPM.with_span( 'job 1', parent: transaction, sync: false ) { |span| span } end.value span2 = Thread.new do ElasticAPM.with_span( 'job 2', parent: transaction, sync: false ) { |span| span } end.value transaction.done expect(transaction.started_spans).to eq(2) expect(span1.parent_id).to eq(span2.parent_id) expect(span1.parent_id).to eq( transaction.trace_context.child.parent_id ) expect(span1.context.sync).to be(false) expect(span2.parent_id).to eq( transaction.trace_context.child.parent_id ) expect(span2.context.sync).to be(false) end end context 'span created after transaction is ended' do it 'allows async spans' do with_agent do transaction = ElasticAPM.start_transaction transaction.done span1 = Thread.new do ElasticAPM.with_span( 'job 1', parent: transaction, sync: false ) { |span| span } end.value span2 = Thread.new do ElasticAPM.with_span( 'job 2', parent: transaction, sync: false ) { |span| span } end.value transaction.done expect(transaction.started_spans).to eq(2) expect(span1.parent_id).to eq(span2.parent_id) expect(span1.context.sync).to be(false) expect(span1.parent_id).to eq( transaction.trace_context.child.parent_id ) expect(span2.context.sync).to be(false) expect(span2.parent_id).to eq( transaction.trace_context.child.parent_id ) end end end context '#with_span' do it 'allows async spans' do with_agent do transaction = ElasticAPM.start_transaction span1 = Thread.new do ElasticAPM.with_span( 'job 1', parent: transaction, sync: false ) { |span| span } end.value span2 = Thread.new do ElasticAPM.with_span('job 2', parent: transaction) { |span| span } end.value transaction.done expect(transaction.started_spans).to eq(2) expect(span1.parent_id).to eq(span2.parent_id) expect(span1.parent_id).to eq( transaction.trace_context.child.parent_id ) expect(span2.parent_id).to eq( transaction.trace_context.child.parent_id ) end end end end context 'span parent' do it 'allows async spans' do with_agent do transaction = ElasticAPM.start_transaction span1 = ElasticAPM.with_span 'run all the jobs' do |span| span2 = Thread.new do ElasticAPM.with_span('job 1', parent: span) { |s| s } end.value expect(span2.parent_id).to eq(span.trace_context.child.parent_id) expect(span2.context.sync).to be nil span3 = Thread.new do ElasticAPM.with_span('job 2', parent: span) { |s| s } end.value expect(span3.parent_id).to eq(span.trace_context.child.parent_id) expect(span3.context.sync).to be nil span end transaction.done expect(transaction.started_spans).to eq(3) expect(span1.parent_id).to eq( transaction.trace_context.child.parent_id ) expect(span1.context.sync).to be nil end end end end context 'when not running' do it 'still yields block' do ran = false ElasticAPM.with_transaction { ran = true } expect(ran).to be true end end end