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 +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
|