perobs 1.1.0 → 2.0.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/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