transaction-simple 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
1
+ require 'transaction/simple'
2
+
3
+ # A transaction group is an object wrapper that manages a group of objects
4
+ # as if they were a single object for the purpose of transaction
5
+ # management. All transactions for this group of objects should be
6
+ # performed against the transaction group object, not against individual
7
+ # objects in the group.
8
+ #
9
+ # == Transaction Group Usage
10
+ # require 'transaction/simple/group'
11
+ #
12
+ # x = "Hello, you."
13
+ # y = "And you, too."
14
+ #
15
+ # g = Transaction::Simple::Group.new(x, y)
16
+ # g.start_transaction(:first) # -> [ x, y ]
17
+ # g.transaction_open?(:first) # -> true
18
+ # x.transaction_open?(:first) # -> true
19
+ # y.transaction_open?(:first) # -> true
20
+ #
21
+ # x.gsub!(/you/, "world") # -> "Hello, world."
22
+ # y.gsub!(/you/, "me") # -> "And me, too."
23
+ #
24
+ # g.start_transaction(:second) # -> [ x, y ]
25
+ # x.gsub!(/world/, "HAL") # -> "Hello, HAL."
26
+ # y.gsub!(/me/, "Dave") # -> "And Dave, too."
27
+ # g.rewind_transaction(:second) # -> [ x, y ]
28
+ # x # -> "Hello, world."
29
+ # y # -> "And me, too."
30
+ #
31
+ # x.gsub!(/world/, "HAL") # -> "Hello, HAL."
32
+ # y.gsub!(/me/, "Dave") # -> "And Dave, too."
33
+ #
34
+ # g.commit_transaction(:second) # -> [ x, y ]
35
+ # x # -> "Hello, HAL."
36
+ # y # -> "And Dave, too."
37
+ #
38
+ # g.abort_transaction(:first) # -> [ x, y ]
39
+ # x = -> "Hello, you."
40
+ # y = -> "And you, too."
41
+ class Transaction::Simple::Group
42
+ # Creates a transaction group for the provided objects. If a block is
43
+ # provided, the transaction group object is yielded to the block; when
44
+ # the block is finished, the transaction group object will be cleared
45
+ # with #clear.
46
+ def initialize(*objects)
47
+ @objects = objects || []
48
+ @objects.freeze
49
+ @objects.each { |obj| obj.extend(Transaction::Simple) }
50
+
51
+ if block_given?
52
+ begin
53
+ yield self
54
+ ensure
55
+ self.clear
56
+ end
57
+ end
58
+ end
59
+
60
+ # Returns the objects that are covered by this transaction group.
61
+ attr_reader :objects
62
+
63
+ # Clears the object group. Removes references to the objects so that
64
+ # they can be garbage collected.
65
+ def clear
66
+ @objects = @objects.dup.clear
67
+ end
68
+
69
+ # Tests to see if all of the objects in the group have an open
70
+ # transaction. See Transaction::Simple#transaction_open? for more
71
+ # information.
72
+ def transaction_open?(name = nil)
73
+ @objects.inject(true) do |val, obj|
74
+ val = val and obj.transaction_open?(name)
75
+ end
76
+ end
77
+
78
+ # Returns the current name of the transaction for the group.
79
+ # Transactions not explicitly named are named +nil+.
80
+ def transaction_name
81
+ @objects[0].transaction_name
82
+ end
83
+
84
+ # Starts a transaction for the group. Stores the current object state.
85
+ # If a transaction name is specified, the transaction will be named.
86
+ # Transaction names must be unique. Transaction names of +nil+ will be
87
+ # treated as unnamed transactions.
88
+ def start_transaction(name = nil)
89
+ @objects.each { |obj| obj.start_transaction(name) }
90
+ end
91
+
92
+ # Rewinds the transaction. If +name+ is specified, then the intervening
93
+ # transactions will be aborted and the named transaction will be
94
+ # rewound. Otherwise, only the current transaction is rewound.
95
+ def rewind_transaction(name = nil)
96
+ @objects.each { |obj| obj.rewind_transaction(name) }
97
+ end
98
+
99
+ # Aborts the transaction. Resets the object state to what it was before
100
+ # the transaction was started and closes the transaction. If +name+ is
101
+ # specified, then the intervening transactions and the named transaction
102
+ # will be aborted. Otherwise, only the current transaction is aborted.
103
+ #
104
+ # If the current or named transaction has been started by a block
105
+ # (Transaction::Simple.start), then the execution of the block will be
106
+ # halted with +break+ +self+.
107
+ def abort_transaction(name = nil)
108
+ @objects.each { |obj| obj.abort_transaction(name) }
109
+ end
110
+
111
+ # If +name+ is +nil+ (default), the current transaction level is closed
112
+ # out and the changes are committed.
113
+ #
114
+ # If +name+ is specified and +name+ is in the list of named
115
+ # transactions, then all transactions are closed and committed until the
116
+ # named transaction is reached.
117
+ def commit_transaction(name = nil)
118
+ @objects.each { |obj| obj.commit_transaction(name) }
119
+ end
120
+
121
+ # Alternative method for calling the transaction methods. An optional
122
+ # name can be specified for named transaction support.
123
+ #
124
+ # #transaction(:start):: #start_transaction
125
+ # #transaction(:rewind):: #rewind_transaction
126
+ # #transaction(:abort):: #abort_transaction
127
+ # #transaction(:commit):: #commit_transaction
128
+ # #transaction(:name):: #transaction_name
129
+ # #transaction:: #transaction_open?
130
+ def transaction(action = nil, name = nil)
131
+ @objects.each { |obj| obj.transaction(action, name) }
132
+ end
133
+ end
@@ -0,0 +1,52 @@
1
+ require 'transaction/simple'
2
+ require 'thread'
3
+
4
+ class Transaction::TransactionThreadError < StandardError
5
+ end
6
+
7
+ # = Transaction::Simple::ThreadSafe
8
+ # Thread-safe simple object transaction support for Ruby.
9
+ # Transaction::Simple::ThreadSafe is used in the same way as
10
+ # Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex object
11
+ # to ensure atomicity at the cost of performance in threaded applications.
12
+ #
13
+ # Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
14
+ # lock cannot be obtained immediately, a
15
+ # Transaction::TransactionThreadError will be raised.
16
+ #
17
+ # Thanks to Mauricio Fern�ndez for help with getting this part working.
18
+ #
19
+ # Threadsafe transactions can be used in any place that normal
20
+ # transactions would. The main difference would be in setup:
21
+ #
22
+ # require 'transaction/simple/threadsafe'
23
+ #
24
+ # x = "Hello, you."
25
+ # x.extend(Transaction::Simple::ThreadSafe) # Threadsafe
26
+ #
27
+ # y = "Hello, you."
28
+ # y.extend(Transaction::Simple) # Not threadsafe
29
+ module Transaction::Simple::ThreadSafe
30
+ include Transaction::Simple
31
+
32
+ SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc:
33
+ SKIP_TRANSACTION_VARS << "@__transaction_mutex__"
34
+
35
+ Transaction::Simple.instance_methods(false) do |meth|
36
+ next if meth == "transaction"
37
+ arg = "(name = nil)" unless meth == "transaction_name"
38
+ module_eval <<-EOS
39
+ def #{meth}#{arg}
40
+ if (@__transaction_mutex__ ||= Mutex.new).try_lock
41
+ result = super
42
+ @__transaction_mutex__.unlock
43
+ return result
44
+ else
45
+ raise TransactionThreadError, Messages[:cannot_obtain_transaction_lock] % meth
46
+ end
47
+ ensure
48
+ @__transaction_mutex__.unlock
49
+ end
50
+ EOS
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ require 'transaction/simple/threadsafe'
2
+
3
+ # A transaction group is an object wrapper that manages a group of objects
4
+ # as if they were a single object for the purpose of transaction
5
+ # management. All transactions for this group of objects should be
6
+ # performed against the transaction group object, not against individual
7
+ # objects in the group. This is the threadsafe version of a transaction
8
+ # group.
9
+ class Transaction::Simple::ThreadSafe::Group < Transaction::Simple::Group
10
+ def initialize(*objects)
11
+ @objects = objects || []
12
+ @objects.freeze
13
+ @objects.each { |obj| obj.extend(Transaction::Simple::ThreadSafe) }
14
+
15
+ if block_given?
16
+ begin
17
+ yield self
18
+ ensure
19
+ self.clear
20
+ end
21
+ end
22
+ end
23
+ end
@@ -120,7 +120,6 @@ class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc:
120
120
  end
121
121
 
122
122
  def test_block
123
- @value = VALUE.dup
124
123
  Transaction::Simple.start(@value) do |tv|
125
124
  assert_equal(true, tv.transaction_open?)
126
125
  assert_nothing_raised { tv.gsub!(/men/, 'women') }
@@ -144,7 +143,6 @@ class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc:
144
143
  end
145
144
 
146
145
  def test_named_block
147
- @value = VALUE.dup
148
146
  Transaction::Simple.start_named(:first, @value) do |tv|
149
147
  assert_equal(true, tv.transaction_open?)
150
148
  assert_equal(true, tv.transaction_open?(:first))
@@ -233,7 +231,6 @@ class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc:
233
231
  end
234
232
 
235
233
  def test_multilevel_block
236
- @value = VALUE.dup
237
234
  Transaction::Simple.start_named(:outer, @value) do |tv0|
238
235
  assert_equal(1, tv0.instance_variable_get(:@__transaction_level__))
239
236
  assert_equal(true, tv0.transaction_open?(:outer))
@@ -267,136 +264,14 @@ class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc:
267
264
  assert_nothing_raised { @value.abort_transaction }
268
265
  assert_equal(@orig, @value)
269
266
  end
270
- end
271
267
 
272
- class Test__Transaction_Simple_ThreadSafe < Test::Unit::TestCase #:nodoc:
273
- VALUE = "Now is the time for all good men to come to the aid of their country."
274
-
275
- def setup
268
+ def test_instance_var
276
269
  @value = VALUE.dup
277
- @value.extend(Transaction::Simple::ThreadSafe)
278
- end
279
-
280
- def test_extended
281
- assert_respond_to(@value, :start_transaction)
282
- end
283
-
284
- def test_started
285
- assert_equal(false, @value.transaction_open?)
286
- assert_nothing_raised { @value.start_transaction }
287
- assert_equal(true, @value.transaction_open?)
288
- end
289
-
290
- def test_rewind
291
- assert_equal(false, @value.transaction_open?)
292
- assert_raises(Transaction::TransactionError) { @value.rewind_transaction }
293
- assert_nothing_raised { @value.start_transaction }
294
- assert_equal(true, @value.transaction_open?)
295
- assert_nothing_raised { @value.gsub!(/men/, 'women') }
296
- assert_not_equal(VALUE, @value)
297
- assert_nothing_raised { @value.rewind_transaction }
298
- assert_equal(true, @value.transaction_open?)
299
- assert_equal(VALUE, @value)
300
- end
301
-
302
- def test_abort
303
- assert_equal(false, @value.transaction_open?)
304
- assert_raises(Transaction::TransactionError) { @value.abort_transaction }
305
- assert_nothing_raised { @value.start_transaction }
306
- assert_equal(true, @value.transaction_open?)
307
- assert_nothing_raised { @value.gsub!(/men/, 'women') }
308
- assert_not_equal(VALUE, @value)
309
- assert_nothing_raised { @value.abort_transaction }
310
- assert_equal(false, @value.transaction_open?)
311
- assert_equal(VALUE, @value)
312
- end
313
-
314
- def test_commit
315
- assert_equal(false, @value.transaction_open?)
316
- assert_raises(Transaction::TransactionError) { @value.commit_transaction }
317
- assert_nothing_raised { @value.start_transaction }
318
- assert_equal(true, @value.transaction_open?)
319
- assert_nothing_raised { @value.gsub!(/men/, 'women') }
320
- assert_not_equal(VALUE, @value)
321
- assert_equal(true, @value.transaction_open?)
322
- assert_nothing_raised { @value.commit_transaction }
323
- assert_equal(false, @value.transaction_open?)
324
- assert_not_equal(VALUE, @value)
325
- end
326
-
327
- def test_multilevel
328
- assert_equal(false, @value.transaction_open?)
329
- assert_nothing_raised { @value.start_transaction }
330
- assert_equal(true, @value.transaction_open?)
331
- assert_nothing_raised { @value.gsub!(/men/, 'women') }
332
- assert_equal(VALUE.gsub(/men/, 'women'), @value)
333
- assert_equal(true, @value.transaction_open?)
334
- assert_nothing_raised { @value.start_transaction }
335
- assert_nothing_raised { @value.gsub!(/country/, 'nation-state') }
336
- assert_nothing_raised { @value.commit_transaction }
337
- assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value)
338
- assert_equal(true, @value.transaction_open?)
339
- assert_nothing_raised { @value.abort_transaction }
340
- assert_equal(VALUE, @value)
341
- end
342
-
343
- def test_multilevel_named
344
- assert_equal(false, @value.transaction_open?)
345
- assert_raises(Transaction::TransactionError) { @value.transaction_name }
346
- assert_nothing_raised { @value.start_transaction(:first) } # 1
347
- assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) }
348
- assert_equal(true, @value.transaction_open?)
349
- assert_equal(true, @value.transaction_open?(:first))
350
- assert_equal(:first, @value.transaction_name)
351
- assert_nothing_raised { @value.start_transaction } # 2
352
- assert_not_equal(:first, @value.transaction_name)
353
- assert_equal(nil, @value.transaction_name)
354
- assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) }
355
- assert_nothing_raised { @value.abort_transaction(:first) }
356
- assert_equal(false, @value.transaction_open?)
357
- assert_nothing_raised do
358
- @value.start_transaction(:first)
359
- @value.gsub!(/men/, 'women')
360
- @value.start_transaction(:second)
361
- @value.gsub!(/women/, 'people')
362
- @value.start_transaction
363
- @value.gsub!(/people/, 'sentients')
364
- end
365
- assert_nothing_raised { @value.abort_transaction(:second) }
366
- assert_equal(true, @value.transaction_open?(:first))
367
- assert_equal(VALUE.gsub(/men/, 'women'), @value)
368
- assert_nothing_raised do
369
- @value.start_transaction(:second)
370
- @value.gsub!(/women/, 'people')
371
- @value.start_transaction
372
- @value.gsub!(/people/, 'sentients')
373
- end
374
- assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) }
375
- assert_nothing_raised { @value.rewind_transaction(:second) }
376
- assert_equal(VALUE.gsub(/men/, 'women'), @value)
377
- assert_nothing_raised do
378
- @value.gsub!(/women/, 'people')
379
- @value.start_transaction
380
- @value.gsub!(/people/, 'sentients')
381
- end
382
- assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) }
383
- assert_nothing_raised { @value.commit_transaction(:first) }
384
- assert_equal(VALUE.gsub(/men/, 'sentients'), @value)
385
- assert_equal(false, @value.transaction_open?)
386
- end
387
-
388
- def test_array
389
- assert_nothing_raised do
390
- @orig = ["first", "second", "third"]
391
- @value = ["first", "second", "third"]
392
- @value.extend(Transaction::Simple::ThreadSafe)
393
- end
394
- assert_equal(@orig, @value)
395
- assert_nothing_raised { @value.start_transaction }
270
+ @value.extend(Transaction::Simple)
271
+ @value.start_transaction
396
272
  assert_equal(true, @value.transaction_open?)
397
- assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
398
- assert_not_equal(@orig, @value)
399
- assert_nothing_raised { @value.abort_transaction }
400
- assert_equal(@orig, @value)
273
+ @value.instance_variable_set("@foo", "bar")
274
+ @value.rewind_transaction
275
+ assert_nil(@value.instance_variable_get("@foo"))
401
276
  end
402
277
  end
@@ -0,0 +1,44 @@
1
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0
2
+
3
+ require 'transaction/simple/group'
4
+ require 'test/unit'
5
+ class Test__Transaction_Simple_Group < Test::Unit::TestCase #:nodoc:
6
+ VALUE1 = "Hello, you."
7
+ VALUE2 = "And you, too."
8
+
9
+ def setup
10
+ @x = VALUE1.dup
11
+ @y = VALUE2.dup
12
+ end
13
+
14
+ def test_group
15
+ group = Transaction::Simple::Group.new(@x, @y)
16
+
17
+ assert_nothing_raised { group.start_transaction(:first) }
18
+ assert_equal(true, group.transaction_open?(:first))
19
+ assert_equal(true, @x.transaction_open?(:first))
20
+ assert_equal(true, @y.transaction_open?(:first))
21
+
22
+ assert_equal("Hello, world.", @x.gsub!(/you/, "world"))
23
+ assert_equal("And me, too.", @y.gsub!(/you/, "me"))
24
+
25
+ assert_nothing_raised { group.start_transaction(:second) }
26
+ assert_equal("Hello, HAL.", @x.gsub!(/world/, "HAL"))
27
+ assert_equal("And Dave, too.", @y.gsub!(/me/, "Dave"))
28
+
29
+ assert_nothing_raised { group.rewind_transaction(:second) }
30
+ assert_equal("Hello, world.", @x)
31
+ assert_equal("And me, too.", @y)
32
+
33
+ assert_equal("Hello, HAL.", @x.gsub!(/world/, "HAL"))
34
+ assert_equal("And Dave, too.", @y.gsub!(/me/, "Dave"))
35
+
36
+ assert_nothing_raised { group.commit_transaction(:second) }
37
+ assert_equal("Hello, HAL.", @x)
38
+ assert_equal("And Dave, too.", @y)
39
+
40
+ assert_nothing_raised { group.abort_transaction(:first) }
41
+ assert_equal("Hello, you.", @x)
42
+ assert_equal("And you, too.", @y)
43
+ end
44
+ end
@@ -0,0 +1,135 @@
1
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0
2
+
3
+ require 'transaction/simple/threadsafe'
4
+ require 'test/unit'
5
+ class Test__Transaction_Simple_ThreadSafe < Test::Unit::TestCase #:nodoc:
6
+ VALUE = "Now is the time for all good men to come to the aid of their country."
7
+
8
+ def setup
9
+ @value = VALUE.dup
10
+ @value.extend(Transaction::Simple::ThreadSafe)
11
+ end
12
+
13
+ def test_extended
14
+ assert_respond_to(@value, :start_transaction)
15
+ end
16
+
17
+ def test_started
18
+ assert_equal(false, @value.transaction_open?)
19
+ assert_nothing_raised { @value.start_transaction }
20
+ assert_equal(true, @value.transaction_open?)
21
+ end
22
+
23
+ def test_rewind
24
+ assert_equal(false, @value.transaction_open?)
25
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction }
26
+ assert_nothing_raised { @value.start_transaction }
27
+ assert_equal(true, @value.transaction_open?)
28
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
29
+ assert_not_equal(VALUE, @value)
30
+ assert_nothing_raised { @value.rewind_transaction }
31
+ assert_equal(true, @value.transaction_open?)
32
+ assert_equal(VALUE, @value)
33
+ end
34
+
35
+ def test_abort
36
+ assert_equal(false, @value.transaction_open?)
37
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction }
38
+ assert_nothing_raised { @value.start_transaction }
39
+ assert_equal(true, @value.transaction_open?)
40
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
41
+ assert_not_equal(VALUE, @value)
42
+ assert_nothing_raised { @value.abort_transaction }
43
+ assert_equal(false, @value.transaction_open?)
44
+ assert_equal(VALUE, @value)
45
+ end
46
+
47
+ def test_commit
48
+ assert_equal(false, @value.transaction_open?)
49
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction }
50
+ assert_nothing_raised { @value.start_transaction }
51
+ assert_equal(true, @value.transaction_open?)
52
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
53
+ assert_not_equal(VALUE, @value)
54
+ assert_equal(true, @value.transaction_open?)
55
+ assert_nothing_raised { @value.commit_transaction }
56
+ assert_equal(false, @value.transaction_open?)
57
+ assert_not_equal(VALUE, @value)
58
+ end
59
+
60
+ def test_multilevel
61
+ assert_equal(false, @value.transaction_open?)
62
+ assert_nothing_raised { @value.start_transaction }
63
+ assert_equal(true, @value.transaction_open?)
64
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
65
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
66
+ assert_equal(true, @value.transaction_open?)
67
+ assert_nothing_raised { @value.start_transaction }
68
+ assert_nothing_raised { @value.gsub!(/country/, 'nation-state') }
69
+ assert_nothing_raised { @value.commit_transaction }
70
+ assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value)
71
+ assert_equal(true, @value.transaction_open?)
72
+ assert_nothing_raised { @value.abort_transaction }
73
+ assert_equal(VALUE, @value)
74
+ end
75
+
76
+ def test_multilevel_named
77
+ assert_equal(false, @value.transaction_open?)
78
+ assert_raises(Transaction::TransactionError) { @value.transaction_name }
79
+ assert_nothing_raised { @value.start_transaction(:first) } # 1
80
+ assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) }
81
+ assert_equal(true, @value.transaction_open?)
82
+ assert_equal(true, @value.transaction_open?(:first))
83
+ assert_equal(:first, @value.transaction_name)
84
+ assert_nothing_raised { @value.start_transaction } # 2
85
+ assert_not_equal(:first, @value.transaction_name)
86
+ assert_equal(nil, @value.transaction_name)
87
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) }
88
+ assert_nothing_raised { @value.abort_transaction(:first) }
89
+ assert_equal(false, @value.transaction_open?)
90
+ assert_nothing_raised do
91
+ @value.start_transaction(:first)
92
+ @value.gsub!(/men/, 'women')
93
+ @value.start_transaction(:second)
94
+ @value.gsub!(/women/, 'people')
95
+ @value.start_transaction
96
+ @value.gsub!(/people/, 'sentients')
97
+ end
98
+ assert_nothing_raised { @value.abort_transaction(:second) }
99
+ assert_equal(true, @value.transaction_open?(:first))
100
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
101
+ assert_nothing_raised do
102
+ @value.start_transaction(:second)
103
+ @value.gsub!(/women/, 'people')
104
+ @value.start_transaction
105
+ @value.gsub!(/people/, 'sentients')
106
+ end
107
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) }
108
+ assert_nothing_raised { @value.rewind_transaction(:second) }
109
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
110
+ assert_nothing_raised do
111
+ @value.gsub!(/women/, 'people')
112
+ @value.start_transaction
113
+ @value.gsub!(/people/, 'sentients')
114
+ end
115
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) }
116
+ assert_nothing_raised { @value.commit_transaction(:first) }
117
+ assert_equal(VALUE.gsub(/men/, 'sentients'), @value)
118
+ assert_equal(false, @value.transaction_open?)
119
+ end
120
+
121
+ def test_array
122
+ assert_nothing_raised do
123
+ @orig = ["first", "second", "third"]
124
+ @value = ["first", "second", "third"]
125
+ @value.extend(Transaction::Simple::ThreadSafe)
126
+ end
127
+ assert_equal(@orig, @value)
128
+ assert_nothing_raised { @value.start_transaction }
129
+ assert_equal(true, @value.transaction_open?)
130
+ assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
131
+ assert_not_equal(@orig, @value)
132
+ assert_nothing_raised { @value.abort_transaction }
133
+ assert_equal(@orig, @value)
134
+ end
135
+ end