scenariotest 0.1.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/LICENSE +20 -0
- data/README.markdown +97 -0
- data/Rakefile +142 -0
- data/lib/scenariotest/driver.rb +207 -0
- data/lib/scenariotest/log_subscriber.rb +24 -0
- data/lib/scenariotest.rb +170 -0
- data/scenariotest.gemspec +41 -0
- metadata +53 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 The Plant Co.,LTD
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
Why
|
2
|
+
=====
|
3
|
+
|
4
|
+
I think Test Case are made up with these:
|
5
|
+
|
6
|
+
1. Certain state (snapshot) of a whole database
|
7
|
+
1. Code to read or change the state of the the database
|
8
|
+
1. Assertions for the change of database state or generated http response
|
9
|
+
|
10
|
+
The current way of change the state of database are loading yaml files (Rails Fixture) or every time create objects by ActiveRecord or FactoryGirl. when you create a lots of records, it tends to be slow.
|
11
|
+
|
12
|
+
So why not use the fastest native `mysqldump` and `mysql` to load the snapshot of database for each test case.
|
13
|
+
|
14
|
+
|
15
|
+
How to use
|
16
|
+
==========
|
17
|
+
1. Create a ruby file like `scenarios.rb` under your test directory:
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
S.define :dalian do
|
21
|
+
S[:dalian] = City.create!(:name => "Dalian")
|
22
|
+
end
|
23
|
+
|
24
|
+
S.define :cafes, :req => :dalian do
|
25
|
+
S[:starbucks] = Store.create!(:name => "Starbucks", :city => S[:dalian])
|
26
|
+
S[:metoo] = Store.create!(:name => "Metoo", :city => S[:dalian])
|
27
|
+
end
|
28
|
+
|
29
|
+
S.define :drinks do
|
30
|
+
S[:orange_juice] = Cafe::Drink.create!(:name => "Orange Juice", :color => 1)
|
31
|
+
S[:latte] = Cafe::Drink.create!(:name => "Latte", :color => 9)
|
32
|
+
end
|
33
|
+
|
34
|
+
S.define :starbucks_with_drinks, :req => [:cafes, :drinks] do
|
35
|
+
S[:starbucks].drinks << S[:orange_juice]
|
36
|
+
S[:starbucks].drinks << S[:latte]
|
37
|
+
end
|
38
|
+
```
|
39
|
+
Which you can define groups of objects as scenario, can depend on other scenario you defined. So that you can use in your test case setup block.
|
40
|
+
|
41
|
+
|
42
|
+
1. And in your test_helper.rb:
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
ENV["RAILS_ENV"] = "test"
|
46
|
+
require File.expand_path('../../config/environment', __FILE__)
|
47
|
+
require 'rails/test_help'
|
48
|
+
|
49
|
+
S = Scenariotest::Scenario.init
|
50
|
+
require 'scenarios'
|
51
|
+
```
|
52
|
+
|
53
|
+
1. Then in your test file:
|
54
|
+
|
55
|
+
You can use the objects created for you freely
|
56
|
+
|
57
|
+
``` ruby
|
58
|
+
require 'test_helper'
|
59
|
+
|
60
|
+
class Cafe::DrinkTest < ActiveSupport::TestCase
|
61
|
+
setup do
|
62
|
+
S.setup(:cafes, :drinks)
|
63
|
+
end
|
64
|
+
|
65
|
+
test "have both cafes and drinks" do
|
66
|
+
assert S[:starbucks]
|
67
|
+
assert S[:latte]
|
68
|
+
end
|
69
|
+
|
70
|
+
test "has many drinks" do
|
71
|
+
S.setup(:starbucks_with_drinks)
|
72
|
+
assert_equal(2, S[:starbucks].drinks.size, S[:starbucks].drinks.count)
|
73
|
+
assert_equal(2, Store.count, Store.count)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
|
80
|
+
How it works
|
81
|
+
============
|
82
|
+
|
83
|
+
First time you call `S.setup(:starbucks_with_drinks)` it will do:
|
84
|
+
|
85
|
+
1. empty the whole database
|
86
|
+
1. invoke the blocks and create data from the blocks you defined and store the objects you created to `S[]`
|
87
|
+
1. dump the database data (not schema) to tmp/scenariotest_fixtures/
|
88
|
+
1. dump the loaded objects ids to tmp/scenariotest_fixtures/
|
89
|
+
|
90
|
+
Next time you run tests and call `S.setup(:starbucks_with_drinks)` it will do:
|
91
|
+
|
92
|
+
1. load the dump database data
|
93
|
+
1. load the objects into `S[]`
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
system("cd test/dummy && rake")
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Generate RCov test coverage and open in your browser"
|
54
|
+
task :coverage do
|
55
|
+
require 'rcov'
|
56
|
+
sh "rm -fr coverage"
|
57
|
+
sh "rcov test/test_*.rb"
|
58
|
+
sh "open coverage/index.html"
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
desc "Open an irb session preloaded with this library"
|
63
|
+
task :console do
|
64
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
65
|
+
end
|
66
|
+
|
67
|
+
#############################################################################
|
68
|
+
#
|
69
|
+
# Custom tasks (add your own tasks here)
|
70
|
+
#
|
71
|
+
#############################################################################
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
#############################################################################
|
76
|
+
#
|
77
|
+
# Packaging tasks
|
78
|
+
#
|
79
|
+
#############################################################################
|
80
|
+
|
81
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
82
|
+
task :release => :build do
|
83
|
+
unless `git branch` =~ /^\* master$/
|
84
|
+
puts "You must be on the master branch to release!"
|
85
|
+
exit!
|
86
|
+
end
|
87
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
88
|
+
sh "git tag v#{version}"
|
89
|
+
sh "git push origin master"
|
90
|
+
sh "git push origin v#{version}"
|
91
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "Build #{gem_file} into the pkg directory"
|
95
|
+
task :build => :gemspec do
|
96
|
+
sh "mkdir -p pkg"
|
97
|
+
sh "gem build #{gemspec_file}"
|
98
|
+
sh "mv #{gem_file} pkg"
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "Generate #{gemspec_file}"
|
102
|
+
task :gemspec => :validate do
|
103
|
+
# read spec file and split out manifest section
|
104
|
+
spec = File.read(gemspec_file)
|
105
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
106
|
+
|
107
|
+
# replace name version and date
|
108
|
+
replace_header(head, :name)
|
109
|
+
replace_header(head, :version)
|
110
|
+
replace_header(head, :date)
|
111
|
+
#comment this out if your rubyforge_project has a different name
|
112
|
+
replace_header(head, :rubyforge_project)
|
113
|
+
|
114
|
+
# determine file list from git ls-files
|
115
|
+
files = `git ls-files`.
|
116
|
+
split("\n").
|
117
|
+
sort.
|
118
|
+
reject { |file| file =~ /^\./ }.
|
119
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
120
|
+
reject { |file| file =~ /^(test)/ }.
|
121
|
+
map { |file| " #{file}" }.
|
122
|
+
join("\n")
|
123
|
+
|
124
|
+
# piece file back together and write
|
125
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
126
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
127
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
128
|
+
puts "Updated #{gemspec_file}"
|
129
|
+
end
|
130
|
+
|
131
|
+
desc "Validate #{gemspec_file}"
|
132
|
+
task :validate do
|
133
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
134
|
+
unless libfiles.empty?
|
135
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
136
|
+
exit!
|
137
|
+
end
|
138
|
+
unless Dir['VERSION*'].empty?
|
139
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
140
|
+
exit!
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require "active_record"
|
2
|
+
module Scenariotest
|
3
|
+
class Driver
|
4
|
+
|
5
|
+
def self.start(app)
|
6
|
+
new(app).start
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_cmd(*commands)
|
14
|
+
dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
|
15
|
+
commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
16
|
+
|
17
|
+
full_path_command = nil
|
18
|
+
found = commands.detect do |cmd|
|
19
|
+
dir = dirs_on_path.detect do |path|
|
20
|
+
full_path_command = File.join(path, cmd)
|
21
|
+
File.executable? full_path_command
|
22
|
+
end
|
23
|
+
end
|
24
|
+
found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
|
25
|
+
end
|
26
|
+
|
27
|
+
def config
|
28
|
+
@config ||= begin
|
29
|
+
database_config = @app.config.database_configuration
|
30
|
+
unless c = database_config[Rails.env]
|
31
|
+
abort "No database is configured for the environment '#{Rails.env}'"
|
32
|
+
end
|
33
|
+
c
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run(type, file, options={})
|
38
|
+
cmd = case config["adapter"]
|
39
|
+
when /^mysql/
|
40
|
+
args = {
|
41
|
+
'host' => '--host',
|
42
|
+
'port' => '--port',
|
43
|
+
'socket' => '--socket',
|
44
|
+
'username' => '--user',
|
45
|
+
'encoding' => '--default-character-set',
|
46
|
+
'password' => '--password'
|
47
|
+
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
|
48
|
+
|
49
|
+
args << (options.map do |name, value|
|
50
|
+
if value.blank?
|
51
|
+
" #{name}"
|
52
|
+
else
|
53
|
+
" #{name}=#{value}"
|
54
|
+
end
|
55
|
+
end).join("")
|
56
|
+
|
57
|
+
args << config['database']
|
58
|
+
|
59
|
+
commands, direct = if type == 'dump'
|
60
|
+
[['mysqldump', 'mysqldump5'], ">>"]
|
61
|
+
else
|
62
|
+
[['mysql', 'mysql5'], "<"]
|
63
|
+
end
|
64
|
+
[find_cmd(*commands), args.join(" "), direct, file].join(" ")
|
65
|
+
|
66
|
+
when "postgresql", "postgres"
|
67
|
+
ENV['PGUSER'] = config["username"] if config["username"]
|
68
|
+
ENV['PGHOST'] = config["host"] if config["host"]
|
69
|
+
ENV['PGPORT'] = config["port"].to_s if config["port"]
|
70
|
+
ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
|
71
|
+
|
72
|
+
commands, direct = if type == 'dump'
|
73
|
+
[['psqldump'], ">>"]
|
74
|
+
else
|
75
|
+
[['psql'], "<"]
|
76
|
+
end
|
77
|
+
|
78
|
+
[find_cmd(*commands), config["database"], direct, file].join(" ")
|
79
|
+
end
|
80
|
+
|
81
|
+
Rails.logger.debug(" Scenariotest: #{cmd}")
|
82
|
+
raise "failed running #{cmd}" unless system(cmd)
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def dump_file(name, source_sha1, type = "sql")
|
87
|
+
f = "#{@app.root}/tmp/scenariotest_fixtures/#{name}_#{source_sha1}.#{type}"
|
88
|
+
FileUtils.mkdir_p(File.dirname(f)) unless File.exist?(File.dirname(f))
|
89
|
+
f
|
90
|
+
end
|
91
|
+
|
92
|
+
class << self
|
93
|
+
def instance
|
94
|
+
@instance ||= new(Rails.application)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def setup(senario_klass, method_names)
|
99
|
+
definations = senario_klass.definations
|
100
|
+
|
101
|
+
method_name = if method_names.length > 1
|
102
|
+
sha1s = method_names.map{|name| definations[name].nil? ? senario_klass.not_defined!(name) : definations[name].sha1}
|
103
|
+
collection_sha1 = if sha1s.uniq.size == 1
|
104
|
+
sha1s[0]
|
105
|
+
else
|
106
|
+
Digest::SHA1.hexdigest(sha1s.join("\n"))
|
107
|
+
end
|
108
|
+
m = "__#{method_names.join("_")}".to_sym
|
109
|
+
senario_klass.define(m, :req => method_names, :source_sha1 => collection_sha1) {} unless definations[m]
|
110
|
+
m
|
111
|
+
else
|
112
|
+
method_names[0]
|
113
|
+
end
|
114
|
+
|
115
|
+
ActiveSupport::Notifications.instrument('setup.scenariotest', :name => "%s" % [method_name]) do
|
116
|
+
Rails.logger.info("\n\n* Scenariotest Setup Started: #{method_name}")
|
117
|
+
senario_klass.invoke(method_name)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def empty_data(source_sha1)
|
123
|
+
f = dump_file("empty_data", source_sha1, "sql")
|
124
|
+
unless File.exist?(f)
|
125
|
+
truncate_sql = ActiveRecord::Base.connection.tables.map{|t| "TRUNCATE `#{t}`;"}.join("\n")
|
126
|
+
File.open(f, "w"){|fh| fh.write("-- clear data\n" << truncate_sql)}
|
127
|
+
# run("dump", f, "--no-data" => nil, "--ignore-table"=>"#{config['database']}.schema_migration")
|
128
|
+
end
|
129
|
+
run("load", f)
|
130
|
+
end
|
131
|
+
|
132
|
+
def can_load?(name, source_sha1)
|
133
|
+
f = dump_file(name, source_sha1, "sql")
|
134
|
+
return false unless File.exist?(f)
|
135
|
+
yml_file = dump_file(name, source_sha1, "objects.yml")
|
136
|
+
return false unless File.exist?(yml_file)
|
137
|
+
true
|
138
|
+
end
|
139
|
+
|
140
|
+
def load(name, source_sha1)
|
141
|
+
f = dump_file(name, source_sha1, "sql")
|
142
|
+
return false unless File.exist?(f)
|
143
|
+
yml_file = dump_file(name, source_sha1, "objects.yml")
|
144
|
+
return false unless File.exist?(yml_file)
|
145
|
+
|
146
|
+
ActiveSupport::Notifications.instrument('load.scenariotest', :name => "%s (%s)" % [name, source_sha1]) do
|
147
|
+
|
148
|
+
run("load", f)
|
149
|
+
|
150
|
+
objs = YAML.load_file(yml_file)
|
151
|
+
|
152
|
+
klass_ids_map = {}
|
153
|
+
objs.each do |name, obj|
|
154
|
+
(if !obj[0].is_a?(Array)
|
155
|
+
[obj]
|
156
|
+
else
|
157
|
+
obj
|
158
|
+
end).each{|klass, id| (klass_ids_map[klass] ||= []) << id}
|
159
|
+
end
|
160
|
+
|
161
|
+
objs_identity_map = {}
|
162
|
+
klass_ids_map.each do |klass, ids|
|
163
|
+
klass.constantize.find(ids).each do |obj|
|
164
|
+
objs_identity_map["#{klass}_#{obj.id}"] = obj
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
objs.each do |name, obj|
|
170
|
+
loaded_obj = if obj[0].is_a?(Array)
|
171
|
+
obj.map{|klass, id| objs_identity_map["#{klass}_#{id}"]}
|
172
|
+
else
|
173
|
+
objs_identity_map["#{obj[0]}_#{obj[1]}"]
|
174
|
+
end
|
175
|
+
Scenariotest::Scenario[name] = loaded_obj
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
true
|
180
|
+
end
|
181
|
+
|
182
|
+
def dump(name, source_sha1, changed_data, truncate = true)
|
183
|
+
ActiveSupport::Notifications.instrument('dump.scenariotest', :name => "%s (%s)" % [name, source_sha1]) do
|
184
|
+
|
185
|
+
File.open(dump_file(name, source_sha1, "objects.yml"), 'wb') do |f|
|
186
|
+
YAML.dump(changed_data, f)
|
187
|
+
end
|
188
|
+
|
189
|
+
if truncate
|
190
|
+
File.open(dump_file(name, source_sha1, "sql"), 'wb') do |f|
|
191
|
+
truncate_sql = ActiveRecord::Base.connection.tables.map{|t| "TRUNCATE `#{t}`;"}.join("\n")
|
192
|
+
f.write("-- clear data start\n" << truncate_sql << "\n-- clear data end\n\n")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
run("dump", dump_file(name, source_sha1, "sql"),
|
197
|
+
"--no-create-info" => nil,
|
198
|
+
"--skip-add-locks" => nil,
|
199
|
+
"--skip-triggers" => nil)
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Scenariotest
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def load(event)
|
4
|
+
name = event.payload[:name]
|
5
|
+
info " " << color("Scenariotest Loaded (%.1fms) %s" % [event.duration, name], GREEN, false).to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
def dump(event)
|
9
|
+
name = event.payload[:name]
|
10
|
+
info " " << color("Scenariotest Dumped (%.1fms) %s" % [event.duration, name], YELLOW, false).to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup(event)
|
14
|
+
name = event.payload[:name]
|
15
|
+
info " " << color("Scenariotest Setup Finished (%.1fms) %s" % [event.duration, name], MAGENTA, true).to_s << "\n\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
def logger
|
19
|
+
ActiveRecord::Base.logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Scenariotest::LogSubscriber.attach_to :scenariotest
|
data/lib/scenariotest.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'scenariotest/driver'
|
2
|
+
require 'scenariotest/log_subscriber'
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Scenariotest
|
6
|
+
VERSION = "0.1.0"
|
7
|
+
class Scenario
|
8
|
+
class Defination
|
9
|
+
attr_accessor :name, :sha1, :options, :main_blk, :after_blk, :scenario_klass
|
10
|
+
|
11
|
+
def initialize(scenario_klass, name, options, blk)
|
12
|
+
self.scenario_klass = scenario_klass
|
13
|
+
self.sha1 = options[:source_sha1]
|
14
|
+
self.sha1 ||= begin
|
15
|
+
source_path = blk.source_location[0].dup
|
16
|
+
Digest::SHA1.hexdigest(source_path << File.read(source_path))
|
17
|
+
end
|
18
|
+
self.name = name
|
19
|
+
self.options = options
|
20
|
+
self.main_blk = blk
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def root?
|
25
|
+
self.options[:req].blank?
|
26
|
+
end
|
27
|
+
|
28
|
+
def root_cache_name
|
29
|
+
"___root_#{self.name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def call
|
33
|
+
loaded = scenario_klass.driver.load(self.name, sha1)
|
34
|
+
|
35
|
+
deps = uniq_dependencies
|
36
|
+
|
37
|
+
unless loaded
|
38
|
+
|
39
|
+
# dump all the reuseable roots
|
40
|
+
roots = deps.select{|d| d.root? }
|
41
|
+
roots.each do |dep|
|
42
|
+
next if scenario_klass.driver.can_load?(dep.root_cache_name, dep.sha1)
|
43
|
+
scenario_klass.driver.empty_data(sha1)
|
44
|
+
dep_changed_data = scenario_klass.changed do
|
45
|
+
dep.main_blk.call
|
46
|
+
end
|
47
|
+
scenario_klass.driver.dump(dep.root_cache_name, dep.sha1, dep_changed_data, false)
|
48
|
+
end
|
49
|
+
|
50
|
+
scenario_klass.driver.empty_data(sha1)
|
51
|
+
changed_data = scenario_klass.changed do
|
52
|
+
deps.each do |dep|
|
53
|
+
if dep.root? && scenario_klass.driver.load(dep.root_cache_name, dep.sha1) # only no dependencies scenarios can be cached
|
54
|
+
# loaded from dump
|
55
|
+
else
|
56
|
+
dep.main_blk.call
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
self.main_blk.call
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
scenario_klass.driver.dump(name, sha1, changed_data)
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
deps.each {|dep| dep.after_blk.call if dep.after_blk }
|
69
|
+
self.after_blk.call if self.after_blk
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def uniq_dependencies(list = [])
|
74
|
+
[self.options[:req]].flatten.compact.reverse.each do |name|
|
75
|
+
dep = scenario_klass.definations[name]
|
76
|
+
scenario_klass.not_defined!(name) if dep.nil?
|
77
|
+
dep.uniq_dependencies(list)
|
78
|
+
list << dep unless list.include?(dep)
|
79
|
+
end
|
80
|
+
list
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class << self
|
86
|
+
def init
|
87
|
+
log("Fixtures are disabled when using Scenariotest.")
|
88
|
+
ActiveRecord::TestFixtures.class_eval <<-EOF
|
89
|
+
def setup_fixtures
|
90
|
+
end
|
91
|
+
def teardown_fixtures
|
92
|
+
end
|
93
|
+
EOF
|
94
|
+
@data = {}
|
95
|
+
@changed_data_stack = [{}]
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def data
|
100
|
+
@data
|
101
|
+
end
|
102
|
+
|
103
|
+
def driver
|
104
|
+
@driver ||= Scenariotest::Driver.instance
|
105
|
+
end
|
106
|
+
|
107
|
+
def log(message)
|
108
|
+
Rails.logger.info(message) if const_defined?(:Rails)
|
109
|
+
end
|
110
|
+
|
111
|
+
def []=(name, value)
|
112
|
+
return if value.nil?
|
113
|
+
changed_data_value = if value.is_a?(Array)
|
114
|
+
value.map{|v| [v.class.name, v.id]}
|
115
|
+
else
|
116
|
+
[value.class.name, value.id]
|
117
|
+
end
|
118
|
+
|
119
|
+
@changed_data_stack[-1][name] = changed_data_value
|
120
|
+
@data[name] = value
|
121
|
+
end
|
122
|
+
|
123
|
+
def changed(&blk)
|
124
|
+
@changed_data_stack << {}
|
125
|
+
blk.call
|
126
|
+
changed = @changed_data_stack.pop
|
127
|
+
# if @changed_data_stack[-1]
|
128
|
+
# @changed_data_stack[-1].update(changed)
|
129
|
+
# end
|
130
|
+
changed
|
131
|
+
end
|
132
|
+
|
133
|
+
def [](name)
|
134
|
+
@data[name]
|
135
|
+
end
|
136
|
+
|
137
|
+
def reload
|
138
|
+
load __FILE__
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def define name, options = {}, &blk
|
143
|
+
definations[name] = Defination.new(self, name, options, blk)
|
144
|
+
end
|
145
|
+
|
146
|
+
def after name, &blk
|
147
|
+
not_defined!(name) unless definations[name]
|
148
|
+
definations[name].after_blk = blk
|
149
|
+
end
|
150
|
+
|
151
|
+
def setup(*names)
|
152
|
+
self.driver.setup(self, names)
|
153
|
+
end
|
154
|
+
|
155
|
+
def invoke(name)
|
156
|
+
(load_or_dump = definations[name]) ? load_or_dump.call : not_defined!(name)
|
157
|
+
end
|
158
|
+
|
159
|
+
def definations
|
160
|
+
(@definations ||= {})
|
161
|
+
end
|
162
|
+
|
163
|
+
def not_defined!(name)
|
164
|
+
raise("`#{name.inspect}' not defined.")
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.rubygems_version = '1.3.5'
|
7
|
+
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
|
10
|
+
s.name = "scenariotest"
|
11
|
+
s.version = "0.1.0"
|
12
|
+
s.date = "2012-02-07"
|
13
|
+
|
14
|
+
s.authors = ["Felix Sun"]
|
15
|
+
s.email = %q{felix@theplant.jp}
|
16
|
+
|
17
|
+
s.homepage = %q{http://github.com/theplant/senariotest}
|
18
|
+
s.summary = %q{Senario Test}
|
19
|
+
s.description = %q{Senario Test}
|
20
|
+
|
21
|
+
s.files = Dir["{app,lib,public,config}/**/*"] + %w{LICENSE README.markdown}
|
22
|
+
s.require_path = "lib"
|
23
|
+
|
24
|
+
s.required_ruby_version = ">= 1.8.7"
|
25
|
+
|
26
|
+
s.extra_rdoc_files = ["README.markdown"]
|
27
|
+
|
28
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
29
|
+
# = MANIFEST =
|
30
|
+
s.files = %w[
|
31
|
+
LICENSE
|
32
|
+
README.markdown
|
33
|
+
Rakefile
|
34
|
+
lib/scenariotest.rb
|
35
|
+
lib/scenariotest/driver.rb
|
36
|
+
lib/scenariotest/log_subscriber.rb
|
37
|
+
scenariotest.gemspec
|
38
|
+
]
|
39
|
+
# = MANIFEST =
|
40
|
+
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scenariotest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Felix Sun
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-07 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Senario Test
|
15
|
+
email: felix@theplant.jp
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- README.markdown
|
20
|
+
files:
|
21
|
+
- LICENSE
|
22
|
+
- README.markdown
|
23
|
+
- Rakefile
|
24
|
+
- lib/scenariotest.rb
|
25
|
+
- lib/scenariotest/driver.rb
|
26
|
+
- lib/scenariotest/log_subscriber.rb
|
27
|
+
- scenariotest.gemspec
|
28
|
+
homepage: http://github.com/theplant/senariotest
|
29
|
+
licenses: []
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.8.7
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 1.8.7
|
49
|
+
signing_key:
|
50
|
+
specification_version: 2
|
51
|
+
summary: Senario Test
|
52
|
+
test_files: []
|
53
|
+
has_rdoc:
|