state_flow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,275 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+ describe "Page :created => :editable" do
5
+ before(:each) do
6
+ StateFlow::Log.delete_all
7
+ Page.delete_all
8
+ end
9
+
10
+ def log(msg)
11
+ ActiveRecord::Base.logger.debug(msg)
12
+ end
13
+
14
+ def next_step
15
+ @current = @schedules.shift
16
+ @executed << @current if @current
17
+ log "next must be #{@current.inspect}"
18
+ end
19
+
20
+ def wait_on(step)
21
+ log "#{Thread.current[:name]} wait_on #{step.inspect}" << (@current == step ? '' : " but #{@current.inspect} now")
22
+ while @current != step; end
23
+ log "#{Thread.current[:name]} met #{step.inspect}"
24
+ next_step
25
+ end
26
+
27
+ def execute_parallel(schedules, options = nil)
28
+ Thread.current[:name] = 'main'
29
+ log "-" * 100
30
+ options = {
31
+ :with_inner_transaction => false,
32
+ :with_outer_transaction => false
33
+ }.update(options || {})
34
+ log options.inspect
35
+ @schedules = schedules.dup
36
+ @executed = []
37
+ p1 = Page.create(:name => "top page")
38
+ @t1, @t2 = %w(t1 t2).map do |name|
39
+ Thread.new do
40
+ # Thread.current.abort_on_exception = true
41
+ Thread.current[:name] = name
42
+ Thread.current[:connection_id] = ActiveRecord::Base.connection.object_id
43
+
44
+ log("connection_id => #{Thread.current[:connection_id]}")
45
+
46
+ wait_on(:"#{name}_before_process_state")
47
+ begin
48
+ Page.process_state(:status_cd, :created,
49
+ :transactional => options[:with_inner_transaction]) do |action|
50
+ wait_on :"#{name}_before_action_proceed"
51
+ action.record.name = "updated by #{name}"
52
+ begin
53
+ action.proceed
54
+ wait_on :"#{name}_after_action_proceed"
55
+ rescue Exception
56
+ wait_on :"#{name}_error_in_process"
57
+ end
58
+ end
59
+ rescue Exception
60
+ log $!
61
+ log $!.backtrace.join("\n ")
62
+ wait_on :"#{name}_error"
63
+ # raise
64
+ end
65
+ wait_on :"#{name}_after_process_state"
66
+ end
67
+ end
68
+ next_step
69
+
70
+ Page.transaction_if_need(options[:with_outer_transaction]) do
71
+ @t1.join(5)
72
+ @t2.join(5)
73
+ end
74
+
75
+ @t1[:connection_id].should_not == @t2[:connection_id]
76
+ end
77
+
78
+ FLOWS = {
79
+ :no_lock => [
80
+ # ロックが発生しないパターン
81
+ :t1_before_process_state,
82
+ :t1_before_action_proceed,
83
+ :t1_after_action_proceed,
84
+ :t2_before_process_state,
85
+ :t2_after_process_state,
86
+ :t1_after_process_state,
87
+ ].freeze,
88
+ :t2_timeout => [
89
+ # t1がコミットするまでt2のprocess_state内のselectは動かないので、
90
+ # 以下のパターンはタイムアウトするまで時間が掛かります。
91
+ :t1_before_process_state,
92
+ :t1_before_action_proceed,
93
+ :t2_before_process_state,
94
+ :t1_after_action_proceed,
95
+ :t2_error,
96
+ :t1_after_process_state,
97
+ :t2_after_process_state,
98
+ ]
99
+ }
100
+
101
+ # t1がコミットするまでt2のprocess_state内のselectは動かないので、
102
+ # t2がトランザクションを開始した後、findを実行した後に、:t2_errorを
103
+ # 待たずにt1をコミットさせ・・・たかったんだけど、どうも1プロセスから
104
+ # 複数のスレッドでロックの発生する処理を行おうとするとどうにもうまくいかない。
105
+ # なので、このテストからは省きます。残念。
106
+ # LOCKED_FLOW_1 = [
107
+ # :t1_before_process_state,
108
+ # :t1_before_action_proceed,
109
+ # :t2_before_process_state,
110
+ # :t1_after_action_proceed,
111
+ # :t1_after_process_state,
112
+ # :t2_after_process_state,
113
+ # ].freeze
114
+
115
+ # t1がコミットするまでt2のprocess_state内のselectは動かないので、
116
+ # :t1_after_action_proceed が :t2_after_process_state よりも後にある
117
+ # 以下のパターンは矛盾しています。なのでテスト対象外です。
118
+ # CONFLICT_FLOW_1 = [
119
+ # :t1_before_process_state,
120
+ # :t1_before_action_proceed,
121
+ # :t2_before_process_state,
122
+ # :t2_after_process_state,
123
+ # :t1_after_action_proceed,
124
+ # :t1_after_process_state,
125
+ # ].freeze
126
+
127
+ case ENV['DB'] || 'sqlite3'
128
+
129
+ when 'mysql' then
130
+ [:no_lock, :t2_timeout].each do |flow_name|
131
+ flow = FLOWS[flow_name]
132
+
133
+ describe flow_name.to_s do
134
+ [false, true].each do |outer_transactional|
135
+ describe ":with_outer_transaction => #{outer_transactional}" do
136
+
137
+ it ":with_inner_transaction => false" do
138
+ execute_parallel(flow,
139
+ :with_inner_transaction => false,
140
+ :with_outer_transaction => outer_transactional)
141
+ Page.first.name.should == 'updated by t1'
142
+ @executed.should == flow
143
+ end
144
+
145
+ it ":with_inner_transaction :all " do
146
+ execute_parallel(flow,
147
+ :with_inner_transaction => :all,
148
+ :with_outer_transaction => outer_transactional)
149
+ if (flow_name == :t2_timeout) && !outer_transactional
150
+ Page.first.name.should == 'top page'
151
+ @executed.should == [
152
+ :t1_before_process_state, :t1_before_action_proceed, :t2_before_process_state, :t1_after_action_proceed, :t2_error]
153
+ else
154
+ Page.first.name.should == 'updated by t1'
155
+ @executed.should == flow
156
+ end
157
+ end
158
+
159
+ it ":with_inner_transaction :each " do
160
+ execute_parallel(flow,
161
+ :with_inner_transaction => :each,
162
+ :with_outer_transaction => outer_transactional)
163
+ Page.first.name.should == 'updated by t1'
164
+ @executed.should == flow
165
+ end
166
+
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ when 'postgresql' then
173
+ [:no_lock, :t2_timeout].each do |flow_name|
174
+ flow = FLOWS[flow_name]
175
+
176
+ describe flow_name.to_s do
177
+ [false, true].each do |outer_transactional|
178
+ describe ":with_outer_transaction => #{outer_transactional}" do
179
+
180
+ expected_flow = [:t1_before_process_state, :t1_before_action_proceed, :t2_before_process_state, :t1_after_action_proceed, :t2_error]
181
+
182
+ it ":with_inner_transaction => false" do
183
+ execute_parallel(flow,
184
+ :with_inner_transaction => false,
185
+ :with_outer_transaction => outer_transactional)
186
+ Page.first.name.should == 'updated by t1'
187
+ if flow_name == :t2_timeout
188
+ @executed.should == expected_flow
189
+ else
190
+ @executed.should == flow
191
+ end
192
+ end
193
+
194
+ # it ":with_inner_transaction :all " do
195
+ # execute_parallel(flow,
196
+ # :with_inner_transaction => :all,
197
+ # :with_outer_transaction => outer_transactional)
198
+ # if (flow_name == :t2_timeout) && !outer_transactional
199
+ # Page.first.name.should == 'top page'
200
+ # @executed.should == [
201
+ # :t1_before_process_state, :t1_before_action_proceed, :t2_before_process_state, :t1_after_action_proceed, :t2_error]
202
+ # else
203
+ # Page.first.name.should == 'updated by t1'
204
+ # @executed.should == flow
205
+ # end
206
+ # end
207
+
208
+ it ":with_inner_transaction :each " do
209
+ execute_parallel(flow,
210
+ :with_inner_transaction => :each,
211
+ :with_outer_transaction => outer_transactional)
212
+ Page.first.name.should == 'updated by t1'
213
+ if flow_name == :t2_timeout
214
+ @executed.should == expected_flow
215
+ else
216
+ @executed.should == flow
217
+ end
218
+ end
219
+
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ when 'sqlite3' then
226
+ # [:no_lock, :t2_timeout].each do |flow_name|
227
+ [:no_lock].each do |flow_name|
228
+ flow = FLOWS[flow_name]
229
+
230
+ describe flow_name.to_s do
231
+ [false, true].each do |outer_transactional|
232
+ describe ":with_outer_transaction => #{outer_transactional}" do
233
+
234
+ it ":with_inner_transaction => false" do
235
+ execute_parallel(flow,
236
+ :with_inner_transaction => false,
237
+ :with_outer_transaction => outer_transactional)
238
+ Page.first.name.should == 'updated by t1'
239
+ if (flow_name == :t2_timeout) && outer_transactional
240
+ @executed.should == [
241
+ :t1_before_process_state, :t1_before_action_proceed, :t2_before_process_state, :t1_after_action_proceed, :t2_error]
242
+ else
243
+ @executed.should == flow
244
+ end
245
+ end
246
+
247
+ it ":with_inner_transaction :all " do
248
+ execute_parallel(flow,
249
+ :with_inner_transaction => :all,
250
+ :with_outer_transaction => outer_transactional)
251
+ if (flow_name == :t2_timeout) && outer_transactional
252
+ Page.first.name.should == 'top page'
253
+ @executed.should == [
254
+ :t1_before_process_state, :t1_before_action_proceed, :t2_before_process_state, :t1_after_action_proceed, :t2_error]
255
+ else
256
+ Page.first.name.should == 'updated by t1'
257
+ @executed.should == flow
258
+ end
259
+ end
260
+
261
+ it ":with_inner_transaction :each " do
262
+ execute_parallel(flow,
263
+ :with_inner_transaction => :each,
264
+ :with_outer_transaction => outer_transactional)
265
+ Page.first.name.should == 'updated by t1'
266
+ @executed.should == flow
267
+ end
268
+
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ end
275
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,25 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: state_flow_plugin_test.sqlite3
4
+ timeout: 5000
5
+
6
+ mysql:
7
+ adapter: mysql
8
+ database: state_flow_test
9
+ encoding: utf8
10
+ username: root
11
+ password:
12
+ socket: /opt/local/var/run/mysql5/mysqld.sock
13
+ # connect_timeout: 5000
14
+ read_timeout: 3000
15
+ write_timeout: 5000
16
+
17
+ postgresql:
18
+ adapter: postgresql
19
+ database: state_flow_test
20
+ encoding: unicode
21
+ host: localhost
22
+ username: postgres
23
+ password:
24
+ allow_concurrency: true
25
+ pool: 30