rspec_profiling 0.0.1
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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +129 -0
- data/Rakefile +3 -0
- data/lib/rspec_profiling.rb +19 -0
- data/lib/rspec_profiling/collectors/csv.rb +49 -0
- data/lib/rspec_profiling/collectors/database.rb +98 -0
- data/lib/rspec_profiling/config.rb +14 -0
- data/lib/rspec_profiling/console.rb +3 -0
- data/lib/rspec_profiling/current_commit.rb +15 -0
- data/lib/rspec_profiling/example.rb +90 -0
- data/lib/rspec_profiling/rspec.rb +9 -0
- data/lib/rspec_profiling/run.rb +55 -0
- data/lib/rspec_profiling/version.rb +3 -0
- data/lib/tasks/rspec_profiling.rake +30 -0
- data/rspec_profiling +0 -0
- data/rspec_profiling.gemspec +27 -0
- data/spec/collectors/database_spec.rb +80 -0
- data/spec/run_spec.rb +135 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MjdkNDIyMmIzYWQ1YjFlZjgwYzEwOTFmZjgzZDY1YzM2N2NmZDI3Yw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTRmYWE0OWRkMmUzOTZlZjQ5MTQzYWE5MjZjMTg3N2JkZTY5NGMxYg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YWZkYmMzZjYzNWJkMjZmNzFhODFjZjJlMmFlNzExYTRmZmI4ZmY0MTU3NTNh
|
10
|
+
MzhmYzEwMGUyYjBmYmFkYTM3YzBlNTRhMWNhMjlkNzM4MDMwYWI2NTJiZDg5
|
11
|
+
NGFlYzk0YTEwOGE3MWYwNTdjNTY0MjBiYTlmNTJmMzJjOGUxNGI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MTkyMmMxNWU2NDQyNDRkMzExNzgyMjRkNjExNzZhODhkMmM0MzJjYzdhNjQ2
|
14
|
+
MDYwMWQwZjExMGJlOTBkZGNkOWY5MjUyN2FhNTJmM2Q4ZmJkZjE1MTcyMTUw
|
15
|
+
ZWYzODE4NjNjOWI4NTA1NGFkODZiNmFkOGE1NmJlMmI3MGY5Mjc=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ben Eddy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# RSpecProfiling
|
2
|
+
|
3
|
+
Collects profiles of RSpec test suites, enabling you to identify specs
|
4
|
+
with interesting attributes. For example, find the slowest specs, or the
|
5
|
+
spec which issues the most queries.
|
6
|
+
|
7
|
+
Collected attributes include:
|
8
|
+
- git commit SHA and date
|
9
|
+
- example file, line number and description
|
10
|
+
- example status (i.e. passed or failed)
|
11
|
+
- example time
|
12
|
+
- query count and time
|
13
|
+
- request count and time
|
14
|
+
|
15
|
+
## Compatibility
|
16
|
+
|
17
|
+
RSpecProfiling should work with Rails >= 3.2 and RSpec >= 2.14.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```
|
24
|
+
gem 'rspec_profiling'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
```
|
30
|
+
bundle
|
31
|
+
```
|
32
|
+
|
33
|
+
Require the gem to your `spec_helper.rb`.
|
34
|
+
|
35
|
+
```
|
36
|
+
require "rspec_profiling/rspec"
|
37
|
+
```
|
38
|
+
|
39
|
+
Lastly, run the installation rake tasks to initialize an empty database in
|
40
|
+
which results will be collected.
|
41
|
+
|
42
|
+
```
|
43
|
+
bundle exec rake rspec_profiling:install
|
44
|
+
```
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
### Choose a results collector
|
49
|
+
|
50
|
+
Results are collected just by running the specs.
|
51
|
+
|
52
|
+
#### SQLite3
|
53
|
+
|
54
|
+
By default, profiles are collected in an SQL database. Make sure you've
|
55
|
+
run the installation rake task before attempting.
|
56
|
+
|
57
|
+
You can review results by running the RSpecProfiling console.
|
58
|
+
|
59
|
+
```
|
60
|
+
bundle exec rake rspec_profiling:console
|
61
|
+
|
62
|
+
> results.count
|
63
|
+
=> 1970
|
64
|
+
|
65
|
+
> results.order(:query_count).last.to_s
|
66
|
+
=> "Updating my account - ./spec/features/account_spec.rb:15"
|
67
|
+
```
|
68
|
+
|
69
|
+
The console has a preloaded `results` variable.
|
70
|
+
|
71
|
+
```
|
72
|
+
results.count
|
73
|
+
> 180
|
74
|
+
```
|
75
|
+
|
76
|
+
#### CSV
|
77
|
+
|
78
|
+
You can configure `RSpecProfiling` to collect results in a CSV in your
|
79
|
+
`spec_helper.rb` file.
|
80
|
+
|
81
|
+
```Ruby
|
82
|
+
RSpecProfiling.configure do |config|
|
83
|
+
config.collector = RSpecProfiling::Collectors::CSV
|
84
|
+
end
|
85
|
+
|
86
|
+
require "rspec_profiling/rspec"
|
87
|
+
```
|
88
|
+
|
89
|
+
By default, the CSV is output to `cat tmp/spec_benchmarks.csv`.
|
90
|
+
Rerunning spec will overwrite the file. You can customize the CSV path
|
91
|
+
to, for example, include the sample time.
|
92
|
+
|
93
|
+
```Ruby
|
94
|
+
RSpecProfiling.configure do |config|
|
95
|
+
config.collector = RSpecProfiling::Collectors::CSV
|
96
|
+
config.csv_path = ->{ "tmp/spec_benchmark_#{Time.now.to_i}" }
|
97
|
+
end
|
98
|
+
|
99
|
+
require "rspec_profiling/rspec"
|
100
|
+
```
|
101
|
+
|
102
|
+
## Configuration Options
|
103
|
+
|
104
|
+
Configuration is performed like this:
|
105
|
+
|
106
|
+
```Ruby
|
107
|
+
RSpecProfiling.configure do |config|
|
108
|
+
config.<option> = <something>
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Options
|
113
|
+
|
114
|
+
- `db_path` - the location of the SQLite database file
|
115
|
+
- `table_name` - the database table name in which results are stored
|
116
|
+
- `csv_path` - the directory in which CSV files are dumped
|
117
|
+
|
118
|
+
## Uninstalling
|
119
|
+
|
120
|
+
To remove the results database, run `bundle exec rake
|
121
|
+
rspec_profiling:uninstall`.
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
1. Fork it
|
126
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
127
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
128
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
129
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "rails"
|
3
|
+
require "rspec_profiling/config"
|
4
|
+
require "rspec_profiling/version"
|
5
|
+
require "rspec_profiling/run"
|
6
|
+
require "rspec_profiling/collectors/csv"
|
7
|
+
require "rspec_profiling/collectors/database"
|
8
|
+
|
9
|
+
module RspecProfiling
|
10
|
+
class Railtie < Rails::Railtie
|
11
|
+
railtie_name :rspec_profiling
|
12
|
+
|
13
|
+
rake_tasks do
|
14
|
+
load "tasks/rspec_profiling.rake"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpecProfiling = RspecProfiling
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "csv"
|
2
|
+
|
3
|
+
module RspecProfiling
|
4
|
+
module Collectors
|
5
|
+
class CSV
|
6
|
+
HEADERS = %w{
|
7
|
+
commit_sha
|
8
|
+
commit_date
|
9
|
+
file
|
10
|
+
line_number
|
11
|
+
description
|
12
|
+
status
|
13
|
+
time
|
14
|
+
query_count
|
15
|
+
query_time
|
16
|
+
request_count
|
17
|
+
request_time
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.install
|
21
|
+
# no op
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.uninstall
|
25
|
+
# no op
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset
|
29
|
+
# no op
|
30
|
+
end
|
31
|
+
|
32
|
+
def insert(attributes)
|
33
|
+
output << HEADERS.map do |field|
|
34
|
+
attributes.fetch(field.to_sym)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def output
|
41
|
+
@output ||= ::CSV.open(path, "w").tap { |csv| csv << HEADERS }
|
42
|
+
end
|
43
|
+
|
44
|
+
def path
|
45
|
+
RspecProfiling.config.csv_path.call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
require "active_record"
|
3
|
+
|
4
|
+
module RspecProfiling
|
5
|
+
module Collectors
|
6
|
+
class Database
|
7
|
+
def self.install
|
8
|
+
new.install
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.uninstall
|
12
|
+
new.uninstall
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.reset
|
16
|
+
new.results.destroy_all
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
establish_connection
|
21
|
+
end
|
22
|
+
|
23
|
+
def install
|
24
|
+
return if prepared?
|
25
|
+
|
26
|
+
connection.create_table(table) do |t|
|
27
|
+
t.string :commit_sha
|
28
|
+
t.datetime :commit_date
|
29
|
+
t.text :file
|
30
|
+
t.integer :line_number
|
31
|
+
t.text :description
|
32
|
+
t.decimal :time
|
33
|
+
t.string :status
|
34
|
+
t.integer :query_count
|
35
|
+
t.decimal :query_time
|
36
|
+
t.integer :request_count
|
37
|
+
t.decimal :request_time
|
38
|
+
t.timestamps
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def uninstall
|
43
|
+
connection.drop_table(table)
|
44
|
+
end
|
45
|
+
|
46
|
+
def insert(attributes)
|
47
|
+
results.create!(attributes.except(:created_at))
|
48
|
+
end
|
49
|
+
|
50
|
+
def results
|
51
|
+
@results ||= begin
|
52
|
+
establish_connection
|
53
|
+
|
54
|
+
Result.table_name = table
|
55
|
+
Result.attr_protected if Result.respond_to?(:attr_protected)
|
56
|
+
Result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def prepared?
|
63
|
+
connection.table_exists?(table)
|
64
|
+
end
|
65
|
+
|
66
|
+
def connection
|
67
|
+
@connection ||= results.connection
|
68
|
+
end
|
69
|
+
|
70
|
+
def establish_connection
|
71
|
+
Result.establish_connection(
|
72
|
+
:adapter => "sqlite3",
|
73
|
+
:database => database
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def table
|
78
|
+
RspecProfiling.config.table_name
|
79
|
+
end
|
80
|
+
|
81
|
+
def database
|
82
|
+
RspecProfiling.config.db_path
|
83
|
+
end
|
84
|
+
|
85
|
+
class Result < ActiveRecord::Base
|
86
|
+
def to_s
|
87
|
+
[description, location].join(" - ")
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def location
|
93
|
+
[file, line_number].join(":")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RspecProfiling
|
2
|
+
def self.configure
|
3
|
+
yield config
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.config
|
7
|
+
@config ||= OpenStruct.new({
|
8
|
+
collector: RspecProfiling::Collectors::Database,
|
9
|
+
db_path: 'tmp/rspec_profiling',
|
10
|
+
table_name: 'spec_profiling_results',
|
11
|
+
csv_path: Proc.new { 'tmp/spec_benchmarks.csv' }
|
12
|
+
})
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
3
|
+
module RspecProfiling
|
4
|
+
class Example
|
5
|
+
IGNORED_QUERIES_PATTERN = %r{(
|
6
|
+
pg_table|
|
7
|
+
pg_attribute|
|
8
|
+
pg_namespace|
|
9
|
+
show\stables|
|
10
|
+
pragma|
|
11
|
+
sqlite_master/rollback|
|
12
|
+
^TRUNCATE TABLE|
|
13
|
+
^ALTER TABLE|
|
14
|
+
^BEGIN|
|
15
|
+
^COMMIT|
|
16
|
+
^ROLLBACK|
|
17
|
+
^RELEASE|
|
18
|
+
^SAVEPOINT
|
19
|
+
)}xi
|
20
|
+
|
21
|
+
def initialize(example)
|
22
|
+
@example = example
|
23
|
+
@counts = Hash.new(0)
|
24
|
+
end
|
25
|
+
|
26
|
+
def file
|
27
|
+
metadata[:file_path]
|
28
|
+
end
|
29
|
+
|
30
|
+
def line_number
|
31
|
+
metadata[:line_number]
|
32
|
+
end
|
33
|
+
|
34
|
+
def description
|
35
|
+
metadata[:full_description]
|
36
|
+
end
|
37
|
+
|
38
|
+
def status
|
39
|
+
execution_result.status
|
40
|
+
end
|
41
|
+
|
42
|
+
def time
|
43
|
+
execution_result.run_time
|
44
|
+
end
|
45
|
+
|
46
|
+
def query_count
|
47
|
+
counts[:query_count]
|
48
|
+
end
|
49
|
+
|
50
|
+
def query_time
|
51
|
+
counts[:query_time]
|
52
|
+
end
|
53
|
+
|
54
|
+
def request_count
|
55
|
+
counts[:request_count]
|
56
|
+
end
|
57
|
+
|
58
|
+
def request_time
|
59
|
+
counts[:request_time]
|
60
|
+
end
|
61
|
+
|
62
|
+
def log_query(query, start, finish)
|
63
|
+
unless query[:sql] =~ IGNORED_QUERIES_PATTERN
|
64
|
+
counts[:query_count] += 1
|
65
|
+
counts[:query_time] += (finish - start)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def log_request(request, start, finish)
|
70
|
+
counts[:request_count] += 1
|
71
|
+
counts[:request_time] += request[:view_runtime].to_f
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_reader :example, :counts
|
77
|
+
|
78
|
+
def execution_result
|
79
|
+
@execution_result ||= begin
|
80
|
+
result = example.execution_result
|
81
|
+
result = OpenStruct.new(result) if result.is_a?(Hash)
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def metadata
|
87
|
+
example.metadata
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "rspec_profiling/example"
|
2
|
+
require "rspec_profiling/current_commit"
|
3
|
+
|
4
|
+
module RspecProfiling
|
5
|
+
class Run
|
6
|
+
def initialize(collector)
|
7
|
+
@collector = collector
|
8
|
+
end
|
9
|
+
|
10
|
+
def start(*args)
|
11
|
+
start_counting_queries
|
12
|
+
start_counting_requests
|
13
|
+
end
|
14
|
+
|
15
|
+
def example_started(example)
|
16
|
+
example = example.example if example.respond_to?(:example)
|
17
|
+
@current_example = Example.new(example)
|
18
|
+
end
|
19
|
+
|
20
|
+
def example_finished(*args)
|
21
|
+
collector.insert({
|
22
|
+
commit_sha: CurrentCommit.sha,
|
23
|
+
commit_date: CurrentCommit.time,
|
24
|
+
file: @current_example.file,
|
25
|
+
line_number: @current_example.line_number,
|
26
|
+
description: @current_example.description,
|
27
|
+
status: @current_example.status,
|
28
|
+
time: @current_example.time,
|
29
|
+
query_count: @current_example.query_count,
|
30
|
+
query_time: @current_example.query_time,
|
31
|
+
request_count: @current_example.request_count,
|
32
|
+
request_time: @current_example.request_time
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :example_passed :example_finished
|
37
|
+
alias :example_failed :example_finished
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :collector
|
42
|
+
|
43
|
+
def start_counting_queries
|
44
|
+
ActiveSupport::Notifications.subscribe("sql.active_record") do |name, start, finish, id, query|
|
45
|
+
@current_example.try(:log_query, query, start, finish)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def start_counting_requests
|
50
|
+
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |name, start, finish, id, request|
|
51
|
+
@current_example.try(:log_request, request, start, finish)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
namespace :rspec_profiling do
|
4
|
+
desc "Install the collector"
|
5
|
+
task :install do
|
6
|
+
collector.install
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Uninstall the collector"
|
10
|
+
task :uninstall do
|
11
|
+
collector.uninstall
|
12
|
+
end
|
13
|
+
|
14
|
+
task :console do
|
15
|
+
require 'irb'
|
16
|
+
require 'irb/completion'
|
17
|
+
require 'rspec_profiling'
|
18
|
+
require 'rspec_profiling/console'
|
19
|
+
ARGV.clear
|
20
|
+
IRB.start
|
21
|
+
end
|
22
|
+
|
23
|
+
task :reset do
|
24
|
+
collector.reset
|
25
|
+
end
|
26
|
+
|
27
|
+
def collector
|
28
|
+
RspecProfiling.config.collector
|
29
|
+
end
|
30
|
+
end
|
data/rspec_profiling
ADDED
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rspec_profiling/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rspec_profiling"
|
8
|
+
spec.version = RspecProfiling::VERSION
|
9
|
+
spec.authors = ["Ben Eddy"]
|
10
|
+
spec.email = ["bae@foraker.com"]
|
11
|
+
spec.description = %q{Profile RSpec test suites}
|
12
|
+
spec.summary = %q{Profile RSpec test suites}
|
13
|
+
spec.homepage = "https://github.com/foraker/rspec_profiling"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "sqlite3"
|
22
|
+
spec.add_dependency "activerecord"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "rspec_profiling/config"
|
2
|
+
require "rspec_profiling/collectors/database"
|
3
|
+
|
4
|
+
module RspecProfiling
|
5
|
+
module Collectors
|
6
|
+
describe Database do
|
7
|
+
before(:all) { Database.install }
|
8
|
+
after(:all) { Database.uninstall }
|
9
|
+
|
10
|
+
describe "#insert" do
|
11
|
+
let(:collector) { described_class.new }
|
12
|
+
let(:result) { collector.results.first }
|
13
|
+
|
14
|
+
before do
|
15
|
+
collector.insert({
|
16
|
+
commit_sha: "ABC123",
|
17
|
+
commit_date: "Thu Dec 18 12:00:00 2012",
|
18
|
+
file: "/some/file.rb",
|
19
|
+
line_number: 10,
|
20
|
+
description: "Some spec",
|
21
|
+
time: 100,
|
22
|
+
status: :passed,
|
23
|
+
query_count: 10,
|
24
|
+
query_time: 50,
|
25
|
+
request_count: 1,
|
26
|
+
request_time: 400
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
it "records a single result" do
|
31
|
+
expect(collector.results.count).to eq 1
|
32
|
+
end
|
33
|
+
|
34
|
+
it "records the commit SHA" do
|
35
|
+
expect(result.commit_sha).to eq "ABC123"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "records the commit date" do
|
39
|
+
expect(result.commit_date).to eq Time.utc(2012, 12, 18, 12)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "records the file" do
|
43
|
+
expect(result.file).to eq "/some/file.rb"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "records the line number" do
|
47
|
+
expect(result.line_number).to eq 10
|
48
|
+
end
|
49
|
+
|
50
|
+
it "records the description" do
|
51
|
+
expect(result.description).to eq "Some spec"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "records the time" do
|
55
|
+
expect(result.time).to eq 100.0
|
56
|
+
end
|
57
|
+
|
58
|
+
it "records the passing status" do
|
59
|
+
expect(result.status).to eq 'passed'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "records the query count" do
|
63
|
+
expect(result.query_count).to eq 10
|
64
|
+
end
|
65
|
+
|
66
|
+
it "records the query time" do
|
67
|
+
expect(result.query_time).to eq 50
|
68
|
+
end
|
69
|
+
|
70
|
+
it "records the request count" do
|
71
|
+
expect(result.request_count).to eq 1
|
72
|
+
end
|
73
|
+
|
74
|
+
it "records the request time" do
|
75
|
+
expect(result.request_time).to eq 400
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/spec/run_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require "active_support/core_ext"
|
2
|
+
require "rspec_profiling/run"
|
3
|
+
require "time"
|
4
|
+
require "ostruct"
|
5
|
+
|
6
|
+
module RspecProfiling
|
7
|
+
describe Run do
|
8
|
+
def simulate_query(sql)
|
9
|
+
ActiveSupport::Notifications.instrument("sql.active_record", "sql", 100, 200, 1, {
|
10
|
+
sql: sql
|
11
|
+
})
|
12
|
+
end
|
13
|
+
|
14
|
+
def simulate_request
|
15
|
+
ActiveSupport::Notifications.instrument("process_action.action_controller", "request", 100, 400, 2, {
|
16
|
+
view_runtime: 10
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#run_example" do
|
21
|
+
let(:collector) { CollectorDouble.new }
|
22
|
+
let(:run) { described_class.new(collector) }
|
23
|
+
let(:result) { collector.results.first }
|
24
|
+
let(:commit) do
|
25
|
+
double({
|
26
|
+
sha: "abc123",
|
27
|
+
time: Time.new(2012, 12, 12)
|
28
|
+
})
|
29
|
+
end
|
30
|
+
let(:example) do
|
31
|
+
ExampleDouble.new({
|
32
|
+
file_path: "/something_spec.rb",
|
33
|
+
line_number: 15,
|
34
|
+
full_description: "should do something"
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
def simulate_test_suite_run
|
39
|
+
run.start
|
40
|
+
run.example_started(double(example: example))
|
41
|
+
simulate_query "SELECT * FROM users LIMIT 1;"
|
42
|
+
simulate_query "SELECT * FROM comments WHERE user_id = 1;"
|
43
|
+
simulate_request
|
44
|
+
run.example_passed
|
45
|
+
end
|
46
|
+
|
47
|
+
before do
|
48
|
+
stub_const("RspecProfiling::CurrentCommit", commit)
|
49
|
+
stub_const("ActiveSupport::Notifications", Notifications.new)
|
50
|
+
simulate_test_suite_run
|
51
|
+
end
|
52
|
+
|
53
|
+
it "collects a single example" do
|
54
|
+
expect(collector.count).to eq 1
|
55
|
+
end
|
56
|
+
|
57
|
+
it "counts two queries" do
|
58
|
+
expect(result.query_count).to eq 2
|
59
|
+
end
|
60
|
+
|
61
|
+
it "counts one request" do
|
62
|
+
expect(result.request_count).to eq 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "records the file" do
|
66
|
+
expect(result.file).to eq "/something_spec.rb"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "records the file number" do
|
70
|
+
expect(result.line_number).to eq 15
|
71
|
+
end
|
72
|
+
|
73
|
+
it "records the description" do
|
74
|
+
expect(result.description).to eq "should do something"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "records the time" do
|
78
|
+
expect(result.time).to eq 500
|
79
|
+
end
|
80
|
+
|
81
|
+
it "records the query time" do
|
82
|
+
expect(result.query_time).to eq 200
|
83
|
+
end
|
84
|
+
|
85
|
+
it "records the request time" do
|
86
|
+
expect(result.request_time).to eq 10
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class CollectorDouble
|
91
|
+
attr_reader :results
|
92
|
+
|
93
|
+
def initialize
|
94
|
+
@results = []
|
95
|
+
end
|
96
|
+
|
97
|
+
def insert(result)
|
98
|
+
@results << OpenStruct.new(result)
|
99
|
+
end
|
100
|
+
|
101
|
+
def count
|
102
|
+
results.count
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class ExampleDouble
|
107
|
+
attr_reader :metadata
|
108
|
+
|
109
|
+
def initialize(metadata)
|
110
|
+
@metadata = metadata
|
111
|
+
end
|
112
|
+
|
113
|
+
def execution_result
|
114
|
+
OpenStruct.new({
|
115
|
+
run_time: 500,
|
116
|
+
status: :passed
|
117
|
+
})
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Notifications
|
122
|
+
def initialize
|
123
|
+
@subscriptions = Hash.new { |h, k| h[k] = [] }
|
124
|
+
end
|
125
|
+
|
126
|
+
def subscribe(event, &block)
|
127
|
+
@subscriptions[event].push block
|
128
|
+
end
|
129
|
+
|
130
|
+
def instrument(event, *args)
|
131
|
+
@subscriptions[event].each { |callback| callback.call(*args) }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec_profiling
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Eddy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sqlite3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Profile RSpec test suites
|
84
|
+
email:
|
85
|
+
- bae@foraker.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- .gitignore
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- lib/rspec_profiling.rb
|
96
|
+
- lib/rspec_profiling/collectors/csv.rb
|
97
|
+
- lib/rspec_profiling/collectors/database.rb
|
98
|
+
- lib/rspec_profiling/config.rb
|
99
|
+
- lib/rspec_profiling/console.rb
|
100
|
+
- lib/rspec_profiling/current_commit.rb
|
101
|
+
- lib/rspec_profiling/example.rb
|
102
|
+
- lib/rspec_profiling/rspec.rb
|
103
|
+
- lib/rspec_profiling/run.rb
|
104
|
+
- lib/rspec_profiling/version.rb
|
105
|
+
- lib/tasks/rspec_profiling.rake
|
106
|
+
- rspec_profiling
|
107
|
+
- rspec_profiling.gemspec
|
108
|
+
- spec/collectors/database_spec.rb
|
109
|
+
- spec/run_spec.rb
|
110
|
+
homepage: https://github.com/foraker/rspec_profiling
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.0.3
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Profile RSpec test suites
|
134
|
+
test_files:
|
135
|
+
- spec/collectors/database_spec.rb
|
136
|
+
- spec/run_spec.rb
|
137
|
+
has_rdoc:
|