perobs 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/perobs/Object.rb CHANGED
@@ -76,7 +76,9 @@ module PEROBS
76
76
 
77
77
  attr_reader :attributes
78
78
 
79
- # Create a new PEROBS::Object object.
79
+ # New PEROBS objects must always be created by calling # Store.new().
80
+ # PEROBS users should never call this method or equivalents of derived
81
+ # methods directly.
80
82
  def initialize(store)
81
83
  super
82
84
  end
@@ -89,7 +91,7 @@ module PEROBS
89
91
  # @param val [Any] Value to be set
90
92
  # @return [true|false] True if the value was initialized, otherwise false.
91
93
  def init_attr(attr, val)
92
- if self.class.attributes.include?(attr)
94
+ if _all_attributes.include?(attr)
93
95
  _set(attr, val)
94
96
  return true
95
97
  end
@@ -102,9 +104,9 @@ module PEROBS
102
104
  # @return [Array of Fixnum or Bignum] IDs of referenced objects
103
105
  def _referenced_object_ids
104
106
  ids = []
105
- self.class.attributes.each do |attr|
107
+ _all_attributes.each do |attr|
106
108
  value = instance_variable_get(('@' + attr.to_s).to_sym)
107
- ids << value.id if value && value.is_a?(POReference)
109
+ ids << value.id if value && value.respond_to?(:is_poxreference?)
108
110
  end
109
111
  ids
110
112
  end
@@ -113,10 +115,10 @@ module PEROBS
113
115
  # delete all referenced to the given object ID.
114
116
  # @param id [Fixnum/Bignum] targeted object ID
115
117
  def _delete_reference_to_id(id)
116
- self.class.attributes.each do |attr|
118
+ _all_attributes.each do |attr|
117
119
  ivar = ('@' + attr.to_s).to_sym
118
120
  value = instance_variable_get(ivar)
119
- if value && value.is_a?(POReference) && value.id == id
121
+ if value && value.respond_to?(:is_poxreference?) && value.id == id
120
122
  instance_variable_set(ivar, nil)
121
123
  end
122
124
  end
@@ -129,6 +131,7 @@ module PEROBS
129
131
  def _deserialize(data)
130
132
  # Initialize all attributes with the provided values.
131
133
  data.each do |attr_name, value|
134
+ value = POXReference.new(@store, value.id) if value.is_a?(POReference)
132
135
  instance_variable_set(('@' + attr_name).to_sym, value)
133
136
  end
134
137
  end
@@ -137,7 +140,7 @@ module PEROBS
137
140
  # @return [String]
138
141
  def inspect
139
142
  "{\n" +
140
- self.class.attributes.map do |attr|
143
+ _all_attributes.map do |attr|
141
144
  ivar = ('@' + attr.to_s).to_sym
142
145
  " #{attr.inspect}=>#{instance_variable_get(ivar).inspect}"
143
146
  end.join(",\n") +
@@ -150,23 +153,25 @@ module PEROBS
150
153
  # class.
151
154
  def _serialize
152
155
  attributes = {}
153
- self.class.attributes.each do |attr|
156
+ _all_attributes.each do |attr|
154
157
  ivar = ('@' + attr.to_s).to_sym
155
- if (value = instance_variable_get(ivar)).is_a?(ObjectBase)
156
- raise ArgumentError, "The instance variable #{ivar} contains a " +
157
- "reference to a PEROBS::ObjectBase object! " +
158
- "This is not allowed. You must use the " +
159
- "accessor method to assign a reference to " +
160
- "another PEROBS object."
161
- end
162
- attributes[attr.to_s] = value
158
+ value = instance_variable_get(ivar)
159
+ #if (value = instance_variable_get(ivar)).is_a?(ObjectBase)
160
+ # raise ArgumentError, "The instance variable #{ivar} contains a " +
161
+ # "reference to a PEROBS::ObjectBase object! " +
162
+ # "This is not allowed. You must use the " +
163
+ # "accessor method to assign a reference to " +
164
+ # "another PEROBS object."
165
+ #end
166
+ attributes[attr.to_s] = value.respond_to?(:is_poxreference?) ?
167
+ POReference.new(value.id) : value
163
168
  end
164
169
  attributes
165
170
  end
166
171
 
167
172
  def _set(attr, val)
168
173
  ivar = ('@' + attr.to_s).to_sym
169
- if val.is_a?(ObjectBase)
174
+ if !val.respond_to?(:is_poxreference?) && val.is_a?(ObjectBase)
170
175
  # References to other PEROBS::Objects must be handled somewhat
171
176
  # special.
172
177
  if @store != val.store
@@ -175,7 +180,7 @@ module PEROBS
175
180
  # To release the object from the Ruby object list later, we store the
176
181
  # PEROBS::Store ID of the referenced object instead of the actual
177
182
  # reference.
178
- instance_variable_set(ivar, POReference.new(val._id))
183
+ instance_variable_set(ivar, POXReference.new(@store, val._id))
179
184
  else
180
185
  instance_variable_set(ivar, val)
181
186
  end
@@ -187,13 +192,24 @@ module PEROBS
187
192
 
188
193
  def _get(attr)
189
194
  value = instance_variable_get(('@' + attr.to_s).to_sym)
190
- if value.is_a?(POReference)
195
+ if value.respond_to?(:is_poxreference?)
191
196
  @store.object_by_id(value.id)
192
197
  else
193
198
  value
194
199
  end
195
200
  end
196
201
 
202
+ def _all_attributes
203
+ # PEROBS objects that don't have persistent attributes declared don't
204
+ # really make sense.
205
+ unless self.class.attributes
206
+ raise StandardError
207
+ "No persistent attributes have been declared for " +
208
+ "class #{self.class}. Use 'po_attr' to declare them."
209
+ end
210
+ self.class.attributes
211
+ end
212
+
197
213
  end
198
214
 
199
215
  end
@@ -32,26 +32,88 @@ module PEROBS
32
32
  # This class is used to replace a direct reference to another Ruby object by
33
33
  # the Store ID. This makes object disposable by the Ruby garbage collector
34
34
  # since it's no longer referenced once it has been evicted from the
35
- # PEROBS::Store cache.
36
- class POReference < Struct.new(:id)
35
+ # PEROBS::Store cache. The POXReference objects function as a transparent
36
+ # proxy for the objects they are referencing.
37
+ class POXReference < BasicObject
38
+
39
+ attr_reader :store, :id
40
+
41
+ def initialize(store, id)
42
+ super()
43
+ @store = store
44
+ @id = id
45
+ end
46
+
47
+ # Proxy all calls to unknown methods to the referenced object.
48
+ def method_missing(method_sym, *args, &block)
49
+ unless (obj = _referenced_object)
50
+ raise ::RuntimeError, "Internal consistency error. No object with " +
51
+ "ID #{@id} found in the store"
52
+ end
53
+ if obj.respond_to?(:is_poxreference?)
54
+ raise ::RuntimeError,
55
+ "POXReference that references a POXReference found"
56
+ end
57
+ obj.send(method_sym, *args, &block)
58
+ end
59
+
60
+ # Proxy all calls to unknown methods to the referenced object. Calling
61
+ # respond_to?(:is_poxreference?) is the only reliable way to find out if
62
+ # the object is a POXReference or not as pretty much every method call is
63
+ # proxied to the referenced object.
64
+ def respond_to?(method_sym, include_private = false)
65
+ (method_sym == :is_poxreference?) ||
66
+ _referenced_object.respond_to?(method_sym, include_private)
67
+ end
68
+
69
+ # Just for completeness. We don't want to be caught lying.
70
+ def is_poxreference?
71
+ true
72
+ end
73
+
74
+ # @return [ObjectBase] Return the referenced object. This method should
75
+ # not be used outside of the PEROBS library. Leaked references can cause
76
+ # data corruption.
77
+ def _referenced_object
78
+ @store.object_by_id(@id)
79
+ end
80
+
81
+ # BasicObject provides a ==() method that prevents method_missing from
82
+ # being called. So we have to pass the call manually to the referenced
83
+ # object.
84
+ # @param obj object to compare this object with.
85
+ def ==(obj)
86
+ _referenced_object == obj
87
+ end
37
88
 
38
- # Textual dump for debugging purposes
39
- # @return [String]
40
- def inspect
41
- "@#{id}"
89
+ # Shortcut to access the _id() method of the referenced object.
90
+ def _id
91
+ @id
42
92
  end
43
93
 
44
94
  end
45
95
 
96
+ # This class is used to serialize the POXReference objects. It only holds
97
+ # the ID of the referenced Object.
98
+ class POReference < Struct.new(:id)
99
+ end
100
+
46
101
  # Base class for all persistent objects. It provides the functionality
47
102
  # common to all classes of persistent objects.
48
103
  class ObjectBase
49
104
 
50
105
  attr_reader :_id, :store
51
106
 
52
- # Create a new PEROBS::ObjectBase object.
107
+ # New PEROBS objects must always be created by calling # Store.new().
108
+ # PEROBS users should never call this method or equivalents of derived
109
+ # methods directly.
53
110
  def initialize(store)
54
111
  @store = store
112
+ unless @store.object_creation_in_progress
113
+ raise ::RuntimeError,
114
+ "All PEROBS objects must exclusively be created by calling " +
115
+ "Store.new(). Never call the object constructor directly."
116
+ end
55
117
  @_id = @store.db.new_id
56
118
  @_stash_map = nil
57
119
 
@@ -59,8 +121,18 @@ module PEROBS
59
121
  @store.cache.cache_write(self)
60
122
  end
61
123
 
124
+ public
125
+
126
+ # This method can be overloaded by derived classes to do some massaging on
127
+ # the data after it has been restored from the database. This could either
128
+ # be some sanity check or code to migrate the object from one version to
129
+ # another.
130
+ def post_restore
131
+ end
132
+
62
133
  # Two objects are considered equal if their object IDs are the same.
63
134
  def ==(obj)
135
+ return false unless obj.is_a?(ObjectBase)
64
136
  obj && @_id == obj._id
65
137
  end
66
138
 
@@ -85,11 +157,12 @@ module PEROBS
85
157
 
86
158
  klass = store.class_map.id_to_class(db_obj['class_id'])
87
159
  # Call the constructor of the specified class.
88
- obj = Object.const_get(klass).new(store)
160
+ obj = store.construct_po(Object.const_get(klass))
89
161
  # The object gets created with a new ID by default. We need to restore
90
162
  # the old one.
91
163
  obj._change_id(id)
92
164
  obj._deserialize(db_obj['data'])
165
+ obj.post_restore
93
166
 
94
167
  obj
95
168
  end
@@ -140,29 +213,10 @@ module PEROBS
140
213
  def _change_id(id)
141
214
  # Unregister the object with the old ID from the write cache to prevent
142
215
  # cache corruption. The objects are index by ID in the cache.
143
- store.cache.unwrite(self)
216
+ @store.cache.unwrite(self)
144
217
  @_id = id
145
218
  end
146
219
 
147
- private
148
-
149
- def _dereferenced(v)
150
- v.is_a?(POReference) ? @store.object_by_id(v.id) : v
151
- end
152
-
153
- def _referenced(obj)
154
- if obj.is_a?(ObjectBase)
155
- # The obj is a reference to another persistent object. Store the ID
156
- # of that object in a POReference object.
157
- if @store != obj.store
158
- raise ArgumentError, 'The referenced object is not part of this store'
159
- end
160
- POReference.new(obj._id)
161
- else
162
- obj
163
- end
164
- end
165
-
166
220
  end
167
221
 
168
222
  end
data/lib/perobs/Store.rb CHANGED
@@ -25,6 +25,8 @@
25
25
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
26
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
+ require 'set'
29
+
28
30
  require 'perobs/Cache'
29
31
  require 'perobs/ClassMap'
30
32
  require 'perobs/BTreeDB'
@@ -38,18 +40,55 @@ module PEROBS
38
40
  # PEROBS::Store is a persistent storage system for Ruby objects. Regular
39
41
  # Ruby objects are transparently stored in a back-end storage and retrieved
40
42
  # when needed. It features a garbage collector that removes all objects that
41
- # are no longer in use. A build-in cache keeps access latencies to recently
43
+ # are no longer in use. A build-in cache keeps access latencies to recently
42
44
  # used objects low and lazily flushes modified objects into the persistend
43
- # back-end. Currently only filesystems are supported but it can easily be
44
- # extended to any key/value database.
45
+ # back-end. The default back-end is a filesystem based database.
46
+ # Alternatively, an Amazon DynamoDB can be used as well. Adding support for
47
+ # other key/value stores is fairly trivial to do. See PEROBS::DynamoDB for
48
+ # an example
49
+ #
50
+ # Persistent objects must be defined by deriving your class from
51
+ # PEROBS::Object, PERBOS::Array or PEROBS::Hash. Only instance variables
52
+ # that are declared via po_attr will be persistent. It is recommended that
53
+ # references to other objects are all going to persistent objects again. TO
54
+ # create a new persistent object you must call Store.new(). Don't use the
55
+ # constructors of persistent classes directly. Store.new() will return a
56
+ # proxy or delegator object that can be used like the actual object. By
57
+ # using delegators we can disconnect the actual object from the delegator
58
+ # handle.
59
+ #
60
+ # require 'perobs'
61
+ #
62
+ # class Person < PEROBS::Object
63
+ #
64
+ # po_attr :name, :mother, :father, :kids
65
+ #
66
+ # def initialize(store, name)
67
+ # super
68
+ # attr_init(:name, name)
69
+ # attr_init(:kids, @store.new(PEROBS::Array))
70
+ # end
71
+ #
72
+ # def to_s
73
+ # "#{@name} is the child of #{self.mother ? self.mother.name : 'unknown'} " +
74
+ # "and #{self.father ? self.father.name : 'unknown'}.
75
+ # end
76
+ #
77
+ # end
78
+ #
79
+ # store = PEROBS::Store.new('family')
80
+ # store['grandpa'] = joe = store.new(Person, 'Joe')
81
+ # store['grandma'] = jane = store.new(Person, 'Jane')
82
+ # jim = store.new(Person, 'Jim')
83
+ # jim.father = joe
84
+ # joe.kids << jim
85
+ # jim.mother = jane
86
+ # jane.kids << jim
87
+ # store.sync
45
88
  #
46
- # Persistent objects must be created by deriving your class from
47
- # PEROBS::Object. Only instance variables that are declared via
48
- # po_attr will be persistent. It is recommended that references to other
49
- # objects are all going to persistent objects again.
50
89
  class Store
51
90
 
52
- attr_reader :db, :cache, :class_map
91
+ attr_reader :db, :cache, :class_map, :object_creation_in_progress
53
92
 
54
93
  # Create a new Store.
55
94
  # @param data_base [String] the name of the database
@@ -85,6 +124,9 @@ module PEROBS
85
124
  # Create a map that can translate classes to numerical IDs and vice
86
125
  # versa.
87
126
  @class_map = ClassMap.new(@db)
127
+ # This flag is used to check that PEROBS objects are only created via
128
+ # the Store.new() call by PEROBS users.
129
+ @object_creation_in_progress = false
88
130
 
89
131
  # The Cache reduces read and write latencies by keeping a subset of the
90
132
  # objects in memory.
@@ -92,7 +134,8 @@ module PEROBS
92
134
 
93
135
  # The named (global) objects IDs hashed by their name
94
136
  unless (@root_objects = object_by_id(0))
95
- @root_objects = Hash.new(self)
137
+ @root_objects = construct_po(Hash)
138
+
96
139
  # The root object hash always has the object ID 0.
97
140
  @root_objects._change_id(0)
98
141
  # The ID change removes it from the write cache. We need to add it
@@ -101,6 +144,26 @@ module PEROBS
101
144
  end
102
145
  end
103
146
 
147
+ # You need to call this method to create new PEROBS objects that belong to
148
+ # this Store.
149
+ # @param klass [Class] The class of the object you want to create. This
150
+ # must be a derivative of ObjectBase.
151
+ # @param *args Optional list of other arguments that are passed to the
152
+ # constructor of the specified class.
153
+ # @return [POXReference] A reference to the newly created object.
154
+ def new(klass, *args)
155
+ POXReference.new(self, construct_po(klass, *args)._id)
156
+ end
157
+
158
+ # For library internal use only!
159
+ def construct_po(klass, *args)
160
+ @object_creation_in_progress = true
161
+ obj = klass.new(self, *args)
162
+ @object_creation_in_progress = false
163
+ obj
164
+ end
165
+
166
+
104
167
  # Delete the entire store. The store is no longer usable after this
105
168
  # method was called.
106
169
  def delete_store
@@ -124,6 +187,9 @@ module PEROBS
124
187
  return nil
125
188
  end
126
189
 
190
+ if obj.respond_to?(:is_poxreference?)
191
+ obj = obj._referenced_object
192
+ end
127
193
  # We only allow derivatives of PEROBS::Object to be stored in the
128
194
  # store.
129
195
  unless obj.is_a?(ObjectBase)
@@ -1,4 +1,4 @@
1
1
  module PEROBS
2
2
  # The version number
3
- VERSION = "1.1.0"
3
+ VERSION = "2.0.0"
4
4
  end
data/tasks/test.rake CHANGED
@@ -3,5 +3,6 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  desc 'Run all RSpec tests in the spec directory'
5
5
  RSpec::Core::RakeTask.new(:test) do |t|
6
- t.pattern = 'test/*_spec.rb'
6
+ t.pattern = Dir.glob('test/*_spec.rb')
7
+ t.rspec_opts = "-I #{File.join(File.dirname(__FILE__), '..', 'test')}"
7
8
  end
@@ -0,0 +1,208 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
4
+ #
5
+ # This file contains tests for Array that are similar to the tests for the
6
+ # Array implementation in MRI. The ideas of these tests were replicated in
7
+ # this code.
8
+ #
9
+ # MIT License
10
+ #
11
+ # Permission is hereby granted, free of charge, to any person obtaining
12
+ # a copy of this software and associated documentation files (the
13
+ # "Software"), to deal in the Software without restriction, including
14
+ # without limitation the rights to use, copy, modify, merge, publish,
15
+ # distribute, sublicense, and/or sell copies of the Software, and to
16
+ # permit persons to whom the Software is furnished to do so, subject to
17
+ # the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be
20
+ # included in all copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
+
30
+ require 'spec_helper'
31
+
32
+ require 'perobs'
33
+
34
+ class PO < PEROBS::Object
35
+
36
+ po_attr :name
37
+
38
+ def initialize(store, name = nil)
39
+ super(store)
40
+ _set(:name, name)
41
+ end
42
+
43
+ end
44
+
45
+ describe PEROBS::Array do
46
+
47
+ before(:all) do
48
+ @db_name = generate_db_name(__FILE__)
49
+ end
50
+
51
+ before(:each) do
52
+ @store = PEROBS::Store.new(@db_name)
53
+ end
54
+
55
+ after(:each) do
56
+ @store.delete_store
57
+ end
58
+
59
+ it 'should store simple objects persistently' do
60
+ @store['a'] = a = @store.new(PEROBS::Array)
61
+ a[0] = 'A'
62
+ a[1] = 'B'
63
+ a[2] = po = @store.new(PO)
64
+ po.name = 'foobar'
65
+
66
+ expect(a[0]).to eq('A')
67
+ expect(a[1]).to eq('B')
68
+ expect(a[2].name).to eq('foobar')
69
+ @store.sync
70
+
71
+ @store = PEROBS::Store.new(@db_name)
72
+ a = @store['a']
73
+ expect(a[0]).to eq('A')
74
+ expect(a[1]).to eq('B')
75
+ expect(a[2].name).to eq('foobar')
76
+ end
77
+
78
+ it 'should have an each method to iterate' do
79
+ @store['a'] = a = @store.new(PEROBS::Array)
80
+ a[0] = 'A'
81
+ a[1] = 'B'
82
+ a[2] = 'C'
83
+ vs = ''
84
+ a.each { |v| vs << v }
85
+ expect(vs).to eq('ABC')
86
+ @store.sync
87
+
88
+ @store = PEROBS::Store.new(@db_name)
89
+ a = @store['a']
90
+ vs = ''
91
+ a[3] = @store.new(PO, 'D')
92
+ a.each { |v| vs << (v.is_a?(String) ? v : v.name) }
93
+ expect(vs).to eq('ABCD')
94
+ end
95
+
96
+ # Utility method to create a PEROBS::Array from a normal Array.
97
+ def cpa(ary = nil)
98
+ a = @store.new(PEROBS::Array)
99
+ a.replace(ary) unless ary.nil?
100
+ @store['a'] = a
101
+ end
102
+
103
+ def pcheck
104
+ yield
105
+ @store.sync
106
+ @store = PEROBS::Store.new(@db_name)
107
+ yield
108
+ end
109
+
110
+ it 'should support reading methods' do
111
+ expect(cpa([ 1, 1, 3, 5 ]) & cpa([ 1, 2, 3 ])).to eq([ 1, 3 ])
112
+ expect(cpa & cpa([ 1, 2, 3 ])).to eq([])
113
+
114
+ expect(cpa.empty?).to be true
115
+ expect(cpa([ 0 ]).empty?).to be false
116
+
117
+ x = cpa([ 'it', 'came', 'to', 'pass', 'that', '...'])
118
+ x = x.sort.join(' ')
119
+ expect(x).to eq('... came it pass that to')
120
+ end
121
+
122
+ it 'should support Enumberable methods' do
123
+ x = cpa([ 2, 5, 3, 1, 7 ])
124
+ expect(x.find { |e| e == 4 }).to be_nil
125
+ expect(x.find { |e| e == 3 }).to eq(3)
126
+ end
127
+
128
+ it 'should support re-writing methods' do
129
+ x = cpa([2, 5, 3, 1, 7])
130
+ x.sort!{ |a, b| a <=> b }
131
+ pcheck { expect(x).to eq([ 1, 2, 3, 5, 7 ]) }
132
+ x.sort!{ |a, b| b - a }
133
+ pcheck { expect(x).to eq([ 7, 5, 3, 2, 1 ]) }
134
+
135
+ x.clear
136
+ pcheck { expect(x).to eq([]) }
137
+ end
138
+
139
+ it 'should support <<()' do
140
+ a = cpa([ 0, 1, 2 ])
141
+ a << 4
142
+ pcheck { expect(a).to eq([ 0, 1, 2, 4 ]) }
143
+ end
144
+
145
+ it 'should support []=' do
146
+ a = cpa([ 0, nil, 2 ])
147
+ a[1] = 1
148
+ pcheck { expect(a).to eq([ 0, 1, 2 ]) }
149
+ end
150
+
151
+ it 'should support collect!()' do
152
+ a = cpa([ 1, 'cat', 1..1 ])
153
+ expect(a.collect! { |e| e.class }).to eq([ Fixnum, String, Range ])
154
+ pcheck { expect(a).to eq([ Fixnum, String, Range ]) }
155
+
156
+ a = cpa([ 1, 'cat', 1..1 ])
157
+ expect(a.collect! { 99 }).to eq([ 99, 99, 99])
158
+ pcheck { expect(a).to eq([ 99, 99, 99]) }
159
+ end
160
+
161
+ it 'should support map!()' do
162
+ a = cpa([ 1, 'cat', 1..1 ])
163
+ expect(a.map! { |e| e.class }).to eq([ Fixnum, String, Range ])
164
+ pcheck { expect(a).to eq([ Fixnum, String, Range ]) }
165
+
166
+ a = cpa([ 1, 'cat', 1..1 ])
167
+ expect(a.map! { 99 }).to eq([ 99, 99, 99])
168
+ pcheck { expect(a).to eq ([ 99, 99, 99]) }
169
+ end
170
+
171
+ it 'should support fill()' do
172
+ pcheck { expect(cpa([]).fill(99)).to eq([]) }
173
+ pcheck { expect(cpa([]).fill(99, 0)).to eq([]) }
174
+ pcheck { expect(cpa([]).fill(99, 0, 1)).to eq([ 99 ]) }
175
+ end
176
+
177
+ it 'should support flatten!()' do
178
+ a1 = cpa([ 1, 2, 3])
179
+ a2 = cpa([ 5, 6 ])
180
+ a3 = cpa([ 4, a2 ])
181
+ a4 = cpa([ a1, a3 ])
182
+ pcheck { expect(a4.flatten).to eq([ 1, 2, 3, 4, 5, 6 ]) }
183
+ end
184
+
185
+ it 'should support replace()' do
186
+ a = cpa([ 1, 2, 3])
187
+ a_id = a.__id__
188
+ expect(a.replace(cpa([4, 5, 6]))).to eq([ 4, 5, 6 ])
189
+ pcheck { expect(a).to eq ([ 4, 5, 6 ]) }
190
+ end
191
+
192
+ it 'should support insert()' do
193
+ a = cpa([ 0 ])
194
+ a.insert(1)
195
+ pcheck { expect(a).to eq([ 0 ]) }
196
+ a.insert(1, 1)
197
+ pcheck { expect(a).to eq([ 0, 1]) }
198
+ end
199
+
200
+ it 'should support push()' do
201
+ a = cpa([ 1, 2, 3 ])
202
+ a.push(4, 5)
203
+ pcheck { expect(a).to eq([ 1, 2, 3, 4, 5 ]) }
204
+ a.push(nil)
205
+ pcheck { expect(a).to eq([ 1, 2, 3, 4, 5, nil ]) }
206
+ end
207
+
208
+ end