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 +4 -2
- data/Gemfile.lock +6 -2
- data/README.md +50 -14
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/are_we_there_yet.gemspec +39 -14
- data/bin/are_we_there_yet +14 -0
- data/lib/are_we_there_yet.rb +2 -106
- data/lib/are_we_there_yet/exceptions.rb +3 -0
- data/lib/are_we_there_yet/formatter.rb +17 -0
- data/lib/are_we_there_yet/metric.rb +27 -0
- data/lib/are_we_there_yet/persistence/connection.rb +13 -0
- data/lib/are_we_there_yet/persistence/schema.rb +32 -0
- data/lib/are_we_there_yet/profiler.rb +60 -0
- data/lib/are_we_there_yet/profiler_ui.rb +15 -0
- data/lib/are_we_there_yet/recorder.rb +47 -0
- data/lib/are_we_there_yet/run.rb +12 -0
- data/spec/lib/formatter_spec.rb +33 -0
- data/spec/lib/metric_spec.rb +63 -0
- data/spec/lib/persistence/connection_spec.rb +21 -0
- data/spec/lib/persistence/schema_spec.rb +94 -0
- data/spec/lib/profiler_spec.rb +51 -0
- data/spec/lib/profiler_ui_spec.rb +50 -0
- data/spec/lib/recorder_spec.rb +78 -0
- data/spec/lib/run_spec.rb +49 -0
- data/spec/spec_helper.rb +2 -31
- data/spec/support/spec_classes.rb +22 -0
- data/spec/support/symbol.rb +6 -0
- metadata +86 -45
- data/spec/are_we_there_yet_spec.rb +0 -252
data/Gemfile
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
-
gem "
|
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.
|
10
|
+
gem "bundler", "~> 1.1.0"
|
9
11
|
gem "jeweler", "~> 1.6.4"
|
10
12
|
gem "rcov", ">= 0"
|
11
13
|
end
|
data/Gemfile.lock
CHANGED
@@ -8,7 +8,7 @@ GEM
|
|
8
8
|
git (>= 1.2.5)
|
9
9
|
rake
|
10
10
|
rake (0.9.2.2)
|
11
|
-
rcov (0.
|
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.
|
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
|
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
|
-
|
10
|
-
|
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
|
-
|
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
|
18
|
-
`spec -fAreWeThereYet
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
1
|
+
1.0.0
|
data/are_we_there_yet.gemspec
CHANGED
@@ -4,14 +4,15 @@
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.
|
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 =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
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
|
-
"
|
31
|
-
"
|
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 =
|
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 =
|
37
|
-
s.summary =
|
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.
|
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.
|
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.
|
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])
|
data/lib/are_we_there_yet.rb
CHANGED
@@ -1,108 +1,4 @@
|
|
1
1
|
require "spec/runner/formatter/base_formatter" unless defined? AWTY_SPEC_RUN
|
2
|
-
require '
|
2
|
+
require 'sequel'
|
3
3
|
|
4
|
-
|
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,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
|