diff --git a/Gemfile b/Gemfile index c9783d3..3755afd 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,5 @@ source 'https://rubygems.org' # Specify your gem's dependencies in harness.gemspec gemspec + +gem 'mutant', git: 'https://github.com/ahawkins/mutant', branch: 'minitest' diff --git a/Rakefile b/Rakefile index 877cc48..74f44fa 100644 --- a/Rakefile +++ b/Rakefile @@ -7,4 +7,8 @@ Rake::TestTask.new(:test) do |test| test.pattern = 'test/**/*_test.rb' end +task :mutant do + sh "bundle exec mutant -I lib -r harness --use minitest '::Harness'" +end + task :default => :test diff --git a/lib/harness.rb b/lib/harness.rb index 3272154..3ef0dd4 100644 --- a/lib/harness.rb +++ b/lib/harness.rb @@ -1,89 +1,47 @@ require "harness/version" require 'statsd' +require 'singleton' module Harness - class Config + Config = Class.new do + include Singleton attr_accessor :collector, :queue - end - - Measurement = Struct.new(:name, :value, :rate) do - def self.from_event(event) - payload = event.payload - value = payload.fetch name.split('::').last.downcase.to_sym - - case value - when true - new event.name - when Fixnum - new event.name, value - when String - new value - when Hash - new(value[:name] || event.name, value[:value], value[:rate]) - end - end - - def sample_rate - rate.nil? ? 1 : rate - end - end - - class Timer < Measurement - def self.from_event(event) - timer = super - timer.value = event.duration - timer - end - - def ms - value - end - - def log - Harness.timing name, ms, sample_rate - end - end - - class Counter < Measurement - def log - if value.nil? - Harness.increment name, sample_rate - else - Harness.count name, value, sample_rate - end - end - end + end.instance def self.config - @config ||= Config.new + Config end - def self.increment(*args) - queue.push [:increment, args] + def self.increment(stat, sample_rate = 1) + queue.push [:increment, [stat, sample_rate]] end - def self.decrement(*args) - queue.push [:decrement, args] + def self.decrement(stat, sample_rate = 1) + queue.push [:decrement, [stat, sample_rate]] end - def self.timing(*args) - queue.push [:timing, args] + def self.count(stat, value, sample_rate = 1) + queue.push [:count, [stat, value, sample_rate]] end - def self.count(*args) - queue.push [:count, args] + def self.timing(start, ms, sample_rate = 1) + queue.push [:timing, [start, ms, sample_rate]] end def self.time(stat, sample_rate = 1) start = Time.now result = yield - timing(stat, ((Time.now - start) * 1000).round, sample_rate) + timing(stat, delta(start), sample_rate) result end - def self.gauge(*args) - queue.push [:gauge, args] + def self.delta(start, finish = Time.now) + ((finish - start) * 1000).round + end + + def self.gauge(stat, value, sample_rate = 1) + queue.push [:gauge, [stat, value, sample_rate]] end def self.queue diff --git a/lib/harness/async_queue.rb b/lib/harness/async_queue.rb index 6974a55..352d03c 100644 --- a/lib/harness/async_queue.rb +++ b/lib/harness/async_queue.rb @@ -2,10 +2,11 @@ module Harness class AsyncQueue - attr_reader :consumer, :queue + attr_reader :queue, :consumer + + def initialize(queue = Queue.new, collector = Harness.collector) + @queue = queue - def initialize - @queue = Queue.new @consumer = Thread.new do loop do msg = queue.pop @@ -22,8 +23,12 @@ def push(msg) queue.push msg end - def collector - Harness.collector + def stop + consumer.kill + end + + def up? + consumer.alive? end end end diff --git a/lib/harness/fake_collector.rb b/lib/harness/fake_collector.rb index 22e24a5..ed8be7b 100644 --- a/lib/harness/fake_collector.rb +++ b/lib/harness/fake_collector.rb @@ -1,8 +1,8 @@ module Harness class FakeCollector - Increment = Struct.new(:name, :amount, :rate) - Decrement = Struct.new(:name, :amount, :rate) - Gauge = Struct.new(:name, :value, :rate) + Increment = Struct.new(:name, :rate) + Decrement = Struct.new(:name, :rate) + Measurement = Struct.new(:name, :value, :rate) attr_reader :gauges, :counters, :timers, :increments, :decrements @@ -11,14 +11,7 @@ def initialize end def timing(*args) - timers << Harness::Timer.new(*args) - end - - def time(stat, sample_rate = 1) - start = Time.now - result = yield - timing(stat, ((Time.now - start) * 1000).round, sample_rate) - result + timers << Measurement.new(*args) end def increment(*args) @@ -30,11 +23,11 @@ def decrement(*args) end def count(*args) - counters << Harness::Counter.new(*args) + counters << Measurement.new(*args) end def gauge(*args) - gauges << Gauge.new(*args) + gauges << Measurement.new(*args) end end end diff --git a/lib/harness/null_collector.rb b/lib/harness/null_collector.rb index 440a0ae..c8ec675 100644 --- a/lib/harness/null_collector.rb +++ b/lib/harness/null_collector.rb @@ -12,8 +12,8 @@ def count(*args) end - def time(*args, &block) - yield block + def time(*args) + yield end def timing(*args) diff --git a/test/acceptance_test.rb b/test/acceptance_test.rb index f418f00..8693419 100644 --- a/test/acceptance_test.rb +++ b/test/acceptance_test.rb @@ -17,4 +17,11 @@ def test_works_with_actual_statsd true end end + + def test_time_deltas_are_calculated_in_milliseconds + start = Time.now + finish = start + 2.5 + + assert_equal 2500, Harness.delta(start, finish) + end end diff --git a/test/async_queue_test.rb b/test/async_queue_test.rb index b606332..7c462f9 100644 --- a/test/async_queue_test.rb +++ b/test/async_queue_test.rb @@ -1,14 +1,69 @@ require_relative 'test_helper' -Thread.abort_on_exception = true class AsyncQueueTest < MiniTest::Unit::TestCase + attr_reader :queue, :collector + + class NullQueue + def push(*) + + end + end + + def setup + super + Harness.config.queue = NullQueue.new + + @collector = Harness::FakeCollector.new + @queue = Harness::AsyncQueue.new Queue.new, collector + assert queue.up?, 'Precondition: consumer not running' + end + + def teardown + queue.stop + super + end + + def test_proceses_queue_after_instantiating + q = Queue.new + q << [:gauge, ['foo', 5]] + + @queue = Harness::AsyncQueue.new q, collector + + sleep 0.1 + + assert queue.up?, 'Queue should be working' + + assert_gauge 'foo' + end + def test_uses_consumer_thread_to_not_block_the_main_thread - queue = Harness::AsyncQueue.new + queue.push [:gauge, ['foo', 5]] + + sleep 0.1 + + assert queue.up?, 'Queue should be working' + + assert_gauge 'foo' + end + + def test_use_configured_collector_by_default + Harness.config.collector = collector + + @queue = Harness::AsyncQueue.new queue.push [:gauge, ['foo', 5]] sleep 0.1 + assert queue.up?, 'Queue should be working' + assert_gauge 'foo' end + + def test_consumer_can_be_stopped + assert queue.up?, 'Consumer must be alive' + queue.stop + sleep 0.1 + refute queue.up?, 'Consumer should be killed' + end end diff --git a/test/instrumentation_test.rb b/test/instrumentation_test.rb index 3a01245..412571e 100644 --- a/test/instrumentation_test.rb +++ b/test/instrumentation_test.rb @@ -8,41 +8,121 @@ class Worker attr_reader :worker def setup + super @worker = Worker.new end - def test_can_use_increments - worker.increment 'foo', 0.5 - assert_increment 'foo' + def test_increment + worker.increment 'foo' + + assert_increment 'foo' do |counter| + assert_equal 1, counter.rate + end + + worker.increment 'bar', 0.5 + + assert_increment 'bar' do |counter| + assert_equal 0.5, counter.rate, 'Default sample rate incorrect' + end end - def test_can_use_decrements - worker.decrement 'foo', 0.5 - assert_decrement 'foo' + def test_decrement + worker.decrement 'foo' + + assert_decrement 'foo' do |counter| + assert_equal 1, counter.rate + end + + worker.decrement 'bar', 0.5 + + assert_decrement 'bar' do |counter| + assert_equal 0.5, counter.rate, 'Default sample rate incorrect' + end end - def test_can_use_count - worker.count 'foo', 1, 0.5 - assert_counter 'foo' + def test_count + worker.count 'foo', 6 + + assert_counter 'foo' do |counter| + assert_equal 6, counter.value + assert_equal 1, counter.rate, 'Default sample rate incorrect' + end + + worker.count 'bar', 1, 0.5 + + assert_counter 'bar' do |counter| + assert_equal 1, counter.value + assert_equal 0.5, counter.rate + end end def test_can_use_gauges - worker.gauge 'foo', 5, 0.5 - assert_gauge 'foo' + worker.gauge 'foo', 6 + + assert_gauge 'foo' do |gauge| + assert_equal 6, gauge.value + assert_equal 1, gauge.rate, 'Default sample rate incorrect' + end + + worker.gauge 'bar', 1, 0.5 + + assert_gauge 'bar' do |gauge| + assert_equal 1, gauge.value + assert_equal 0.5, gauge.rate + end end def test_can_use_timings - worker.timing 'foo', 5, 0.5 - assert_timer 'foo' + worker.timing 'foo', 6 + + assert_timer 'foo' do |timer| + assert_equal 6, timer.value + assert_equal 1, timer.rate, 'Default sample rate incorrect' + end + + worker.timing 'bar', 1, 0.5 + + assert_timer 'bar' do |timer| + assert_equal 1, timer.value + assert_equal 0.5, timer.rate + end end def test_can_be_timed - result = worker.time 'foo', 0.5 do + result = worker.time 'foo' do + 'bar' + end + + assert_timer 'foo' do |timer| + assert_instance_of Fixnum, timer.value + assert_equal 1, timer.rate, 'Default sample rate incorrect' + end + + result = worker.time 'baz', 0.5 do + 'bar' + end + + assert_timer 'baz' do |timer| + assert_equal 0.5, timer.rate + end + end + + def test_time_returns_block_value + result = worker.time 'foo' do 'bar' end assert_equal 'bar', result, "#time did not return block's value" - assert_timer 'foo' + end + + def test_timer_use_1_for_default_sample_rate + result = worker.time 'foo' do + 'bar' + end + + assert_timer 'foo' do |timer| + assert_equal 1, timer.rate + end end def test_also_works_with_extend diff --git a/test/null_collector_test.rb b/test/null_collector_test.rb index 9adb928..a669055 100644 --- a/test/null_collector_test.rb +++ b/test/null_collector_test.rb @@ -8,11 +8,15 @@ def setup end def test_respond_to_increment - statsd.increment 'foo', 5, 0.5 + statsd.increment 'foo', 0.5 end def test_respond_to_decrement - statsd.decrement 'foo', 5, 0.5 + statsd.decrement 'foo', 0.5 + end + + def test_respond_to_count + statsd.count 'foo', 5, 0.5 end def test_responds_to_timing diff --git a/test/test_helper.rb b/test/test_helper.rb index 97a9a49..324c848 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,14 +1,12 @@ require 'bundler/setup' -require 'simplecov' -SimpleCov.start - require 'harness' require 'minitest/unit' -require 'minitest/autorun' -Thread.abort_on_exception = true +require 'mutant-minitest' + +require 'minitest/autorun' unless Mutant::Minitest.active? class MiniTest::Unit::TestCase def setup @@ -20,30 +18,35 @@ def assert_timer(name) refute_empty timers timer = timers.find { |t| t.name == name } assert timer, "Timer #{name} not logged!" + yield timer if block_given? end def assert_increment(name) refute_empty increments increment = increments.find { |i| i.name == name } assert increment, "Increment #{name} not logged!" + yield increment if block_given? end def assert_decrement(name) refute_empty decrements decrement = decrements.find { |i| i.name == name } assert decrement, "decrement #{name} not logged!" + yield decrement if block_given? end def assert_gauge(name) refute_empty gauges gauge = gauges.find { |g| g.name == name } assert gauge, "gauge #{name} not logged!" + yield gauge if block_given? end def assert_counter(name) refute_empty counters counter = counters.find { |g| g.name == name } assert counter, "counter #{name} not logged!" + yield counter if block_given? end def collector