speed_gun 0.0.4 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Rakefile +6 -1
  6. data/lib/speed_gun/app/views/meter.html.slim +4 -9
  7. data/lib/speed_gun/config.rb +33 -42
  8. data/lib/speed_gun/event.rb +72 -0
  9. data/lib/speed_gun/middleware.rb +43 -43
  10. data/lib/speed_gun/profile.rb +102 -0
  11. data/lib/speed_gun/profiler/action_controller_profiler.rb +14 -0
  12. data/lib/speed_gun/profiler/action_view_profiler.rb +11 -0
  13. data/lib/speed_gun/profiler/active_record_profiler.rb +11 -0
  14. data/lib/speed_gun/profiler/active_support_notifications_profiler.rb +29 -0
  15. data/lib/speed_gun/profiler/rack_profiler.rb +7 -0
  16. data/lib/speed_gun/profiler.rb +11 -118
  17. data/lib/speed_gun/railtie.rb +7 -16
  18. data/lib/speed_gun/store/elastic_search_store.rb +64 -0
  19. data/lib/speed_gun/store/fluent_logger_store.rb +29 -0
  20. data/lib/speed_gun/store/memcache_store.rb +40 -0
  21. data/lib/speed_gun/store/memory_store.rb +23 -0
  22. data/lib/speed_gun/store/multiple_store.rb +23 -0
  23. data/lib/speed_gun/store/redis_store.rb +41 -0
  24. data/lib/speed_gun/store.rb +7 -3
  25. data/lib/speed_gun/template.rb +1 -1
  26. data/lib/speed_gun/version.rb +1 -1
  27. data/lib/speed_gun.rb +30 -39
  28. data/spec/lib/speed_gun/config_spec.rb +37 -0
  29. data/spec/lib/speed_gun/event_spec.rb +70 -0
  30. data/spec/lib/speed_gun/middleware_spec.rb +65 -0
  31. data/spec/lib/speed_gun/profile_spec.rb +41 -0
  32. data/spec/lib/speed_gun_spec.rb +52 -0
  33. data/spec/spec_helper.rb +9 -0
  34. data/spec/support/simplecov.rb +12 -0
  35. data/speed_gun.gemspec +10 -7
  36. metadata +102 -56
  37. data/app/assets/javascripts/browser.js +0 -83
  38. data/app/assets/javascripts/profiler.js +0 -45
  39. data/app/views/speed_gun/_meter.html.slim +0 -9
  40. data/lib/speed_gun/app/public/browser.js +0 -83
  41. data/lib/speed_gun/app/public/jquery-1.10.2.min.js +0 -6
  42. data/lib/speed_gun/app/public/profile.js +0 -5
  43. data/lib/speed_gun/app/public/profiler.js +0 -45
  44. data/lib/speed_gun/app/public/style.css +0 -170
  45. data/lib/speed_gun/app/views/profile.slim +0 -97
  46. data/lib/speed_gun/app.rb +0 -58
  47. data/lib/speed_gun/browser/navigation.rb +0 -23
  48. data/lib/speed_gun/browser/timing.rb +0 -92
  49. data/lib/speed_gun/browser.rb +0 -22
  50. data/lib/speed_gun/hook.rb +0 -25
  51. data/lib/speed_gun/profiler/action_controller.rb +0 -12
  52. data/lib/speed_gun/profiler/action_view.rb +0 -12
  53. data/lib/speed_gun/profiler/active_record.rb +0 -16
  54. data/lib/speed_gun/profiler/base.rb +0 -139
  55. data/lib/speed_gun/profiler/js.rb +0 -17
  56. data/lib/speed_gun/profiler/manual.rb +0 -14
  57. data/lib/speed_gun/profiler/rack.rb +0 -7
  58. data/lib/speed_gun/store/base.rb +0 -9
  59. data/lib/speed_gun/store/file.rb +0 -62
  60. data/lib/speed_gun/store/memcache.rb +0 -27
  61. data/lib/speed_gun/store/memory.rb +0 -22
  62. data/lib/speed_gun/store/redis.rb +0 -28
@@ -1,33 +1,24 @@
1
1
  require 'speed_gun'
2
- require 'speed_gun/store/file'
3
2
 
4
3
  class SpeedGun::Railtie < ::Rails::Railtie
5
4
  initializer 'speed_gun' do |app|
6
5
  app.middleware.insert(0, SpeedGun::Middleware)
7
6
 
8
- SpeedGun.config[:enable_if] = -> { Rails.env.development? }
9
- SpeedGun.config[:backtrace_remove] = Rails.root.to_s + '/'
10
- SpeedGun.config[:backtrace_includes] = [/^(app|config|lib|test|spec)/]
11
- SpeedGun.config[:authorize_proc] = ->(request) { Rails.env.development? }
12
- SpeedGun.config.skip_paths << /^#{Regexp.escape(app.config.assets.prefix)}/
13
- SpeedGun.config[:store] =
14
- SpeedGun::Store::File.new(path: Rails.root.join('tmp/speed_gun'))
7
+ SpeedGun.config.logger = Rails.logger
8
+ SpeedGun.config.skip_paths.push(
9
+ /^#{Regexp.escape(app.config.assets.prefix)}/
10
+ )
15
11
 
16
12
  ActiveSupport.on_load(:action_controller) do
17
- require 'speed_gun/profiler/action_controller'
13
+ require 'speed_gun/profiler/action_controller_profiler'
18
14
  end
19
15
 
20
16
  ActiveSupport.on_load(:action_view) do
21
- require 'speed_gun/profiler/action_view'
17
+ require 'speed_gun/profiler/action_view_profiler'
22
18
  end
23
19
 
24
20
  ActiveSupport.on_load(:active_record) do
25
- require 'speed_gun/profiler/active_record'
26
-
27
- SpeedGun::Profiler::ActiveRecord.hook_method(
28
- ActiveRecord::Base.connection.class,
29
- :execute
30
- )
21
+ require 'speed_gun/profiler/active_record_profiler'
31
22
  end
32
23
  end
33
24
  end
@@ -0,0 +1,64 @@
1
+ require 'speed_gun/store'
2
+
3
+ class SpeedGun::Store::ElasticSearchStore < SpeedGun::Store
4
+ DEFAULT_INDEX = 'speed_gun'
5
+
6
+ def initialize(options = {})
7
+ @index = options[:index] || DEFAULT_INDEX
8
+ @async = options.fetch(:async, true)
9
+ @client = options[:client] || default_clinet(options)
10
+ end
11
+
12
+ def save(object)
13
+ @async ? save_with_async(object) : save_without_async(object)
14
+ end
15
+
16
+ def load(klass, id)
17
+ hit = @client.search(
18
+ index: @index,
19
+ body: {
20
+ query: {
21
+ match: {
22
+ "_id" => id,
23
+ "_type" => underscore(klass.name)
24
+ }
25
+ }
26
+ }
27
+ )['hits']['hits'].first['_source']
28
+
29
+ klass.from_hash(id, hit)
30
+ end
31
+
32
+ private
33
+
34
+ def save_with_async(object)
35
+ Thread.new(object) { |object| save_without_async(object) }
36
+ end
37
+
38
+ def save_without_async(object)
39
+ @client.index(
40
+ index: @index,
41
+ type: underscore(object.class.name),
42
+ id: object.id,
43
+ body: object.to_hash.merge(
44
+ '@timestamp' => Time.now
45
+ )
46
+ )
47
+ end
48
+
49
+ def index(klass)
50
+ [@prefix, underscore(klass.name)].join('-')
51
+ end
52
+
53
+ def underscore(name)
54
+ name = name
55
+ name.sub!(/^[A-Z]/) { |c| c.downcase }
56
+ name.gsub!(/[A-Z]/) { |c| "_#{c.downcase}" }
57
+ name.gsub!('::', '')
58
+ end
59
+
60
+ def default_clinet(options)
61
+ require 'elasticsearch' unless defined?(Elasticsearch)
62
+ Elasticsearch::Client.new(options)
63
+ end
64
+ end
@@ -0,0 +1,29 @@
1
+ require 'speed_gun/store'
2
+
3
+ class SpeedGun::Store::FluentLoggerStore < SpeedGun::Store
4
+ DEFAULT_PREFIX = 'speed_gun'
5
+
6
+ def initialize(options = {})
7
+ @prefix = options[:prefix] || DEFAULT_PREFIX
8
+ @logger = options[:logger] || default_logger(options)
9
+ end
10
+
11
+ def save(object)
12
+ @logger.post(tag(object), object.to_hash.merge(id: object.id))
13
+ end
14
+
15
+ def load(klass, id)
16
+ nil
17
+ end
18
+
19
+ private
20
+
21
+ def tag(object)
22
+ object.class.name.sub(/.*::/, '').downcase
23
+ end
24
+
25
+ def default_logger(options)
26
+ require 'fluent-logger' unless defined?(Fluent::Logger)
27
+ Fluent::Logger::FluentLogger.new(@prefix, options)
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ require 'speed_gun/store'
2
+
3
+ class SpeedGun::Store::MemcacheStroe < SpeedGun::Store
4
+ DEFAULT_PREFIX = 'speed-gun'
5
+ DEFAULT_EXPIRES_IN_SECONDS = 60 * 60 * 24
6
+
7
+ def initialize(options = {})
8
+ @prefix = options[:prefix] || DEFAULT_PREFIX
9
+ @client = options[:client] || default_client(options)
10
+ @expires = (options[:expires] || DEFAULT_EXPIRES_IN_SECONDS).to_i
11
+ end
12
+
13
+ def save(object)
14
+ @client.set(
15
+ key(object.class, object.id),
16
+ object.to_hash.to_msgpack,
17
+ @expires
18
+ )
19
+ end
20
+
21
+ def load(klass, id)
22
+ klass.from_hash(id, MessagePack.unpack(@client.get(key(klass, id))))
23
+ end
24
+
25
+ private
26
+
27
+ def key(klass, id)
28
+ klass_name = klass.name
29
+ klass_name.gsub!(/([a-z])([A-Z])/) { |c| "#{$1.to_s}_#{$2.to_s.downcase}" }
30
+ klass_name.gsub!(/[A-Z]/) { |c| "#{c.downcase}" }
31
+ klass_name.gsub!('::', '-')
32
+
33
+ [@prefix, klass_name, id].join('-')
34
+ end
35
+
36
+ def default_client(options)
37
+ require 'dalli' unless defined?(Dalli)
38
+ Dalli.new(options)
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ require 'speed_gun/store'
2
+
3
+ class SpeedGun::Store::MemoryStore < SpeedGun::Store
4
+ DEFAULT_MAX_ENTRIES = 1000
5
+
6
+ def initialize(options = {})
7
+ @max_entries = options[:max_entries] || DEFAULT_MAX_ENTRIES
8
+ @store = {}
9
+ @stored_list = []
10
+ end
11
+
12
+ def save(object)
13
+ id = "#{object.class.name}-#{object.id}"
14
+ @store[id] = object.to_hash
15
+ @stored_list.push(id)
16
+
17
+ @store.delete(@stored_list.shift) while @stored_list.length > @max_entries
18
+ end
19
+
20
+ def load(klass, id)
21
+ klass.from_hash(id, @store["#{klass.name}-#{id}"])
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'speed_gun/store'
2
+
3
+ class SpeedGun::Store::MultipleStore < SpeedGun::Store
4
+ def initialize(stores = [])
5
+ @stores = stores
6
+ end
7
+
8
+ def save(object)
9
+ @stores.each do |store|
10
+ store.save(object)
11
+ end
12
+ end
13
+
14
+ def load(klass, id)
15
+ @stores.each do |store|
16
+ ret = store.load(klass, id)
17
+
18
+ return ret if ret
19
+ end
20
+
21
+ nil
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ require 'msgpack'
2
+ require 'speed_gun/store'
3
+
4
+ class SpeedGun::Store::RedisStore < SpeedGun::Store
5
+ DEFAULT_PREFIX = 'speed-gun'
6
+ DEFAULT_EXPIRES_IN_SECONDS = 60 * 60 * 24
7
+
8
+ def initialize(options = {})
9
+ @prefix = options[:prefix] || DEFAULT_PREFIX
10
+ @client = options[:client] || default_client(options)
11
+ @expires = (options[:expires] || DEFAULT_EXPIRES_IN_SECONDS).to_i
12
+ end
13
+
14
+ def save(object)
15
+ @client.setex(
16
+ key(object.class, object.id),
17
+ @expires,
18
+ object.to_hash.to_msgpack
19
+ )
20
+ end
21
+
22
+ def load(klass, id)
23
+ klass.from_hash(id, MessagePack.unpack(@client.get(key(klass, id))))
24
+ end
25
+
26
+ private
27
+
28
+ def key(klass, id)
29
+ klass_name = klass.name
30
+ klass_name.gsub!(/([a-z])([A-Z])/) { |c| "#{$1.to_s}_#{$2.to_s.downcase}" }
31
+ klass_name.gsub!(/[A-Z]/) { |c| "#{c.downcase}" }
32
+ klass_name.gsub!('::', '-')
33
+
34
+ [@prefix, klass_name, id].join('-')
35
+ end
36
+
37
+ def default_client(options)
38
+ require 'redis' unless defined? Redis
39
+ Redis.new(options)
40
+ end
41
+ end
@@ -1,6 +1,10 @@
1
1
  require 'speed_gun'
2
2
 
3
- module SpeedGun::Store
4
- end
3
+ # @abstract
4
+ class SpeedGun::Store
5
+ def save(object)
6
+ end
5
7
 
6
- require 'speed_gun/store/memory'
8
+ def load(klass, id)
9
+ end
10
+ end
@@ -6,7 +6,7 @@ class SpeedGun::Template < Slim::Template
6
6
  File.join(File.dirname(__FILE__), 'app/views/meter.html.slim')
7
7
 
8
8
  def self.render
9
- new.render(SpeedGun.current)
9
+ new.render(SpeedGun.current_profile)
10
10
  end
11
11
 
12
12
  def initialize
@@ -1,3 +1,3 @@
1
1
  module SpeedGun
2
- VERSION = '0.0.4'
2
+ VERSION = '1.0.0.rc1'.freeze
3
3
  end
data/lib/speed_gun.rb CHANGED
@@ -1,52 +1,43 @@
1
+ require 'forwardable'
1
2
  require 'thread'
3
+ require 'speed_gun/version'
4
+ require 'speed_gun/config'
5
+ require 'speed_gun/profile'
6
+ require 'speed_gun/middleware'
2
7
 
3
8
  module SpeedGun
4
9
  class << self
5
- attr_writer :config
6
- end
7
-
8
- def self.current
9
- Thread.current[:speed_gun_current]
10
- end
11
-
12
- def self.current=(profiler)
13
- Thread.current[:speed_gun_current] = profiler
14
- end
15
-
16
- def self.config
17
- @config ||= SpeedGun::Config.new
18
- end
19
-
20
- def self.active?
21
- current && current.active?
22
- end
23
-
24
- def self.activate!
25
- current && current.activate!
26
- end
10
+ # @return [SpeedGun::Config] the config of speed gun
11
+ def config
12
+ @config ||= Config.new
13
+ end
27
14
 
28
- def self.deactivate!
29
- current && current.deactivate!
30
- end
15
+ # @return [SpeedGun::Profile, nil] the profile of a current thread
16
+ def current_profile
17
+ Thread.current[:speed_gun_current_profile]
18
+ end
31
19
 
32
- def self.enable?
33
- config.enable?
34
- end
20
+ # Set the profile of a current thread
21
+ #
22
+ # @param profile [SpeedGun::Profile] the profile
23
+ # @return [SpeedGun::Profile] the profile of a current thread
24
+ def current_profile=(profile)
25
+ Thread.current[:speed_gun_current_profile] = profile
26
+ end
35
27
 
36
- def self.store
37
- config.store
38
- end
28
+ # Discard the profile of a current thread
29
+ #
30
+ # @return [nil]
31
+ def discard_profile!
32
+ self.current_profile = nil
33
+ end
39
34
 
40
- def self.profile(title, *args, &block)
41
- if title.kind_of?(String)
42
- current && current.profile(:manual, title, &block)
43
- else
44
- current && current.profile(title, *args, &block)
35
+ # @see SpeedGun::Config#enabled?
36
+ # @return [Boolean] true if enabled speed gun
37
+ def enabled?
38
+ config.enabled?
45
39
  end
46
40
  end
47
41
  end
48
42
 
49
- require 'speed_gun/version'
50
- require 'speed_gun/config'
51
- require 'speed_gun/middleware'
52
43
  require 'speed_gun/railtie' if defined?(Rails)
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe SpeedGun::Config do
4
+ subject(:config) { described_class.new }
5
+
6
+ describe '#enable!' do
7
+ before do
8
+ config.disable!
9
+ end
10
+
11
+ it 'enables the config' do
12
+ expect(config).to_not be_enabled
13
+ config.enable!
14
+ expect(config).to be_enabled
15
+ end
16
+ end
17
+
18
+ describe '#disable!' do
19
+ it 'disables the config' do
20
+ expect(config).to_not be_disabled
21
+ config.disable!
22
+ expect(config).to be_disabled
23
+ end
24
+ end
25
+
26
+ describe '#enabled?' do
27
+ it 'defaults to true' do
28
+ expect(config.enabled?).to be_true
29
+ end
30
+ end
31
+
32
+ describe '#disabled?' do
33
+ it 'defaults to false' do
34
+ expect(config.disabled?).to be_false
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe SpeedGun::Event do
4
+ let(:event_name) { 'spec.test' }
5
+
6
+ subject(:event) { described_class.new(event_name) }
7
+
8
+ describe '#id' do
9
+ subject { event.id }
10
+
11
+ it { should be_kind_of(String) }
12
+ end
13
+
14
+ describe '#name' do
15
+ subject { event.name }
16
+
17
+ it { should be_kind_of(String) }
18
+ it { should eq(event_name) }
19
+ end
20
+
21
+ describe '#started_at' do
22
+ subject { event.started_at }
23
+
24
+ it { should be_kind_of(Time) }
25
+ end
26
+
27
+ describe '#finished_at' do
28
+ subject { event.finished_at }
29
+
30
+ context 'when finished event' do
31
+ before { event.finish! }
32
+
33
+ it { should be_kind_of(Time) }
34
+ end
35
+
36
+ context 'when continues event' do
37
+ it { should be_nil }
38
+ end
39
+ end
40
+
41
+ describe '#finish!' do
42
+ it 'finishes the event' do
43
+ expect(event).to_not be_finished
44
+ event.finish!
45
+ expect(event).to be_finished
46
+ end
47
+ end
48
+
49
+ describe '#duration' do
50
+ subject(:duration) { event.duration }
51
+
52
+ context 'when continues event' do
53
+ it { should eq(-1) }
54
+ end
55
+
56
+ context 'when finished event' do
57
+ before { event.finish! }
58
+
59
+ it { should be_kind_of(Float) }
60
+ end
61
+ end
62
+
63
+ describe '#to_hash' do
64
+ it 'valid serialize' do
65
+ expect(
66
+ SpeedGun::Event.from_hash(event.id, event.to_hash).to_hash
67
+ ).to eq(event.to_hash)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ describe SpeedGun::Middleware do
5
+ include Rack::Test::Methods
6
+
7
+ before(:all) do
8
+ SpeedGun.config.skip_paths << '/skip'
9
+ end
10
+
11
+ let(:app) do
12
+ builder = Rack::Builder.new do
13
+ use SpeedGun::Middleware
14
+
15
+ map '/skip' do
16
+ process = lambda do |env|
17
+ [
18
+ 200,
19
+ { 'Content-Type' => 'text/html' },
20
+ "<html><BODY><h1>Skip</h1></BODY>\n \t</html>"
21
+ ]
22
+ end
23
+
24
+ run process
25
+ end
26
+
27
+ map '/html' do
28
+ process = lambda do |env|
29
+ [
30
+ 200,
31
+ { 'Content-Type' => 'text/html' },
32
+ "<html><BODY><h1>Hi</h1></BODY>\n \t</html>"
33
+ ]
34
+ end
35
+
36
+ run process
37
+ end
38
+ end
39
+ builder.to_app
40
+ end
41
+
42
+ describe 'GET /skip' do
43
+ subject(:response) { get '/skip' }
44
+
45
+ it { should be_ok }
46
+
47
+ describe '#headers' do
48
+ subject { response.headers }
49
+
50
+ it { should_not be_has_key('X-SpeedGun-Profile-Id') }
51
+ end
52
+ end
53
+
54
+ describe 'GET /html' do
55
+ subject(:response) { get '/html' }
56
+
57
+ it { should be_ok }
58
+
59
+ describe '#headers' do
60
+ subject { response.headers }
61
+
62
+ it { should be_has_key('X-SpeedGun-Profile-Id') }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe SpeedGun::Profile do
4
+ subject(:profile) { described_class.new }
5
+
6
+ describe '#id' do
7
+ subject { profile.id }
8
+
9
+ it { should be_kind_of(String) }
10
+ end
11
+
12
+ describe '#events' do
13
+ subject { profile.events }
14
+
15
+ it { should be_kind_of(Array) }
16
+ end
17
+
18
+ describe '#record!' do
19
+ let(:logger) { double(debug: nil) }
20
+ let(:event) { SpeedGun::Event.new('spec.test') }
21
+
22
+ before { profile.config.logger = logger }
23
+
24
+ it 'records event' do
25
+ expect(profile.record!(event)).to eq(profile.events)
26
+ expect(profile.events).to include(event)
27
+ end
28
+ end
29
+
30
+ describe '#to_hash' do
31
+ let(:event) { SpeedGun::Event.new('spec.test') }
32
+
33
+ before { profile.record!(event) }
34
+
35
+ it 'valid serialize' do
36
+ expect(
37
+ SpeedGun::Profile.from_hash(profile.id, profile.to_hash).to_hash
38
+ ).to eq(profile.to_hash)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe SpeedGun do
4
+ subject { described_class }
5
+
6
+ describe '#config' do
7
+ subject { described_class.config }
8
+
9
+ it { should be_kind_of(SpeedGun::Config) }
10
+ end
11
+
12
+ describe '#current_profile' do
13
+ let(:profile) { double }
14
+ subject(:current_profile) { described_class.current_profile }
15
+
16
+ it 'defaults to be nil' do
17
+ expect(current_profile).to be_nil
18
+ end
19
+
20
+ it 'thread localy' do
21
+ described_class.current_profile = profile
22
+ expect(current_profile).to eq(profile)
23
+
24
+ thread = Thread.new { expect(described_class.current_profile).to be_nil }
25
+ thread.join
26
+ end
27
+ end
28
+
29
+ describe '#discard_profile!' do
30
+ let(:profile) { double }
31
+
32
+ it 'discards current profile' do
33
+ described_class.current_profile = profile
34
+ described_class.discard_profile!
35
+ expect(described_class.current_profile).to be_nil
36
+ end
37
+ end
38
+
39
+ describe '#enabled?' do
40
+ context 'when enabled' do
41
+ before { described_class.config.stub(enabled?: true) }
42
+
43
+ it { should be_enabled }
44
+ end
45
+
46
+ context 'when disabled' do
47
+ before { described_class.config.stub(enabled?: false) }
48
+
49
+ it { should_not be_enabled }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ require 'support/simplecov'
2
+
3
+ require 'speed_gun'
4
+
5
+ RSpec.configure do |config|
6
+ config.treat_symbols_as_metadata_keys_with_true_values = true
7
+ config.run_all_when_everything_filtered = true
8
+ config.filter_run :focus
9
+ end
@@ -0,0 +1,12 @@
1
+ require 'simplecov'
2
+ require 'simplecov-console'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ SimpleCov::Formatter::Console,
8
+ Coveralls::SimpleCov::Formatter
9
+ ]
10
+ SimpleCov.start do
11
+ add_filter('spec')
12
+ end