blueprints 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Lachie Cox
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.rdoc ADDED
@@ -0,0 +1,150 @@
1
+ = Blueprints
2
+
3
+ Another replacement for factories and fixtures that focuses on being DRY and making developers type as little as possible.
4
+
5
+ == Usage
6
+
7
+ Plans look like:
8
+
9
+ plan :apple do
10
+ Fruit.create! :species => 'apple'
11
+ end
12
+
13
+ plan :orange do
14
+ Fruit.create! :species => 'orange'
15
+ end
16
+
17
+ plan :fruitbowl => [:apple, :orange] do
18
+ FruitBowl.create! :fruit => [@apple,@orange]
19
+ end
20
+
21
+ plan :kitchen => :fruitbowl do
22
+ Kitchen.create! :fruitbowl => @fruitbowl
23
+ end
24
+
25
+ ...and you use them in specs like:
26
+
27
+ describe Fruit, "@apple" do
28
+ before do
29
+ build :apple
30
+ end
31
+
32
+ it "should be an apple" do
33
+ @apple.species.should == 'apple'
34
+ end
35
+ end
36
+
37
+ describe FruitBowl, "with and apple and an orange" do
38
+ before do
39
+ build :fruitbowl
40
+ end
41
+
42
+ it "should have 2 fruits" do
43
+ @fruitbowl.should have(2).fruit
44
+ end
45
+ end
46
+
47
+ Result of 'plan' block is evaluated and assigned to instance variable with the same name. You can also assign your own instance variables
48
+ inside 'plan' block and they will be accessible in tests that build this plan.
49
+
50
+ All scenarios are run only once, no matter how many times they were called, meaning that you don't need to worry about
51
+ duplicating data.
52
+
53
+ There's also a possibility to delete preloaded data with demolish. When called without arguments it will drop all data.
54
+ You can also pass it table names, that will be cleared of any data. Beware that any scenarios already executed will still be
55
+ marked as executed, so you won't be able to execute them again. If you want to execute those scenarios later in test, you
56
+ can pass :undo option with list of scenarios to mark as not executed or :all if you want to mark that no scenario has been executed.
57
+
58
+ demolish :fruits, :trees # Deletes trees and fruits tables
59
+ demolish # Deletes all data except tables that are defined by Hornsby.skip_tables
60
+ demolish :fruits, :undo => :apples # Deletes fruits table and marks :apples scenario as not executed
61
+ demolish :undo => :all # Deletes all tables and marks all scenario as not executed (fresh start)
62
+
63
+ Blueprints searches for scenario files in this particular order in Rails (Merb) root:
64
+ * blueprints.rb
65
+ * blueprints/*.rb
66
+ * blueprint.rb
67
+ * blueprint/*.rb
68
+ * spec/blueprints.rb
69
+ * spec/blueprints/*.rb
70
+ * spec/blueprint.rb
71
+ * spec/blueprint/*.rb
72
+ * test/blueprints.rb
73
+ * test/blueprints/*.rb
74
+ * test/blueprint.rb
75
+ * test/blueprint/*.rb
76
+ You can pass :root option to override framework root and :filename option to pass custom filename pattern
77
+
78
+ == Setup
79
+
80
+ The easiest way to install this gem for Ruby on Rails is just add this line to config/environment.rb (or config/environments/test.rb):
81
+
82
+ config.gem 'sinsiliux-blueprints', :lib => 'blueprints', :source => 'http://gems.github.com'
83
+
84
+ If you’re not using rails, then you can install it through command line
85
+
86
+ gem sources -a http://gems.github.com
87
+ sudo gem install sinsiliux-blueprints
88
+
89
+ Lastly you could use it as plugin:
90
+
91
+ ruby script/plugin install git://github.com/sinsiliux/blueprints.git
92
+
93
+ Hornsby scenarios is activated by calling enable_blueprints. For specifics on how to call that in your testing framework see a little lower.
94
+ enable_blueprintssupports these parameters:
95
+ * root - custom framework root if automatic detection fails for some reason (eg. not rails/merb project)
96
+ * filename - custom files pattern with blueprints plans
97
+ * prebuild - list of blueprints plans that should be preloaded (available in all tests, never reloaded so they're much faster)
98
+
99
+ === RSpec
100
+
101
+ Add the following to spec_helper.rb
102
+
103
+ Spec::Runner.configure do |config|
104
+ ...
105
+
106
+ config.enable_blueprints :filename => 'scenarios.rb', :prebuild => :preloaded_scenario
107
+ end
108
+
109
+ === Test::Unit
110
+
111
+ Add the following lines to test_helper.rb
112
+
113
+ class ActiveSupport::TestCase
114
+ ...
115
+
116
+ enable_blueprints
117
+ end
118
+
119
+ == Advanced Usage
120
+
121
+ Its just ruby, right? So go nuts:
122
+
123
+ 1.upto(9) do |i|
124
+ plan("user_#{i}") do
125
+ user = User.create! :name => "user#{i}"
126
+ instance_variable_set("@user_#{i}",user)
127
+ end
128
+ end
129
+
130
+ == Transactions
131
+
132
+ Blueprints is transactional, meaning that after every test transaction is dropped and database is reset to
133
+ starting point. Starting point is empty database + any scenarios that you specify in enable_blueprints.
134
+
135
+ == TODO
136
+
137
+ * Add plan namespaces for better organisation.
138
+ * Add ability to revert one plan.
139
+ * Add preloading plans for whole block of tests.
140
+ * Fix rake tasks
141
+
142
+ == Credits
143
+
144
+ Andrius Chamentauskas <sinsiliux@gmail.com>
145
+
146
+ The code is based on hornsby scenario plugin by Lachie Cox, which is based on Err's code found in this post: http://errtheblog.com/post/7708
147
+
148
+ == License
149
+
150
+ MIT, see LICENCE
data/lib/blueprints.rb ADDED
@@ -0,0 +1,76 @@
1
+ require File.join(File.dirname(__FILE__), 'blueprints/plan')
2
+ require File.join(File.dirname(__FILE__), 'blueprints/file_context')
3
+ require File.join(File.dirname(__FILE__), 'blueprints/helper')
4
+ require File.join(File.dirname(__FILE__), 'blueprints/errors')
5
+ if defined? Spec
6
+ require File.join(File.dirname(__FILE__), 'blueprints/rspec_extensions')
7
+ else
8
+ require File.join(File.dirname(__FILE__), 'blueprints/test_unit_extensions')
9
+ end
10
+
11
+ module Blueprints
12
+ PLAN_FILES = [nil, "spec", "test"].map do |dir|
13
+ ["blueprint", "blueprints"].map do |file|
14
+ path = File.join([dir, file].compact)
15
+ ["#{path}.rb", File.join(path, "*.rb")]
16
+ end
17
+ end.flatten
18
+
19
+ DELETE_POLICIES = {:delete => "DELETE FROM %s", :truncate => "TRUNCATE %s"}
20
+
21
+ def self.framework_root
22
+ @@framework_root ||= RAILS_ROOT rescue Rails.root rescue Merb.root rescue nil
23
+ end
24
+
25
+ def self.setup(current_context)
26
+ Plan.setup
27
+ Plan.copy_ivars(current_context)
28
+ ActiveRecord::Base.connection.increment_open_transactions
29
+ ActiveRecord::Base.connection.transaction_joinable = false
30
+ ActiveRecord::Base.connection.begin_db_transaction
31
+ end
32
+
33
+ def self.teardown
34
+ ActiveRecord::Base.connection.rollback_db_transaction
35
+ ActiveRecord::Base.connection.decrement_open_transactions
36
+ end
37
+
38
+ def self.load(options = {})
39
+ options.assert_valid_keys(:delete_policy, :filename, :prebuild, :root)
40
+ return unless Plan.plans.empty?
41
+
42
+ @@delete_sql = DELETE_POLICIES[options[:delete_policy]] || DELETE_POLICIES[:delete]
43
+ delete_tables
44
+ @@framework_root = options[:root] if options[:root]
45
+ load_scenarios_files(options[:filename] || PLAN_FILES)
46
+
47
+ Plan.prebuild(options[:prebuild])
48
+ end
49
+
50
+ def self.load_scenarios_files(*patterns)
51
+ patterns.flatten!
52
+ patterns.collect! {|pattern| File.join(framework_root, pattern)} if framework_root
53
+
54
+ patterns.each do |pattern|
55
+ unless (files = Dir.glob(pattern)).empty?
56
+ files.each{|f| FileContext.module_eval File.read(f)}
57
+ return
58
+ end
59
+ end
60
+
61
+ raise "Plans file not found! Put plans in #{patterns.join(' or ')} or pass custom filename pattern with :filename option"
62
+ end
63
+
64
+ def self.delete_tables(*args)
65
+ args = tables if args.blank?
66
+ args.each { |t| ActiveRecord::Base.connection.delete(@@delete_sql % t) }
67
+ end
68
+
69
+ def self.tables
70
+ ActiveRecord::Base.connection.tables - skip_tables
71
+ end
72
+
73
+ def self.skip_tables
74
+ %w( schema_info schema_migrations )
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ module Blueprints
2
+ class PlanNotFoundError < NameError
3
+ def initialize(*args)
4
+ @plans = args
5
+ end
6
+
7
+ def to_s
8
+ "Plan(s) not found '#{@plans.join(',')}'"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Blueprints
2
+ module FileContext
3
+ def self.blueprint(plan, &block)
4
+ Plan.new(plan, &block)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ module Blueprints
2
+ module Helper
3
+ def build_plan(*names)
4
+ Plan.build(*names)
5
+ Plan.copy_ivars(self)
6
+ end
7
+
8
+ alias :build :build_plan
9
+
10
+ def demolish(*args)
11
+ options = args.extract_options!
12
+ Blueprints.delete_tables(*args)
13
+
14
+ if options[:undo] == :all
15
+ Plan.executed_plans.clear
16
+ else
17
+ undo = [options[:undo]].flatten.compact
18
+ unless (not_found = undo - Plan.executed_plans.to_a).blank?
19
+ raise(ArgumentError, "Scenario(s) #{not_found} not found")
20
+ end
21
+ Plan.executed_plans -= undo
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,98 @@
1
+ module Blueprints
2
+ class Plan
3
+ cattr_reader :plans
4
+ cattr_accessor :executed_plans
5
+ @@plans = {}
6
+ @@executed_plans = Set.new
7
+ @@global_executed_plans = Set.new
8
+
9
+ @@global_context = Module.new
10
+ @@context = nil
11
+
12
+ def self.setup
13
+ @@context = YAML.load(@@global_context)
14
+ @@executed_plans = @@global_executed_plans.clone
15
+ end
16
+
17
+ def self.copy_ivars(to)
18
+ @@context.instance_variables.each do |iv|
19
+ to.instance_variable_set(iv, @@context.instance_variable_get(iv))
20
+ end
21
+ end
22
+
23
+ def self.prebuild(plans)
24
+ @@context = @@global_context
25
+ @@global_scenarios = Plan.build(plans) if plans
26
+ @@global_executed_plans = @@executed_plans
27
+ @@global_context = YAML.dump(@@global_context)
28
+ end
29
+
30
+ def self.build(*names)
31
+ names.map {|name| @@plans[name.to_sym] or raise PlanNotFoundError, name}.each {|p| p.build}
32
+ end
33
+
34
+ # Instance
35
+
36
+ attr_reader :name
37
+
38
+ def initialize(scenario, &block)
39
+ @name, @parents = parse_name(scenario)
40
+ @block = block
41
+
42
+ @@plans[@name] = self
43
+ end
44
+
45
+ def build
46
+ build_parent_plans(@@context)
47
+ build_plan(@@context)
48
+ end
49
+
50
+ protected
51
+
52
+ def parse_name(name)
53
+ case name
54
+ when Hash
55
+ return name.keys.first.to_sym, [name.values.first].flatten.map{|sc| parse_name(sc).first}
56
+ when Symbol, String
57
+ return name.to_sym, []
58
+ else
59
+ raise TypeError, "Pass plan names as strings or symbols only, cannot build plan #{name.inspect}"
60
+ end
61
+ end
62
+
63
+ def say(*messages)
64
+ puts messages.map { |message| "=> #{message}" }
65
+ end
66
+
67
+ def build_plan(context)
68
+ surface_errors do
69
+ if @block
70
+ result = context.module_eval(&@block)
71
+ iv_name = :"@#{@name}"
72
+ context.instance_variable_set(iv_name, result) unless context.instance_variable_get(iv_name)
73
+ end
74
+ end unless @@executed_plans.include?(@name)
75
+ @@executed_plans << @name
76
+ end
77
+
78
+ def build_parent_plans(context)
79
+ @parents.each do |p|
80
+ parent = @@plans[p] or raise PlanNotFoundError, p
81
+
82
+ parent.build_parent_plans(context)
83
+ parent.build_plan(context)
84
+ end
85
+ end
86
+
87
+ def surface_errors
88
+ yield
89
+ rescue StandardError => error
90
+ puts
91
+ say "There was an error building scenario '#{@name}'", error.inspect
92
+ puts
93
+ puts error.backtrace
94
+ puts
95
+ raise error
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ module Spec
2
+ module Runner
3
+ class Configuration
4
+ def enable_blueprints(options = {})
5
+ Blueprints.load(options)
6
+
7
+ include(Blueprints::Helper)
8
+ before do
9
+ Blueprints.setup(self)
10
+ end
11
+ after do
12
+ Blueprints.teardown
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Test
2
+ module Unit
3
+ class TestCase
4
+ def run_with_blueprints(result, &progress_block)
5
+ Blueprints.setup(self)
6
+ run_without_blueprints(result, &progress_block)
7
+ Blueprints.teardown
8
+ end
9
+
10
+ def self.enable_blueprints(options = {})
11
+ include Blueprints::Helper
12
+ Blueprints.load(options)
13
+ alias_method_chain :run, :blueprints
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__)+'/../blueprints'
2
+
3
+ namespace :hornsby do
4
+
5
+ desc "Load the scenario named in the env var SCENARIO"
6
+ task :scenario => :environment do
7
+ raise "set SCENARIO to define which scenario to load" unless ENV['SCENARIO']
8
+ ::Hornsby.load
9
+ ::Hornsby.build(ENV['SCENARIO'])
10
+ end
11
+
12
+ end
@@ -0,0 +1,35 @@
1
+ blueprint :apple do
2
+ Fruit.create! :species => 'apple'
3
+ end
4
+
5
+ blueprint :many_apples => [:apple, :apple, :apple]
6
+
7
+ blueprint :bananas_and_apples => :apple do
8
+ @banana = Fruit.create! :species => 'banana'
9
+ end
10
+
11
+ blueprint :orange do
12
+ Fruit.create! :species => 'orange'
13
+ end
14
+
15
+ blueprint :fruit => [:apple, :orange] do
16
+ [@orange, @apple]
17
+ end
18
+
19
+ blueprint :bananas_and_apples_and_oranges => [:bananas_and_apples, :orange] do
20
+ @fruit = [@orange, @apple, @banana]
21
+ end
22
+
23
+ blueprint :cherry do
24
+ Fruit.create! :species => 'cherry', :average_diameter => 3
25
+ end
26
+
27
+ blueprint :big_cherry => :cherry do
28
+ Fruit.create! :species => @cherry.species, :average_diameter => 7
29
+ end
30
+
31
+ blueprint :cherry_basket => [:big_cherry, :cherry] do
32
+ [@cherry, @big_cherry]
33
+ end
34
+
35
+ blueprint :parent_not_existing => :not_existing
@@ -0,0 +1,216 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Blueprints do
4
+ describe "scenario files" do
5
+ it "should be loaded from specified dirs" do
6
+ Blueprints::PLAN_FILES.should == ["blueprint.rb", "blueprint/*.rb", "blueprints.rb", "blueprints/*.rb", "spec/blueprint.rb", "spec/blueprint/*.rb", "spec/blueprints.rb", "spec/blueprints/*.rb", "test/blueprint.rb", "test/blueprint/*.rb", "test/blueprints.rb", "test/blueprints/*.rb"]
7
+ end
8
+ end
9
+
10
+ describe "with apple scenario" do
11
+ before do
12
+ build :apple
13
+ end
14
+
15
+ it "should create @apple" do
16
+ @apple.should_not be_nil
17
+ end
18
+
19
+ it "should create Fruit @apple" do
20
+ @apple.should be_instance_of(Fruit)
21
+ end
22
+
23
+ it "should not create @banana" do
24
+ @banana.should be_nil
25
+ end
26
+
27
+ it "should have correct species" do
28
+ @apple.species.should == 'apple'
29
+ end
30
+ end
31
+
32
+ describe "with bananas_and_apples scenario" do
33
+ before do
34
+ build :bananas_and_apples
35
+ end
36
+
37
+ it "should have correct @apple species" do
38
+ @apple.species.should == 'apple'
39
+ end
40
+
41
+ it "should have correct @banana species" do
42
+ @banana.species.should == 'banana'
43
+ end
44
+ end
45
+
46
+ describe "with fruit scenario" do
47
+ before do
48
+ build :fruit
49
+ end
50
+
51
+ it "should have 2 fruits" do
52
+ @fruit.should have(2).items
53
+ end
54
+
55
+ it "should have an @apple" do
56
+ @apple.species.should == 'apple'
57
+ end
58
+
59
+ it "should have an @orange" do
60
+ @orange.species.should == 'orange'
61
+ end
62
+
63
+ it "should have no @banana" do
64
+ @banana.should be_nil
65
+ end
66
+ end
67
+
68
+ describe 'with preloaded cherry scenario' do
69
+ it "should have correct size after changed by second test" do
70
+ @cherry.average_diameter.should == 3
71
+ @cherry.update_attribute(:average_diameter, 1)
72
+ @cherry.average_diameter.should == 1
73
+ end
74
+
75
+ it "should have correct size" do
76
+ @cherry.average_diameter.should == 3
77
+ @cherry.update_attribute(:average_diameter, 5)
78
+ @cherry.average_diameter.should == 5
79
+ end
80
+
81
+ it "should create big cherry" do
82
+ @big_cherry.species.should == 'cherry'
83
+ end
84
+ end
85
+
86
+ describe 'demolish' do
87
+ before do
88
+ build :apple
89
+ end
90
+
91
+ it "should clear scenarios when calling demolish" do
92
+ demolish
93
+ Fruit.count.should == 0
94
+ end
95
+
96
+ it "should clear only tables passed" do
97
+ Tree.create!(:name => 'oak')
98
+ demolish :fruits
99
+ Tree.count.should == 1
100
+ Fruit.count.should == 0
101
+ end
102
+
103
+ it "should mark scenarios as undone when passed :undone option" do
104
+ build :fruit
105
+ demolish :undo => [:apple]
106
+ Fruit.count.should == 0
107
+ build :fruit
108
+ Fruit.count.should == 1
109
+ end
110
+
111
+ it "should mark all scenarios as undone when passed :undone option as :all" do
112
+ build :fruit
113
+ demolish :undo => :all
114
+ Fruit.count.should == 0
115
+ build :fruit
116
+ Fruit.count.should == 2
117
+ end
118
+
119
+ it "should raise error when not executed scenarios passed to :undo option" do
120
+ lambda {
121
+ demolish :undo => :orange
122
+ }.should raise_error(ArgumentError)
123
+ end
124
+ end
125
+
126
+ describe 'delete policies' do
127
+ before do
128
+ Blueprints::Plan.plans.should_receive(:empty?).and_return(true)
129
+ Blueprints.should_receive(:load_scenarios_files).with(Blueprints::PLAN_FILES)
130
+ Blueprints::Plan.should_receive(:prebuild).with(nil)
131
+ end
132
+
133
+ it "should allow using custom delete policy" do
134
+ ActiveRecord::Base.connection.should_receive(:delete).with("TRUNCATE fruits")
135
+ ActiveRecord::Base.connection.should_receive(:delete).with("TRUNCATE trees")
136
+
137
+ Blueprints.load(:delete_policy => :truncate)
138
+ end
139
+
140
+ it "should default to :delete policy if unexisting policy given" do
141
+ ActiveRecord::Base.connection.should_receive(:delete).with("DELETE FROM fruits")
142
+ ActiveRecord::Base.connection.should_receive(:delete).with("DELETE FROM trees")
143
+
144
+ Blueprints.load(:delete_policy => :ukndown)
145
+ end
146
+ end
147
+
148
+ describe 'with many apples scenario' do
149
+ before do
150
+ build :many_apples, :cherry, :cherry_basket
151
+ end
152
+
153
+ it "should create only one apple" do
154
+ Fruit.all(:conditions => 'species = "apple"').size.should == 1
155
+ end
156
+
157
+ it "should create only two cherries even if they were preloaded" do
158
+ Fruit.all(:conditions => 'species = "cherry"').size.should == 2
159
+ end
160
+
161
+ it "should contain cherries in basket if basket is loaded in test and cherries preloaded" do
162
+ @cherry_basket.should == [@cherry, @big_cherry]
163
+ end
164
+ end
165
+
166
+ describe 'transactions' do
167
+ before do
168
+ build :apple
169
+ end
170
+
171
+ it "should drop only inner transaction" do
172
+ @apple.reload.should_not be_nil
173
+ begin
174
+ ActiveRecord::Base.transaction do
175
+ f = Fruit.create(:species => 'orange')
176
+ f.reload.should_not be_nil
177
+ raise 'some error'
178
+ end
179
+ rescue
180
+ end
181
+ @apple.reload.should_not be_nil
182
+ Fruit.find_by_species('orange').should be_nil
183
+ end
184
+ end
185
+
186
+ describe 'errors' do
187
+ it 'should raise ScenarioNotFoundError when scenario could not be found' do
188
+ lambda {
189
+ build :not_existing
190
+ }.should raise_error(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'")
191
+ end
192
+
193
+ it 'should raise ScenarioNotFoundError when scenario parent could not be found' do
194
+ lambda {
195
+ build :parent_not_existing
196
+ }.should raise_error(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'")
197
+ end
198
+
199
+ it 'should raise TypeError when scenario name is not symbol or string' do
200
+ lambda {
201
+ Blueprints::Plan.new(1)
202
+ }.should raise_error(TypeError, "Pass plan names as strings or symbols only, cannot build plan 1")
203
+ end
204
+ end
205
+
206
+ #describe "with pitted namespace" do
207
+ # before do
208
+ # Hornsby.build('pitted:peach').copy_ivars(self)
209
+ # end
210
+
211
+ # it "should have @peach" do
212
+ # @peach.species.should == 'peach'
213
+ # end
214
+ #end
215
+ end
216
+
@@ -0,0 +1,8 @@
1
+ test:
2
+ adapter: mysql
3
+ socket: /var/run/mysqld/mysqld.sock
4
+ encoding: utf8
5
+ database: hornsby_test
6
+ username: root
7
+ password:
8
+ host: localhost
data/spec/db/fruit.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Fruit < ActiveRecord::Base
2
+ end
data/spec/db/schema.rb ADDED
@@ -0,0 +1,10 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :fruits, :force => true do |t|
3
+ t.string :species
4
+ t.integer :average_diameter
5
+ end
6
+
7
+ create_table :trees, :force => true do |t|
8
+ t.string :name
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ require 'fileutils'
2
+ require 'activerecord'
3
+ begin
4
+ require 'mysqlplus'
5
+ rescue LoadError
6
+ end
7
+
8
+ Dir.chdir File.join(File.dirname(__FILE__), '..')
9
+
10
+ ActiveRecord::Base.logger = Logger.new("debug.log")
11
+
12
+ databases = YAML::load(IO.read("spec/db/database.yml"))
13
+ db_info = databases[ENV["DB"] || "test"]
14
+ ActiveRecord::Base.establish_connection(db_info)
15
+
16
+ require 'spec/autorun'
17
+ require 'lib/blueprints'
18
+ require 'spec/db/fruit'
19
+ require 'spec/db/tree'
20
+
21
+ Spec::Runner.configure do |config|
22
+ config.enable_blueprints :root => File.expand_path(File.join(File.dirname(__FILE__), '..')), :prebuild => :big_cherry
23
+ end
@@ -0,0 +1,194 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class BlueprintsTest < ActiveSupport::TestCase
4
+ context "scenario files" do
5
+ should "be loaded from specified dirs" do
6
+ assert(Blueprints::PLAN_FILES == ["blueprint.rb", "blueprint/*.rb", "blueprints.rb", "blueprints/*.rb", "spec/blueprint.rb", "spec/blueprint/*.rb", "spec/blueprints.rb", "spec/blueprints/*.rb", "test/blueprint.rb", "test/blueprint/*.rb", "test/blueprints.rb", "test/blueprints/*.rb"] )
7
+ end
8
+ end
9
+
10
+ context "with apple scenario" do
11
+ setup do
12
+ build :apple
13
+ end
14
+
15
+ should "create @apple" do
16
+ assert(!(@apple.nil?))
17
+ end
18
+
19
+ should "create Fruit @apple" do
20
+ assert(@apple.instance_of?(Fruit))
21
+ end
22
+
23
+ should "not create @banana" do
24
+ assert(@banana.nil?)
25
+ end
26
+
27
+ should "have correct species" do
28
+ assert(@apple.species == 'apple')
29
+ end
30
+ end
31
+
32
+ context "with bananas_and_apples scenario" do
33
+ setup do
34
+ build :bananas_and_apples
35
+ end
36
+
37
+ should "have correct @apple species" do
38
+ assert(@apple.species == 'apple')
39
+ end
40
+
41
+ should "have correct @banana species" do
42
+ assert(@banana.species == 'banana')
43
+ end
44
+ end
45
+
46
+ context "with fruit scenario" do
47
+ setup do
48
+ build :fruit
49
+ end
50
+
51
+ should "have 2 fruits" do
52
+ assert(@fruit.size == 2)
53
+ end
54
+
55
+ should "have an @apple" do
56
+ assert(@apple.species == 'apple')
57
+ end
58
+
59
+ should "have an @orange" do
60
+ assert(@orange.species == 'orange')
61
+ end
62
+
63
+ should "have no @banana" do
64
+ assert(@banana.nil?)
65
+ end
66
+ end
67
+
68
+ context 'with preloaded cherry scenario' do
69
+ should "have correct size after changed by second test" do
70
+ assert(@cherry.average_diameter == 3)
71
+ @cherry.update_attribute(:average_diameter, 1)
72
+ assert(@cherry.average_diameter == 1)
73
+ end
74
+
75
+ should "have correct size" do
76
+ assert(@cherry.average_diameter == 3)
77
+ @cherry.update_attribute(:average_diameter, 5)
78
+ assert(@cherry.average_diameter == 5)
79
+ end
80
+
81
+ should "create big cherry" do
82
+ assert(@big_cherry.species == 'cherry')
83
+ end
84
+ end
85
+
86
+ context 'demolish' do
87
+ setup do
88
+ build :apple
89
+ end
90
+
91
+ should "clear scenarios when calling demolish" do
92
+ demolish
93
+ assert(Fruit.count == 0)
94
+ end
95
+
96
+ should "clear only tables passed" do
97
+ Tree.create!(:name => 'oak')
98
+ demolish :fruits
99
+ assert(Tree.count == 1)
100
+ assert(Fruit.count == 0)
101
+ end
102
+
103
+ should "mark scenarios as undone when passed :undone option" do
104
+ build :fruit
105
+ demolish :undo => [:apple]
106
+ assert(Fruit.count == 0)
107
+ build :fruit
108
+ assert(Fruit.count == 1)
109
+ end
110
+
111
+ should "mark all scenarios as undone when passed :undone option as :all" do
112
+ build :fruit
113
+ demolish :undo => :all
114
+ assert(Fruit.count == 0)
115
+ build :fruit
116
+ assert(Fruit.count == 2)
117
+ end
118
+
119
+ should "raise error when not executed scenarios passed to :undo option" do
120
+ assert_raise(ArgumentError) do
121
+ demolish :undo => :orange
122
+ end
123
+ end
124
+ end
125
+
126
+ context 'with many apples scenario' do
127
+ setup do
128
+ build :many_apples, :cherry, :cherry_basket
129
+ end
130
+
131
+ should "create only one apple" do
132
+ assert(Fruit.all(:conditions => 'species = "apple"').size == 1)
133
+ end
134
+
135
+ should "create only two cherries even if they were preloaded" do
136
+ assert(Fruit.all(:conditions => 'species = "cherry"').size == 2)
137
+ end
138
+
139
+ should "contain cherries in basket if basket is loaded in test and cherries preloaded" do
140
+ assert(@cherry_basket == [@cherry, @big_cherry])
141
+ end
142
+ end
143
+
144
+ context 'transactions' do
145
+ setup do
146
+ build :apple
147
+ end
148
+
149
+ should "drop only inner transaction" do
150
+ assert(!(@apple.reload.nil?))
151
+ begin
152
+ ActiveRecord::Base.transaction do
153
+ f = Fruit.create(:species => 'orange')
154
+ assert(!(f.reload.nil?))
155
+ raise 'some error'
156
+ end
157
+ rescue
158
+ end
159
+ assert(!(@apple.reload.nil?))
160
+ assert(Fruit.find_by_species('orange').nil?)
161
+ end
162
+ end
163
+
164
+ context 'errors' do
165
+ should 'raise ScenarioNotFoundError when scenario could not be found' do
166
+ assert_raise(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'") do
167
+ build :not_existing
168
+ end
169
+ end
170
+
171
+ should 'raise ScenarioNotFoundError when scenario parent could not be found' do
172
+ assert_raise(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'") do
173
+ build :parent_not_existing
174
+ end
175
+ end
176
+
177
+ should 'raise TypeError when scenario name is not symbol or string' do
178
+ assert_raise(TypeError, "Pass plan names as strings or symbols only, cannot build plan 1") do
179
+ Blueprints::Plan.new(1)
180
+ end
181
+ end
182
+ end
183
+
184
+ #describe "with pitted namespace" do
185
+ # before do
186
+ # Hornsby.build('pitted:peach').copy_ivars(self)
187
+ # end
188
+
189
+ # it "should have @peach" do
190
+ # @peach.species.should == 'peach'
191
+ # end
192
+ #end
193
+ end
194
+
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require 'test/unit'
4
+ require 'active_record/test_case'
5
+ require 'shoulda'
6
+ begin
7
+ require 'mysqlplus'
8
+ rescue LoadError
9
+ end
10
+
11
+ Dir.chdir(File.join(File.dirname(__FILE__), '..'))
12
+
13
+ ActiveRecord::Base.logger = Logger.new("debug.log")
14
+
15
+ databases = YAML::load(IO.read("spec/db/database.yml"))
16
+ db_info = databases[ENV["DB"] || "test"]
17
+ ActiveRecord::Base.establish_connection(db_info)
18
+
19
+ require 'lib/blueprints'
20
+ require 'spec/db/fruit'
21
+ require 'spec/db/tree'
22
+
23
+ class ActiveSupport::TestCase
24
+ enable_blueprints :root => File.join(File.dirname(__FILE__), '..'), :prebuild => :big_cherry
25
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blueprints
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrius Chamentauskas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-27 00:00:00 +03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ version:
25
+ description:
26
+ email: sinsiliux@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/blueprints.rb
35
+ - lib/blueprints/errors.rb
36
+ - lib/blueprints/file_context.rb
37
+ - lib/blueprints/helper.rb
38
+ - lib/blueprints/plan.rb
39
+ - lib/blueprints/rspec_extensions.rb
40
+ - lib/blueprints/test_unit_extensions.rb
41
+ - lib/tasks/blueprints_tasks.rake
42
+ - README.rdoc
43
+ - LICENSE
44
+ has_rdoc: true
45
+ homepage: http://github.com/sinsiliux/blueprints
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options: []
50
+
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Another replacement for factories and fixtures
72
+ test_files:
73
+ - spec/spec_helper.rb
74
+ - spec/blueprints_spec.rb
75
+ - spec/blueprints.rb
76
+ - spec/db/fruit.rb
77
+ - spec/db/database.yml.example
78
+ - spec/db/schema.rb
79
+ - test/test_helper.rb
80
+ - test/blueprints_test.rb