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.
- data/.travis.yml +1 -0
- data/README.md +10 -1
- data/bin/click-console +3 -0
- data/click.gemspec +3 -1
- data/demo/app.rb +7 -0
- data/lib/click/clicker.rb +2 -2
- data/lib/click/database.rb +15 -5
- data/lib/click/database/models/snapshot.rb +16 -1
- data/lib/click/database/writer.rb +1 -5
- data/lib/click/version.rb +1 -1
- data/spec/clicker_spec.rb +55 -61
- data/spec/database/models/object_count_spec.rb +12 -29
- data/spec/database/models/session_spec.rb +11 -25
- data/spec/database/models/snapshot_spec.rb +47 -69
- data/spec/database/writer_spec.rb +46 -23
- data/spec/spec_helper.rb +8 -9
- metadata +35 -3
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# Click
|
2
2
|
|
3
|
-
|
3
|
+
[](https://travis-ci.org/mark-rushakoff/click)
|
4
|
+
[](http://badge.fury.io/rb/click)
|
5
|
+
[](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
|
data/bin/click-console
CHANGED
@@ -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|
|
data/click.gemspec
CHANGED
@@ -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
|
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
|
|
data/demo/app.rb
CHANGED
@@ -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
|
data/lib/click/clicker.rb
CHANGED
@@ -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
|
55
|
+
Click::Database.with_database(connection_string) do
|
56
56
|
require 'click/database/writer'
|
57
|
-
writer = Click::Database::Writer.new
|
57
|
+
writer = Click::Database::Writer.new
|
58
58
|
clicker = Click::Clicker.new(session_name)
|
59
59
|
clicker.add_observer(writer)
|
60
60
|
yield clicker
|
data/lib/click/database.rb
CHANGED
@@ -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
|
-
|
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
|
29
|
-
Click::Database::Models::Snapshot
|
30
|
-
Click::Database::Models::ObjectCount
|
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 :
|
23
|
+
attr_reader :session
|
28
24
|
end
|
29
25
|
end
|
30
26
|
end
|
data/lib/click/version.rb
CHANGED
data/spec/clicker_spec.rb
CHANGED
@@ -1,99 +1,93 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Click::Clicker do
|
4
|
-
|
4
|
+
Given(:session_name) { SecureRandom.uuid }
|
5
|
+
Given(:clicker) { described_class.new(session_name) }
|
5
6
|
|
6
7
|
describe '#session_name' do
|
7
|
-
|
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
|
-
|
14
|
-
klass = Class.new
|
12
|
+
Given(:klass) { Class.new }
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
describe 'with no instances' do
|
15
|
+
When { clicker.click! }
|
18
16
|
|
19
|
-
|
17
|
+
Then { clicker.instance_count(klass) == 0 }
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
describe 'with instances' do
|
21
|
+
Given!(:object) { klass.new }
|
23
22
|
|
24
|
-
|
23
|
+
When { clicker.click! }
|
25
24
|
|
26
|
-
clicker.
|
27
|
-
expect(clicker.instance_count(klass)).to eq(0)
|
25
|
+
Then { clicker.instance_count(klass) == 1 }
|
28
26
|
end
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
describe 'symbols' do
|
29
|
+
Given { clicker.click! }
|
30
|
+
Given!(:original_symbol_count) { clicker.instance_count(Symbol) }
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
37
|
+
describe 'BasicObject' do
|
38
|
+
Given { clicker.click! }
|
39
|
+
Given!(:original_count) { clicker.instance_count(Click::Indeterminable) }
|
44
40
|
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
73
|
-
ObjectSpace.
|
74
|
-
Symbol.
|
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
|
-
|
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
|
-
|
88
|
-
|
82
|
+
When { clicker.add_observer(observer) }
|
83
|
+
|
84
|
+
Then { expect(observer).to have_received(:on_add).with(clicker) }
|
89
85
|
|
90
|
-
|
91
|
-
|
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
|
-
|
96
|
-
clicker
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
10
|
+
describe 'the .by_name dataset' do
|
11
|
+
Given(:session) { Session.create(name: session_name, started_at: Time.now) }
|
15
12
|
|
16
|
-
|
17
|
-
|
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
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
32
|
+
Then { expect(dataset.sessions.all).to match_array([parent_session, other_parent_session]) }
|
33
|
+
end
|
61
34
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
70
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
2
|
require 'click'
|
3
|
-
|
4
|
-
|
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.
|
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-
|
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
|
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:
|