click 0.1.2 → 0.2.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.
@@ -1,3 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
+ script: "bundle exec rspec spec --order rand"
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Click
2
2
 
3
- TODO: Write a gem description
3
+ [![Build Status](https://travis-ci.org/mark-rushakoff/click.png?branch=master)](https://travis-ci.org/mark-rushakoff/click)
4
+ [![Gem Version](https://badge.fury.io/rb/click.png)](http://badge.fury.io/rb/click)
5
+ [![Code Climate](https://codeclimate.com/github/mark-rushakoff/click.png)](https://codeclimate.com/github/mark-rushakoff/click)
6
+
7
+ A tool to track down object leaks in a Ruby process.
4
8
 
5
9
  ## Installation
6
10
 
@@ -20,6 +24,11 @@ Or install it yourself as:
20
24
 
21
25
  TODO: Write usage instructions here
22
26
 
27
+ ## Example
28
+
29
+ See [the demo app](demo/app.rb) for an example of a simple Sinatra app that uses an existing Sequel database and that leaks objects when connecting to a certain endpoint.
30
+ Try running the app and then running `click-console /tmp/click_demo_memory.sqlite` to inspect the live database.
31
+
23
32
  ## Contributing
24
33
 
25
34
  1. Fork it
@@ -8,6 +8,9 @@ module Click::Database
8
8
  module Models
9
9
  default_connection_string = 'sqlite:/'
10
10
  connection_string = "#{ARGV.fetch(0, default_connection_string)}"
11
+ if File.file?(connection_string)
12
+ connection_string = [default_connection_string, File.expand_path(connection_string)].join('/')
13
+ end
11
14
  puts "Using connection string: #{connection_string}"
12
15
 
13
16
  Click.clicker_with_database('click-console', connection_string) do |clicker|
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Click::VERSION
9
9
  spec.authors = ["Mark Rushakoff"]
10
10
  spec.email = ["mark.rushakoff@gmail.com"]
11
- spec.description = %q{A tool to help track down the source of a memory leak in a Ruby process}
11
+ spec.description = %q{A tool to help track down the source of an object leak in a Ruby process}
12
12
  spec.summary = %q{Track the changes in counts of live objects in your Ruby process.}
13
13
  spec.homepage = "https://github.com/mark-rushakoff/click"
14
14
  spec.license = "MIT"
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "rspec", "~> 2.14"
24
+ spec.add_development_dependency "rspec", "~> 2.14"
25
+ spec.add_development_dependency "rspec-given", "~> 3.1"
24
26
  spec.add_development_dependency "sqlite3"
25
27
  spec.add_development_dependency "pry"
26
28
 
@@ -42,6 +42,13 @@ get '/' do
42
42
  <html><body>
43
43
  <h1>Click demo app</h1>
44
44
  <br/>
45
+ <ul>
46
+ <li><a href="/make_objects/1">Leak 1 object</a></li>
47
+ <li><a href="/make_objects/10">Leak 10 objects</a></li>
48
+ <li><a href="/make_objects/100">Leak 100 objects</a></li>
49
+ <li><a href="/make_objects/1000">Leak 1000 objects</a></li>
50
+ <li><a href="/make_objects/10000">Leak 10000 objects</a></li>
51
+ </ul>
45
52
  Visited #{Visit.count} time(s) by #{Visit.group_by(:ip_address).count} IP address(es).
46
53
  </body></html>
47
54
  HTML
@@ -52,9 +52,9 @@ module Click
52
52
  class << self
53
53
  def clicker_with_database(session_name, connection_string)
54
54
  require 'click/database'
55
- Click::Database.with_database(connection_string) do |db|
55
+ Click::Database.with_database(connection_string) do
56
56
  require 'click/database/writer'
57
- writer = Click::Database::Writer.new(db)
57
+ writer = Click::Database::Writer.new
58
58
  clicker = Click::Clicker.new(session_name)
59
59
  clicker.add_observer(writer)
60
60
  yield clicker
@@ -16,18 +16,28 @@ module Click
16
16
  end
17
17
  end
18
18
 
19
+ def prepare(sequel_db)
20
+ ensure_tables!(sequel_db)
21
+ assign_db_to_models(sequel_db)
22
+ sequel_db
23
+ end
24
+
19
25
  private
20
26
  def _with_db(db)
21
- ensure_tables!(db)
22
- assign_db_to_models(db)
27
+ prepare(db)
23
28
  yield db
24
29
  end
25
30
 
26
31
  def assign_db_to_models(db)
27
32
  require 'click/database/models'
28
- Click::Database::Models::Session.db = db
29
- Click::Database::Models::Snapshot.db = db
30
- Click::Database::Models::ObjectCount.db = db
33
+ assign_db(db, Click::Database::Models::Session)
34
+ assign_db(db, Click::Database::Models::Snapshot)
35
+ assign_db(db, Click::Database::Models::ObjectCount)
36
+ end
37
+
38
+ def assign_db(db, model_class)
39
+ # Repeatedly reassigning the DB seems to cause problems, so only assign if we need it.
40
+ model_class.db = db unless model_class.db == db
31
41
  end
32
42
 
33
43
  def ensure_tables!(db)
@@ -1,4 +1,5 @@
1
1
  require 'sequel'
2
+ require 'set'
2
3
 
3
4
  module Click
4
5
  module Database
@@ -7,13 +8,27 @@ module Click
7
8
  many_to_one :session
8
9
  one_to_many :object_counts
9
10
 
11
+ def difference(earlier)
12
+ earlier_object_count_hash = Hash[earlier.object_counts_dataset.map([:class_name, :count])]
13
+ later_object_count_hash = Hash[object_counts_dataset.map([:class_name, :count])]
14
+
15
+ keys = Set.new(earlier_object_count_hash.keys + later_object_count_hash.keys)
16
+
17
+ diff = {}
18
+ keys.each do |key|
19
+ diff[key] = later_object_count_hash.fetch(key, 0) - earlier_object_count_hash.fetch(key, 0)
20
+ end
21
+
22
+ diff
23
+ end
24
+
10
25
  dataset_module do
11
26
  def after(time)
12
27
  filter { timestamp > time }
13
28
  end
14
29
 
15
30
  def before(time)
16
- filter { timestamp < time}
31
+ filter { timestamp < time }
17
32
  end
18
33
 
19
34
  def by_session_name(session_name)
@@ -6,10 +6,6 @@ module Click
6
6
  class Writer
7
7
  include Click::Observer
8
8
 
9
- def initialize(db)
10
- @db = db
11
- end
12
-
13
9
  def on_add(clicker)
14
10
  @session = Models::Session.create(name: clicker.session_name, started_at: Time.now)
15
11
  end
@@ -24,7 +20,7 @@ module Click
24
20
  end
25
21
 
26
22
  private
27
- attr_reader :db, :session
23
+ attr_reader :session
28
24
  end
29
25
  end
30
26
  end
@@ -1,3 +1,3 @@
1
1
  module Click
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,99 +1,93 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Click::Clicker do
4
- subject(:clicker) { described_class.new('test') }
4
+ Given(:session_name) { SecureRandom.uuid }
5
+ Given(:clicker) { described_class.new(session_name) }
5
6
 
6
7
  describe '#session_name' do
7
- it 'is the string given to the constructor' do
8
- expect(clicker.session_name).to eq('test')
9
- end
8
+ Then { clicker.session_name == session_name }
10
9
  end
11
10
 
12
11
  describe '#instance_count' do
13
- it 'counts instances of a single class' do
14
- klass = Class.new
12
+ Given(:klass) { Class.new }
15
13
 
16
- clicker.click!
17
- expect(clicker.instance_count(klass)).to eq(0)
14
+ describe 'with no instances' do
15
+ When { clicker.click! }
18
16
 
19
- obj = klass.new
17
+ Then { clicker.instance_count(klass) == 0 }
18
+ end
20
19
 
21
- clicker.click!
22
- expect(clicker.instance_count(klass)).to eq(1)
20
+ describe 'with instances' do
21
+ Given!(:object) { klass.new }
23
22
 
24
- obj = nil
23
+ When { clicker.click! }
25
24
 
26
- clicker.click!
27
- expect(clicker.instance_count(klass)).to eq(0)
25
+ Then { clicker.instance_count(klass) == 1 }
28
26
  end
29
27
 
30
- it 'tracks differences in symbols' do
31
- difference = 100
28
+ describe 'symbols' do
29
+ Given { clicker.click! }
30
+ Given!(:original_symbol_count) { clicker.instance_count(Symbol) }
32
31
 
33
- expect {
34
- difference.times { |i| [Time.now.to_i, i].join('_-_-_').to_sym }
35
- }.to change {
36
- clicker.click!
37
- clicker.instance_count(Symbol)
38
- }.by_at_least(difference)
32
+ When { 100.times { |i| [Time.now.to_i, i].join('_-_').to_sym }; clicker.click! }
33
+
34
+ Then { clicker.instance_count(Symbol) >= original_symbol_count + 100 }
39
35
  end
40
36
 
41
- it 'counts BasicObjects as indeterminable' do
42
- difference = 100
43
- stuff = []
37
+ describe 'BasicObject' do
38
+ Given { clicker.click! }
39
+ Given!(:original_count) { clicker.instance_count(Click::Indeterminable) }
44
40
 
45
- expect {
46
- difference.times { |i| stuff << BasicObject.new }
47
- }.to change {
48
- clicker.click!
49
- clicker.instance_count(Click::Indeterminable)
50
- }.by_at_least(difference)
41
+ When { stuff = 25.times.map { BasicObject.new }; clicker.click! }
42
+
43
+ Then { clicker.instance_count(Click::Indeterminable) >= original_count + 25 }
51
44
  end
52
45
 
53
- it 'counts objects that have overridden #class as indeterminable' do
54
- difference = 100
55
- stuff = []
56
- klass = Class.new do
57
- def class
58
- nil
46
+ describe 'objects that poorly override #class' do
47
+ # This test was flaking out with the Given setup. *shrug*
48
+ before do
49
+ klass = Class.new do
50
+ def class
51
+ nil
52
+ end
59
53
  end
54
+ clicker.click!
55
+ @original_count = clicker.instance_count(Click::Indeterminable)
56
+
57
+ @stuff = 33.times.map { klass.new }
60
58
  end
61
59
 
62
- expect {
63
- difference.times { |i| stuff << klass.new }
64
- }.to change {
65
- clicker.click!
66
- clicker.instance_count(Click::Indeterminable)
67
- }.by_at_least(difference)
60
+ When { clicker.click! }
61
+
62
+ Then { clicker.instance_count(Click::Indeterminable) >= @original_count + 33 }
68
63
  end
69
64
  end
70
65
 
71
66
  describe '#object_counts' do
72
- it "groups objects by their class" do
73
- ObjectSpace.should_receive(:each_object).and_yield(Object.new).and_yield(":)").and_yield(":(")
74
- Symbol.should_receive(:all_symbols).and_return([:foo, :bar, :baz])
75
-
76
- clicker.click!
77
- expect(clicker.object_counts).to be_a(Hash)
78
- expect(clicker.object_counts.to_a).to eq([[Object, 1], [String, 2], [Symbol, 3]])
67
+ Given do
68
+ ObjectSpace.stub(:each_object).and_yield(Object.new).and_yield(":)").and_yield(":(")
69
+ Symbol.stub(:all_symbols).and_return([:foo, :bar, :baz])
79
70
  end
71
+
72
+ When { clicker.click! }
73
+
74
+ Then { clicker.object_counts == {Object => 1, String => 2, Symbol => 3} }
75
+ And { expect(ObjectSpace).to have_received(:each_object).with(no_args) }
76
+ And { expect(Symbol).to have_received(:all_symbols).with(no_args) }
80
77
  end
81
78
 
82
79
  describe 'observers' do
83
- it 'calls #on_add' do
84
- observer = double(on_add: nil)
85
- observer.should_receive(:on_add).with(clicker)
80
+ Given(:observer) { double(on_add: nil, before_click: nil, after_click: nil) }
86
81
 
87
- clicker.add_observer(observer)
88
- end
82
+ When { clicker.add_observer(observer) }
83
+
84
+ Then { expect(observer).to have_received(:on_add).with(clicker) }
89
85
 
90
- it 'calls #before_click and #after_click' do
91
- observer = double(on_add: nil, before_click: nil, after_click: nil)
92
- observer.should_receive(:before_click).with(clicker).ordered
93
- observer.should_receive(:after_click).with(clicker).ordered
86
+ describe 'after click!' do
87
+ When { clicker.click! }
94
88
 
95
- clicker.add_observer(observer)
96
- clicker.click!
89
+ Then { expect(observer).to have_received(:before_click).with(clicker) }
90
+ Then { expect(observer).to have_received(:after_click).with(clicker) }
97
91
  end
98
92
  end
99
93
  end
@@ -5,40 +5,23 @@ initialize_models
5
5
 
6
6
  module Click::Database::Models
7
7
  describe ObjectCount do
8
- describe '.scopes' do
9
- describe '.by_session_name' do
10
- it 'returns only object counts belonging to the session with that name' do
11
- with_in_memory_db do
12
- ignored_session = Session.create(name: 'ignored', started_at: Time.now)
13
- ignored_snapshot = Snapshot.create(timestamp: Time.now, session_id: ignored_session.id)
14
- 50.times { ObjectCount.create(class_name: "Foo", count: 1, snapshot_id: ignored_snapshot.id) }
8
+ Given(:parent_session) { Session.create(name: SecureRandom.uuid, started_at: Time.now) }
9
+ Given(:parent_snapshot) { parent_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
10
+ Given(:object_count) { parent_snapshot.add_object_count(ObjectCount.new(class_name: SecureRandom.uuid, count: 1)) }
15
11
 
16
- important_session = Session.create(name: 'important', started_at: Time.now)
17
- important_snapshot = Snapshot.create(timestamp: Time.now, session_id: important_session.id)
18
- important_object_count = ObjectCount.create(class_name: "Bar", count: 2, snapshot_id: important_snapshot.id)
12
+ describe 'the .by_session_name scope' do
13
+ Given(:dataset) { ObjectCount.by_session_name(parent_session.name) }
19
14
 
20
- dataset = ObjectCount.by_session_name('important')
21
- expect(dataset.count).to eq(1)
22
- expect(dataset.map([:id, :class_name, :count])).to eq([[important_object_count.id, "Bar", 2]])
23
- end
24
- end
25
- end
15
+ Then { [object_count] == dataset.all }
16
+ end
26
17
 
27
- describe '.sessions' do
28
- it 'returns the sessions owning the object counts' do
29
- with_in_memory_db do
30
- ignored_session = Session.create(name: 'ignored', started_at: Time.now)
31
- ignored_snapshot = Snapshot.create(timestamp: Time.now, session_id: ignored_session.id)
32
- 50.times { ObjectCount.create(class_name: "Foo", count: 1, snapshot_id: ignored_snapshot.id) }
18
+ describe '.snapshots' do
19
+ Given(:other_snapshot) { parent_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
20
+ Given(:other_object_count) { other_snapshot.add_object_count(ObjectCount.new(class_name: SecureRandom.uuid, count: 1)) }
33
21
 
34
- important_session = Session.create(name: 'important', started_at: Time.now)
35
- important_snapshot = Snapshot.create(timestamp: Time.now, session_id: important_session.id)
36
- important_object_count = ObjectCount.create(class_name: "Bar", count: 2, snapshot_id: important_snapshot.id)
22
+ Given(:dataset) { ObjectCount.where(id: [object_count.id, other_object_count.id]).snapshots }
37
23
 
38
- expect(ObjectCount.where(class_name: 'Bar').snapshots.all).to eq([important_snapshot])
39
- end
40
- end
41
- end
24
+ Then { expect(dataset.all).to match_array([parent_snapshot, other_snapshot])}
42
25
  end
43
26
  end
44
27
  end
@@ -5,36 +5,22 @@ initialize_models
5
5
 
6
6
  module Click::Database::Models
7
7
  describe Session do
8
- describe '.scopes' do
9
- describe '.by_name' do
10
- it 'returns only object counts belonging to the session with that name' do
11
- with_in_memory_db do
12
- Session.create(name: 'ignored', started_at: Time.now)
8
+ Given(:session_name) { SecureRandom.uuid }
13
9
 
14
- important_session = Session.create(name: 'important', started_at: Time.now)
10
+ describe 'the .by_name dataset' do
11
+ Given(:session) { Session.create(name: session_name, started_at: Time.now) }
15
12
 
16
- dataset = Session.by_name('important')
17
- expect(dataset.all).to eq([important_session])
18
- end
19
- end
20
- end
21
-
22
- describe '.snapshots' do
23
- it 'returns all the snapshots belonging to the set of sessions' do
24
- with_in_memory_db do
25
- ignored_session = Session.create(name: 'ignored', started_at: Time.now)
26
- 50.times { ignored_session.add_snapshot(Snapshot.new(timestamp: Time.now))}
13
+ Then { [session] == Session.by_name(session_name).all }
14
+ end
27
15
 
28
- session = Session.create(name: 'important', started_at: Time.now)
29
- snapshot = session.add_snapshot(Snapshot.new(timestamp: Time.now))
16
+ describe 'the .snapshots dataset' do
17
+ Given!(:first_session) { Session.create(name: session_name, started_at: Time.now) }
18
+ Given!(:first_snapshot) { first_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
30
19
 
31
- other_session = Session.create(name: 'important', started_at: Time.now)
32
- other_snapshot = other_session.add_snapshot(Snapshot.new(timestamp: Time.now))
20
+ Given!(:other_session) { Session.create(name: session_name, started_at: Time.now) }
21
+ Given!(:other_snapshot) { other_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
33
22
 
34
- expect(Session.by_name('important').snapshots.map(:id)).to match_array([snapshot.id, other_snapshot.id])
35
- end
36
- end
37
- end
23
+ Then { expect(Session.by_name(session_name).snapshots.all).to match_array([first_snapshot, other_snapshot]) }
38
24
  end
39
25
  end
40
26
  end
@@ -5,85 +5,63 @@ initialize_models
5
5
 
6
6
  module Click::Database::Models
7
7
  describe Snapshot do
8
- let(:session_id) { Click::Database::Models::Session.create(name: "test", started_at: Time.now).id }
9
-
10
- describe 'scopes' do
11
- describe '.after' do
12
- it 'excludes records prior to the given time' do
13
- with_in_memory_db do
14
- described_class.create(timestamp: Date.new(2000), session_id: session_id)
15
- later = described_class.create(timestamp: Date.new(2006), session_id: session_id)
16
-
17
- expect(described_class.after(Date.new(2003)).all).to eq([later])
18
- end
19
- end
20
- end
21
-
22
- describe '.before' do
23
- it 'excludes records after the given time' do
24
- with_in_memory_db do
25
- earlier = described_class.create(timestamp: Date.new(2000), session_id: session_id)
26
- described_class.create(timestamp: Date.new(2006), session_id: session_id)
27
-
28
- expect(described_class.before(Date.new(2003)).all).to eq([earlier])
29
- end
30
- end
31
- end
32
-
33
- describe '.by_session_name' do
34
- it 'returns only snapshots belonging to the session with that name' do
35
- with_in_memory_db do
36
- ignored_session = Session.create(name: 'ignored', started_at: Time.now)
37
- 50.times { Snapshot.create(timestamp: Time.now, session_id: ignored_session.id) }
8
+ Given!(:parent_session) { Session.create(name: SecureRandom.uuid, started_at: Time.now) }
9
+ describe 'the .before, .after scopes' do
10
+ Given!(:earlier) { Snapshot.create(timestamp: Date.new(2000), session_id: parent_session.id) }
11
+ Given!(:later) { Snapshot.create(timestamp: Date.new(2006), session_id: parent_session.id) }
12
+ Given(:dataset) { parent_session.snapshots_dataset }
13
+
14
+ Then { dataset.before(Date.new(2003)).all == [earlier] }
15
+ And { dataset.after(Date.new(2003)).all == [later] }
16
+ end
38
17
 
39
- important_session = Session.create(name: 'important', started_at: Time.now)
40
- important_snapshot = Snapshot.create(timestamp: Time.now, session_id: important_session.id)
18
+ describe 'the .by_session_name dataset' do
19
+ Given!(:snapshot) { Snapshot.create(timestamp: Time.now, session_id: parent_session.id) }
20
+ Given(:dataset) { Snapshot.by_session_name(parent_session.name) }
41
21
 
42
- dataset = Snapshot.by_session_name('important')
43
- expect(dataset.count).to eq(1)
44
- expect(dataset.map([:id, :timestamp, :session_id])).to eq([[important_snapshot.id, important_snapshot.timestamp, important_session.id]])
45
- end
46
- end
47
- end
22
+ Then { dataset.all == [snapshot] }
23
+ end
48
24
 
49
- describe '.sessions' do
50
- it 'returns sessions owning the current snapshots' do
51
- with_in_memory_db do
52
- Session.create(name: 'ignored', started_at: Time.now)
25
+ describe 'the .sessions dataset' do
26
+ Given(:other_parent_session) { Session.create(name: SecureRandom.uuid, started_at: Time.now) }
27
+ Given(:other_snapshot) { other_parent_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
53
28
 
54
- important_session = Session.create(name: 'important', started_at: Time.now)
55
- Snapshot.create(timestamp: Time.now, session_id: important_session.id)
29
+ Given(:snapshot) { parent_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
30
+ Given(:dataset) { Snapshot.where(id: [snapshot.id, other_snapshot.id]) }
56
31
 
57
- expect(Snapshot.dataset.sessions.all).to eq([important_session])
58
- end
59
- end
60
- end
32
+ Then { expect(dataset.sessions.all).to match_array([parent_session, other_parent_session]) }
33
+ end
61
34
 
62
- describe '.object_counts' do
63
- it 'returns object counts owned by the current snapshots' do
64
- with_in_memory_db do
65
- Session.create(name: 'ignored', started_at: Time.now).
66
- add_snapshot(Snapshot.new(timestamp: Time.now)).
67
- add_object_count(ObjectCount.new(class_name: 'Bar', count: 1))
35
+ describe '.object_counts' do
36
+ Given(:snapshot) { parent_session.add_snapshot(Snapshot.new(timestamp: Time.now)) }
37
+ Given(:class_name) { SecureRandom.uuid }
38
+ Given(:object_count) { snapshot.add_object_count(ObjectCount.new(class_name: class_name, count: 5)) }
39
+ Given(:dataset) { Snapshot.where(id: snapshot.id) }
68
40
 
69
- session = Session.create(name: 'important', started_at: Time.now)
70
- snapshot = Snapshot.create(timestamp: Time.now, session_id: session.id)
71
- object_count = snapshot.add_object_count(ObjectCount.new(class_name: 'Foo', count: 5))
41
+ Then { [object_count] == dataset.object_counts.all }
42
+ end
72
43
 
73
- expect(Snapshot.where(session_id: session.id).object_counts.all).to eq([object_count])
74
- end
75
- end
44
+ describe '#difference' do
45
+ Given(:first_snapshot) { Snapshot.create(timestamp: Time.now, session_id: parent_session.id) }
46
+ Given { first_snapshot.add_object_count(ObjectCount.new(class_name: "first_only", count: 10)) }
47
+ Given { first_snapshot.add_object_count(ObjectCount.new(class_name: "both", count: 1)) }
48
+ Given { first_snapshot.add_object_count(ObjectCount.new(class_name: "both_same", count: 2)) }
49
+
50
+ Given(:second_snapshot) { Snapshot.create(timestamp: Time.now, session_id: parent_session.id) }
51
+ Given { second_snapshot.add_object_count(ObjectCount.new(class_name: "both", count: 5)) }
52
+ Given { second_snapshot.add_object_count(ObjectCount.new(class_name: "both_same", count: 2)) }
53
+ Given { second_snapshot.add_object_count(ObjectCount.new(class_name: "second_only", count: 9)) }
54
+
55
+ Given(:expected_hash) do
56
+ {
57
+ "both" => 4,
58
+ "both_same" => 0,
59
+ "second_only" => 9,
60
+ "first_only" => -10,
61
+ }
76
62
  end
77
63
 
78
- it 'can combine scopes' do
79
- with_in_memory_db do
80
- described_class.create(timestamp: Date.new(2000), session_id: session_id)
81
- middle = described_class.create(timestamp: Date.new(2003), session_id: session_id)
82
- described_class.create(timestamp: Date.new(2006), session_id: session_id)
83
-
84
- expect(described_class.after(Date.new(2001)).before(Date.new(2005)).all).to eq([middle])
85
- end
86
- end
64
+ Then { second_snapshot.difference(first_snapshot) == expected_hash }
87
65
  end
88
66
  end
89
67
  end
@@ -1,33 +1,56 @@
1
1
  require 'spec_helper'
2
2
  require 'click/database'
3
+ require 'securerandom'
3
4
 
4
5
  initialize_models
5
6
 
6
7
  require 'click/database/writer'
7
8
 
8
- describe Click::Database::Writer do
9
- it 'writes records to the database' do
10
- with_in_memory_db do |db|
11
- writer = Click::Database::Writer.new(db)
12
- clicker = Click::Clicker.new("test")
13
-
14
- expect {
15
- clicker.add_observer(writer)
16
- }.to change { Click::Database::Models::Session.count }.from(0).to(1)
17
-
18
- expect(Click::Database::Models::Snapshot.count).to eq(0)
19
- expect(Click::Database::Models::ObjectCount.count).to eq(0)
20
-
21
- before_time = Time.now
22
- clicker.click!
23
- after_time = Time.now
24
-
25
- expect(Click::Database::Models::Snapshot.count).to eq(1)
26
- expect(Click::Database::Models::ObjectCount.count).to be > 0
27
- snapshot = Click::Database::Models::Snapshot.first
28
- expect(snapshot.object_counts.count).to eq(Click::Database::Models::ObjectCount.count)
29
- expect(snapshot.timestamp).to be >= before_time
30
- expect(snapshot.timestamp).to be <= after_time
9
+ module Click::Database
10
+ describe Writer do
11
+ Given(:writer) { Writer.new }
12
+ Given(:session_name) { SecureRandom.uuid }
13
+ Given(:clicker) { Click::Clicker.new(session_name) }
14
+
15
+ describe 'when added to the clicker' do
16
+ When { clicker.add_observer(writer) }
17
+
18
+ Then { Models::Session.where(name: session_name).count == 1 }
19
+
20
+ describe 'the session' do
21
+ Given(:session) { Models::Session[name: session_name] }
22
+
23
+ Then { session.snapshots_dataset.count == 0 }
24
+
25
+ context 'when a click happens' do
26
+ Given!(:before_click_time) { Time.now }
27
+ When { clicker.click! }
28
+
29
+ Then { session.snapshots_dataset.count == 1 }
30
+
31
+ describe 'the created snapshot' do
32
+ Given(:snapshot) { session.snapshots.first }
33
+
34
+ Then { snapshot.object_counts_dataset.count > 0 }
35
+ And { snapshot.timestamp >= before_click_time }
36
+ And { snapshot.timestamp <= Time.now }
37
+ end
38
+
39
+ context 'and another click happens' do
40
+ When { clicker.click! }
41
+
42
+ Then { session.snapshots_dataset.count == 2 }
43
+
44
+ describe 'the created snapshot' do
45
+ Given(:snapshot) { session.snapshots.last }
46
+
47
+ Then { snapshot.object_counts_dataset.count > 0 }
48
+ And { snapshot.timestamp >= before_click_time }
49
+ And { snapshot.timestamp <= Time.now }
50
+ end
51
+ end
52
+ end
53
+ end
31
54
  end
32
55
  end
33
56
  end
@@ -1,15 +1,14 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'click'
3
-
4
- def with_in_memory_db
5
- Click::Database.with_in_memory_database do |db|
6
- Click::Database::Models::ObjectCount.dataset.delete
7
- Click::Database::Models::Snapshot.dataset.delete
8
-
9
- yield db
10
- end
11
- end
3
+ require 'securerandom'
4
+ require 'rspec/given'
12
5
 
13
6
  def initialize_models
14
7
  Click::Database.with_in_memory_database { }
15
8
  end
9
+
10
+ RSpec.configure do |c|
11
+ c.before(:suite) do
12
+ Click::Database.prepare(Sequel.sqlite)
13
+ end
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: click
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-04 00:00:00.000000000 Z
12
+ date: 2013-12-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -59,6 +59,38 @@ dependencies:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
61
  version: '2.14'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.14'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.14'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec-given
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '3.1'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '3.1'
62
94
  - !ruby/object:Gem::Dependency
63
95
  name: sqlite3
64
96
  requirement: !ruby/object:Gem::Requirement
@@ -107,7 +139,7 @@ dependencies:
107
139
  - - ! '>='
108
140
  - !ruby/object:Gem::Version
109
141
  version: '0'
110
- description: A tool to help track down the source of a memory leak in a Ruby process
142
+ description: A tool to help track down the source of an object leak in a Ruby process
111
143
  email:
112
144
  - mark.rushakoff@gmail.com
113
145
  executables: