are_we_there_yet 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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