Skip to content

Commit

Permalink
Merge pull request #2 from senid231/add-test-gauge-with-time
Browse files Browse the repository at this point in the history
add tests for gauge_with_time, add rspec matchers
  • Loading branch information
senid231 authored Nov 29, 2023
2 parents ed137d0 + b58fd29 commit 1c704a2
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 4 deletions.
5 changes: 5 additions & 0 deletions lib/prometheus_exporter/ext/metric/gauge_with_time.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'prometheus_exporter/metric'
require 'date'

module PrometheusExporter::Ext::Metric
class GaugeWithTime < PrometheusExporter::Metric::Gauge
Expand Down Expand Up @@ -41,6 +42,10 @@ def decrement(labels = {}, value = 1)
result
end

def to_h
super.to_h { |labels, value| [labels, [value, timestamps[labels]]] }
end

private

def update_timestamp(labels)
Expand Down
7 changes: 7 additions & 0 deletions lib/prometheus_exporter/ext/rspec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

require_relative 'rspec/matchers'

RSpec.configure do |config|
config.include PrometheusExporter::Ext::RSpec::Matchers
end
40 changes: 40 additions & 0 deletions lib/prometheus_exporter/ext/rspec/matchers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require 'date'
require_relative 'metric_matcher'

module PrometheusExporter::Ext::RSpec
module Matchers
def a_prometheus_metric(klass, name)
MetricMatcher.new(klass, name)
end

# Matches approximate milliseconds since epoch
# @param date_time [DateTime] default DateTime.now
# @param delta [Integer] default 1000 ms
# @return [RSpec::Matchers::BuiltIn::BeWithin]
def ms_since_epoch(date_time: DateTime.now, delta: 1_000)
be_within(delta).of(date_time.strftime('%Q').to_i)
end

def a_gauge_metric(name)
a_prometheus_metric(PrometheusExporter::Metric::Gauge, name)
end

def a_gauge_with_time_metric(name)
a_prometheus_metric(PrometheusExporter::Ext::Metric::GaugeWithTime, name)
end

def a_counter_metric(name)
a_prometheus_metric(PrometheusExporter::Metric::Counter, name)
end

def a_histogram_metric(name)
a_prometheus_metric(PrometheusExporter::Metric::Histogram, name)
end

def a_summary_metric(name)
a_prometheus_metric(PrometheusExporter::Metric::Summary, name)
end
end
end
45 changes: 45 additions & 0 deletions lib/prometheus_exporter/ext/rspec/metric_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module PrometheusExporter::Ext::RSpec
class MetricMatcher
include RSpec::Matchers::DSL::DefaultImplementations
include RSpec::Matchers
include RSpec::Matchers::Composable

attr_reader :metric_class, :metric_name, :metric_payload, :actual

def initialize(metric_class, metric_name)
@metric_class = metric_class
@metric_name = metric_name.to_s
@metric_payload = nil
end

def name
'be a prometheus metric'
end

def expected
"#{metric_class}(name=#{metric_name}, to_h=#{description_of(metric_payload)})"
end

def matches?(actual)
@actual = actual

return false unless values_match?(metric_class, actual.class)
return false unless values_match?(metric_name, actual.name.to_s)
return false if !metric_payload.nil? && !values_match?(metric_payload, actual.to_h)

true
end

def with(value, labels)
@metric_payload ||= {}
metric_payload[labels.transform_keys(&:to_s)] = value
self
end

def description_of(object)
RSpec::Support::ObjectFormatter.new(nil).format(object)
end
end
end
98 changes: 98 additions & 0 deletions spec/prometheus_exporter/ext/metric/gauge_with_time_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

require 'prometheus_exporter/ext/metric/gauge_with_time'

RSpec.describe PrometheusExporter::Ext::Metric::GaugeWithTime do
describe '#to_prometheus_text' do
subject { metric.to_prometheus_text }

let(:metric) { described_class.new('metric_name', 'help_msg') }

it 'returns correct text' do
expect(subject).to eq("# HELP metric_name help_msg\n# TYPE metric_name gauge\n\n")
end

context 'with metric_text' do
let(:metric_text) { "metric_text 1\nmetric_text 2" }

before { allow(metric).to receive(:metric_text).once.and_return(metric_text) }

it 'returns correct text' do
title = "# HELP metric_name help_msg\n# TYPE metric_name gauge"
expect(subject).to eq("#{title}\n#{metric_text}\n")
end
end
end

describe '#metric_text' do
subject { metric.metric_text.split("\n") }

let(:metric) { described_class.new('metric_name', 'help_msg') }

it 'returns correct text' do
expect(subject).to eq([])
end

it 'has correct to_h' do
expect(metric.to_h).to eq({})
end

context 'when metric has data with stubbed timestamps' do
let!(:date_time_old) { DateTime.now - 25 }
let!(:date_time_new) { DateTime.now }

before do
allow(DateTime).to receive(:now).once.and_return(date_time_old)
metric.observe(1, 'foo' => 'bar')

allow(DateTime).to receive(:now).once.and_return(date_time_new)
metric.observe(2, 'baz' => 'boo')
end

it 'returns correct text' do
expect(subject).to contain_exactly(
%(metric_name{foo="bar"} 1 #{date_time_old.strftime('%Q').to_i}),
%(metric_name{baz="boo"} 2 #{date_time_new.strftime('%Q').to_i})
)
end

it 'has correct to_h' do
expect(metric.to_h).to match(
{
{ 'foo' => 'bar' } => [1, date_time_old.strftime('%Q').to_i],
{ 'baz' => 'boo' } => [2, date_time_new.strftime('%Q').to_i]
}
)
end

it 'matches a_gauge_with_time_metric' do
expect(metric).to a_gauge_with_time_metric('metric_name')
.with([1, date_time_old.strftime('%Q').to_i], foo: 'bar')
.with([2, date_time_new.strftime('%Q').to_i], baz: 'boo')
end
end

context 'when metric has data without stubbed timestamps' do
before do
metric.observe(1, 'foo' => 'bar')
sleep 0.2
metric.observe(2, 'baz' => 'boo')
end

it 'has correct to_h' do
expect(metric.to_h).to match(
{
{ 'foo' => 'bar' } => [1, ms_since_epoch],
{ 'baz' => 'boo' } => [2, ms_since_epoch]
}
)
end

it 'matches a_gauge_with_time_metric' do
expect(metric).to a_gauge_with_time_metric('metric_name')
.with([1, ms_since_epoch], foo: 'bar')
.with([2, ms_since_epoch], baz: 'boo')
end
end
end
end
4 changes: 0 additions & 4 deletions spec/prometheus_exporter/ext_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,4 @@
it 'has a version number' do
expect(PrometheusExporter::Ext::VERSION).not_to be_nil
end

it 'does something useful' do
expect(false).to be(true)
end
end
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'prometheus_exporter/ext'
require 'prometheus_exporter/ext/rspec/matchers'

RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
Expand All @@ -12,4 +13,6 @@
config.expect_with :rspec do |c|
c.syntax = :expect
end

config.include PrometheusExporter::Ext::RSpec::Matchers
end

0 comments on commit 1c704a2

Please sign in to comment.