are_we_there_yet 0.2.1 → 1.0.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/Gemfile CHANGED
@@ -1,11 +1,13 @@
1
1
  source "http://rubygems.org"
2
- gem "sqlite3", "~>1.3.0"
2
+ gem "trollop", "~> 1.16.2"
3
+ gem "sequel", "~> 3.31.0"
4
+ gem "sqlite3", "~> 1.3.0"
3
5
  # Add dependencies to develop your gem here.
4
6
  # Include everything needed to run rake, tests, features, etc.
5
7
 
6
8
  group :development do
7
9
  gem "rspec", "~> 2.7.0"
8
- gem "bundler", "~> 1.0.0"
10
+ gem "bundler", "~> 1.1.0"
9
11
  gem "jeweler", "~> 1.6.4"
10
12
  gem "rcov", ">= 0"
11
13
  end
@@ -8,7 +8,7 @@ GEM
8
8
  git (>= 1.2.5)
9
9
  rake
10
10
  rake (0.9.2.2)
11
- rcov (0.9.11)
11
+ rcov (1.0.0)
12
12
  rspec (2.7.0)
13
13
  rspec-core (~> 2.7.0)
14
14
  rspec-expectations (~> 2.7.0)
@@ -17,14 +17,18 @@ GEM
17
17
  rspec-expectations (2.7.0)
18
18
  diff-lcs (~> 1.1.2)
19
19
  rspec-mocks (2.7.0)
20
+ sequel (3.31.0)
20
21
  sqlite3 (1.3.5)
22
+ trollop (1.16.2)
21
23
 
22
24
  PLATFORMS
23
25
  ruby
24
26
 
25
27
  DEPENDENCIES
26
- bundler (~> 1.0.0)
28
+ bundler (~> 1.1.0)
27
29
  jeweler (~> 1.6.4)
28
30
  rcov
29
31
  rspec (~> 2.7.0)
32
+ sequel (~> 3.31.0)
30
33
  sqlite3 (~> 1.3.0)
34
+ trollop (~> 1.16.2)
data/README.md CHANGED
@@ -3,32 +3,68 @@
3
3
  ## About
4
4
 
5
5
  AreWeThereYet is a gem that provides alternative profiling for RSpec 1.3.x for those who are not blessed enough to be using all the
6
- crunchy goodness that is RSpec 2.x. Metrics are tracked per file and per example in a SQLite3 database. The location of the
6
+ crunchy goodness that is RSpec 2.x. Metrics are tracked per file and per example in a SQLite3 database. The URI of the
7
7
  database is passed through as a parameter when running the specs
8
8
 
9
- AWTY only logs data, so you are currently required to handroll any reporting functionality. There is also, currently no data output
10
- to STDOUT when spec runs with this formatter.
9
+ AreWeThereYet does not produce any output to STDOUT during the spec run. Howeevr, it does provide some rudimentary reporting that can
10
+ be run against the data in the selected database.
11
+
12
+ V1.0 provides no backwards compatibility for data recorded by earlier versions.
11
13
 
12
14
  ## Usage
13
15
 
14
- Usage is fairly simple:
16
+ ### Logging of spec execution time
15
17
 
16
18
  1. Add `require 'are_we_there_yet'` to your `spec_helper.rb` file.
17
- 2. When running the specs pass the name of the class together with the location of your SQLite3 database, e.g:
18
- `spec -fAreWeThereYet:/path/to/db.sqlite3 spec`
19
+ 2. When running the specs pass the name of the class together with a database uri, e.g:
20
+ `spec -fAreWeThereYet::Recorder:sqlite:///path/to/db.sqlite3 spec`
19
21
 
20
22
  Only passing tests are profiled.
21
23
 
22
- ## Data Structure
24
+ ### Displaying results
25
+
26
+ AWTY currently offers two methods of listing execution times:
27
+
28
+ - By file, ordered by descending average execution time.
29
+ - By example, listing all the examples for a given file, ordered by descending average execution time. When listing by example, the
30
+ file path in question must also be supplied.
31
+
32
+ The results can either be displayed by using the executable provided by the AWTY gem or by including the gem in code of your choice,
33
+ and making use of the `AreWeThereYet::Profiler#list_files` or `AreWeThereYet::Profiler#list_examples` methods.
34
+
35
+ An example of using the executable:
36
+
37
+ `bundle exec are_we_there_yet --database_uri sqlite:///path/to/results.sqlite3 --list examples --file_path /path/to/spec`
38
+
39
+ `are_we_there_yet -h` will provide a list of available options.
40
+
41
+ Currently, the only output supported is csv. The generator of this is very rudimentary. If there are sufficient use cases where
42
+ example descriptions contain characters that will break the output (e.g. examples that contain double quotes within their
43
+ description) then it would make sense to use something like FasterCSV.
44
+
45
+ An example of including AreWeThereYet::Profiler in other Ruby code:
46
+
47
+ require 'rubygems'
48
+ require 'are_we_there_yet'
49
+
50
+ # Instantiate the Profiler class with a string containing the path to the location of the database
51
+ profiler = AreWeThereYet::Profiler.new(@db_name)
52
+
53
+ examples_for_file = profiler.list_examples("/path/to/spec")
54
+
55
+ Details of the output format as well as usage of `AreWeThereYet::Profiler#list_files` method can be found in
56
+ `spec/lib/profiler_spec.rb`.
57
+
58
+ ## Database
59
+
60
+ AWTY should support any database that Sequel (the current underlying ORM library) supports - simply specify the URI accordingly.
61
+
62
+ ## Acknowledgements
23
63
 
24
- The following data is stored in the database:
64
+ My employer, Hetzner (Pty) Ltd, generously allows me to work on Open Source projects during work hours - which accounted for most
65
+ of the time spent on this gem.
25
66
 
26
- - runs (from v0.2.0 onwards) - this is to allow conclusive tracking of metrics against a specific run. Multiple runs close to one
27
- may result in guesswork when determining which metrics belong to which run. v0.1.0 does not track this data, but v0.2.0 is backwards
28
- compatible and can handle the reduced fidelity when dealing with a database created by v0.1.0.
29
- - files - this represents the individual files containing the examples that are being run.
30
- - examples - the individual examples themselves (one file has many examples)
31
- - metrics - the run time per example (per run from v0.2.0 onwards) - one example has many metrics, one run has many metrics
67
+ Thanks also to Sheldon Hearn for guidance/code review.
32
68
 
33
69
  ## License
34
70
 
data/Rakefile CHANGED
@@ -21,6 +21,7 @@ Jeweler::Tasks.new do |gem|
21
21
  gem.description = %Q{Provides detailed profiling data for RSpec runs in a SQLite3 DB}
22
22
  gem.email = "rorymckinley@gmail.com"
23
23
  gem.authors = ["Rory McKinley"]
24
+ gem.bindir = 'bin'
24
25
  # dependencies defined in Gemfile
25
26
  end
26
27
  Jeweler::RubygemsDotOrgTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 1.0.0
@@ -4,14 +4,15 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{are_we_there_yet}
8
- s.version = "0.2.1"
7
+ s.name = "are_we_there_yet"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Rory McKinley"]
12
- s.date = %q{2012-01-10}
13
- s.description = %q{Provides detailed profiling data for RSpec runs in a SQLite3 DB}
14
- s.email = %q{rorymckinley@gmail.com}
12
+ s.date = "2012-05-09"
13
+ s.description = "Provides detailed profiling data for RSpec runs in a SQLite3 DB"
14
+ s.email = "rorymckinley@gmail.com"
15
+ s.executables = ["are_we_there_yet"]
15
16
  s.extra_rdoc_files = [
16
17
  "LICENSE.txt",
17
18
  "README.md"
@@ -26,37 +27,61 @@ Gem::Specification.new do |s|
26
27
  "Rakefile",
27
28
  "VERSION",
28
29
  "are_we_there_yet.gemspec",
30
+ "bin/are_we_there_yet",
29
31
  "lib/are_we_there_yet.rb",
30
- "spec/are_we_there_yet_spec.rb",
31
- "spec/spec_helper.rb"
32
+ "lib/are_we_there_yet/exceptions.rb",
33
+ "lib/are_we_there_yet/formatter.rb",
34
+ "lib/are_we_there_yet/metric.rb",
35
+ "lib/are_we_there_yet/persistence/connection.rb",
36
+ "lib/are_we_there_yet/persistence/schema.rb",
37
+ "lib/are_we_there_yet/profiler.rb",
38
+ "lib/are_we_there_yet/profiler_ui.rb",
39
+ "lib/are_we_there_yet/recorder.rb",
40
+ "lib/are_we_there_yet/run.rb",
41
+ "spec/lib/formatter_spec.rb",
42
+ "spec/lib/metric_spec.rb",
43
+ "spec/lib/persistence/connection_spec.rb",
44
+ "spec/lib/persistence/schema_spec.rb",
45
+ "spec/lib/profiler_spec.rb",
46
+ "spec/lib/profiler_ui_spec.rb",
47
+ "spec/lib/recorder_spec.rb",
48
+ "spec/lib/run_spec.rb",
49
+ "spec/spec_helper.rb",
50
+ "spec/support/spec_classes.rb",
51
+ "spec/support/symbol.rb"
32
52
  ]
33
- s.homepage = %q{http://github.com/rorymckinley/are_we_there_yet}
53
+ s.homepage = "http://github.com/rorymckinley/are_we_there_yet"
34
54
  s.licenses = ["MIT"]
35
55
  s.require_paths = ["lib"]
36
- s.rubygems_version = %q{1.3.7}
37
- s.summary = %q{Profiler for RSpec 1.3.x}
56
+ s.rubygems_version = "1.8.17"
57
+ s.summary = "Profiler for RSpec 1.3.x"
38
58
 
39
59
  if s.respond_to? :specification_version then
40
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
60
  s.specification_version = 3
42
61
 
43
62
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
63
+ s.add_runtime_dependency(%q<trollop>, ["~> 1.16.2"])
64
+ s.add_runtime_dependency(%q<sequel>, ["~> 3.31.0"])
44
65
  s.add_runtime_dependency(%q<sqlite3>, ["~> 1.3.0"])
45
66
  s.add_development_dependency(%q<rspec>, ["~> 2.7.0"])
46
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
67
+ s.add_development_dependency(%q<bundler>, ["~> 1.1.0"])
47
68
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
48
69
  s.add_development_dependency(%q<rcov>, [">= 0"])
49
70
  else
71
+ s.add_dependency(%q<trollop>, ["~> 1.16.2"])
72
+ s.add_dependency(%q<sequel>, ["~> 3.31.0"])
50
73
  s.add_dependency(%q<sqlite3>, ["~> 1.3.0"])
51
74
  s.add_dependency(%q<rspec>, ["~> 2.7.0"])
52
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
75
+ s.add_dependency(%q<bundler>, ["~> 1.1.0"])
53
76
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
54
77
  s.add_dependency(%q<rcov>, [">= 0"])
55
78
  end
56
79
  else
80
+ s.add_dependency(%q<trollop>, ["~> 1.16.2"])
81
+ s.add_dependency(%q<sequel>, ["~> 3.31.0"])
57
82
  s.add_dependency(%q<sqlite3>, ["~> 1.3.0"])
58
83
  s.add_dependency(%q<rspec>, ["~> 2.7.0"])
59
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
84
+ s.add_dependency(%q<bundler>, ["~> 1.1.0"])
60
85
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
61
86
  s.add_dependency(%q<rcov>, [">= 0"])
62
87
  end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+
4
+ require 'are_we_there_yet'
5
+ require 'trollop'
6
+
7
+ opts = Trollop::options do
8
+ opt :database_uri, "Database URI for the db that contains the execution times", :type => String
9
+ opt :list, "The type of list required - currently the options are 'files' or 'examples'", :type => String
10
+ opt :file_path, "The path for the spec file - used when listing examples for a given file", :type => String
11
+ end
12
+
13
+ AreWeThereYet::ProfilerUI.get_profiler_output(opts.delete(:database_uri), STDOUT, :list => opts[:list],
14
+ :file_path => opts[:file_path])
@@ -1,108 +1,4 @@
1
1
  require "spec/runner/formatter/base_formatter" unless defined? AWTY_SPEC_RUN
2
- require 'sqlite3'
2
+ require 'sequel'
3
3
 
4
- class AreWeThereYet < Spec::Runner::Formatter::BaseFormatter
5
- def initialize(options,where)
6
- @db = SQLite3::Database.new(where)
7
-
8
- create_tables
9
-
10
- log_run
11
- end
12
-
13
- def example_started(example)
14
- @start = Time.now
15
- end
16
-
17
- def example_passed(example)
18
- @db.transaction do |db|
19
- location_id = persist_file(db, example)
20
-
21
- example_id = persist_example(db, example, location_id)
22
-
23
- persist_metric(db, example_id)
24
- end
25
- end
26
-
27
- def close
28
- @db.execute(
29
- "UPDATE runs SET ended_at = :end_time WHERE id = :run_id",
30
- :end_time => Time.now.utc.strftime("%Y-%m-%d %H:%M:%S"),
31
- :run_id => @run_id
32
- ) if tracking_runs?
33
- @db.close
34
- end
35
-
36
- private
37
-
38
- def log_run
39
- @db.execute("INSERT INTO runs(id) VALUES(NULL)") if tracking_runs?
40
- @run_id = @db.last_insert_row_id
41
- end
42
-
43
- def persist_file(db, example)
44
- path = example.location.split(':').first
45
-
46
- locations = db.execute("SELECT id FROM files WHERE path = :path", :path => path)
47
- if locations.empty?
48
- db.execute("INSERT INTO files(path) VALUES(:path)", :path => path)
49
- db.last_insert_row_id
50
- else
51
- locations.first[0]
52
- end
53
- end
54
-
55
- def persist_example(db, example, file_id)
56
- examples = db.execute(
57
- "SELECT id FROM examples WHERE file_id = :file_id AND description = :description",
58
- :file_id => file_id,
59
- :description => example.description
60
- )
61
- if examples.empty?
62
- db.execute(
63
- "INSERT INTO examples(file_id, description) VALUES(:file_id, :description)",
64
- :file_id => file_id,
65
- :description => example.description
66
- )
67
- db.last_insert_row_id
68
- else
69
- examples.first[0]
70
- end
71
- end
72
-
73
- def persist_metric(db, example_id)
74
- if tracking_runs?
75
- db.execute(
76
- "INSERT INTO metrics(example_id, execution_time, run_id) VALUES(:example_id, :execution_time, :run_id)",
77
- :example_id => db.last_insert_row_id,
78
- :execution_time => Time.now - @start,
79
- :run_id => @run_id
80
- )
81
- else
82
- db.execute(
83
- "INSERT INTO metrics(example_id, execution_time) VALUES(:example_id, :execution_time)",
84
- :example_id => db.last_insert_row_id,
85
- :execution_time => Time.now - @start
86
- )
87
- end
88
- end
89
-
90
- def create_tables
91
- existing_tables = @db.execute("SELECT name FROM sqlite_master")
92
-
93
- if existing_tables.empty?
94
- @db.transaction do |db|
95
- db.execute("CREATE TABLE runs(id INTEGER PRIMARY KEY, started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ended_at TIMESTAMP)")
96
- db.execute("CREATE TABLE files(id INTEGER PRIMARY KEY, path VARCHAR(255))")
97
- db.execute("CREATE INDEX path ON files (path)")
98
- db.execute("CREATE TABLE examples(id INTEGER PRIMARY KEY, file_id INTEGER, description TEXT)")
99
- db.execute("CREATE INDEX file_description ON examples (file_id, description)")
100
- db.execute("CREATE TABLE metrics(id INTEGER PRIMARY KEY, example_id INTEGER, execution_time FLOAT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, run_id INTEGER )")
101
- end
102
- end
103
- end
104
-
105
- def tracking_runs?
106
- @tracking_runs ||= @db.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'runs'").any?
107
- end
108
- end
4
+ Dir["#{File.dirname(__FILE__)}/are_we_there_yet/**/*.rb"].each {|f| require f}
@@ -0,0 +1,3 @@
1
+ module AreWeThereYet
2
+ class InvalidDBLocation < StandardError; end
3
+ end
@@ -0,0 +1,17 @@
1
+ module AreWeThereYet
2
+ class Formatter
3
+ def self.format_for_output(data)
4
+ if data.first.has_key? :file
5
+ headers = %Q{"File Path","Average Execution Time"\n\n}
6
+ data.inject(headers) do |output_string, metric_record|
7
+ output_string += %Q{"#{metric_record[:file]}",#{metric_record[:average_execution_time]}\n}
8
+ end
9
+ else
10
+ headers = %Q{"Example","Average Execution Time"\n\n}
11
+ data.inject(headers) do |output_string, metric_record|
12
+ output_string += %Q{"#{metric_record[:example]}",#{metric_record[:average_execution_time]}\n}
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module AreWeThereYet
2
+ class Metric
3
+ attr_reader :id, :execution_time, :path, :run_id, :description
4
+ def initialize(options={})
5
+ @id = options[:id]
6
+ @execution_time = options[:execution_time]
7
+ @path = options[:path]
8
+ @run_id = options[:run_id]
9
+ @description = options[:description]
10
+ end
11
+
12
+ # Only works for creates not for updates - will need to be cleverer if we ever need to provide for updates
13
+ def save(datastore)
14
+ @id = datastore[:metrics].insert(to_h.merge(:created_at => Time.now.utc))
15
+ end
16
+
17
+ def self.all(datastore)
18
+ datastore[:metrics].all.map { |record| new record }
19
+ end
20
+
21
+ private
22
+
23
+ def to_h
24
+ { :execution_time => execution_time, :path => path, :description => description, :run_id => run_id }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module AreWeThereYet
2
+ module Persistence
3
+ module Connection
4
+ class InvalidDBLocation < StandardError; end
5
+ def self.create(uri)
6
+ Sequel.connect(uri)
7
+ rescue
8
+ raise AreWeThereYet::Persistence::Connection::InvalidDBLocation,
9
+ "Could not connect to the database specified by the URI - please check that the location is valid"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module AreWeThereYet
2
+ module Persistence
3
+ module Schema
4
+ @@tables = {
5
+ :runs => Proc.new {
6
+ primary_key :id
7
+ DateTime :started_at
8
+ DateTime :ended_at
9
+ },
10
+ :metrics => Proc.new {
11
+ primary_key :id
12
+ String :path
13
+ column :description, :text
14
+ Float :execution_time
15
+ DateTime :created_at
16
+ Integer :run_id
17
+ index :path
18
+ index :description
19
+ }
20
+ }
21
+ def self.create(connection)
22
+ if connection.tables.empty?
23
+ connection.transaction do
24
+ @@tables.each do |name,attributes|
25
+ connection.create_table(name, &attributes)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end