vigilem-support 0.0.9

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/vigilem/ffi.rb +19 -0
  3. data/lib/vigilem/ffi/array_pointer_sync.rb +382 -0
  4. data/lib/vigilem/ffi/struct.rb +54 -0
  5. data/lib/vigilem/ffi/utils.rb +240 -0
  6. data/lib/vigilem/ffi/utils/struct.rb +93 -0
  7. data/lib/vigilem/support.rb +31 -0
  8. data/lib/vigilem/support/core_ext.rb +13 -0
  9. data/lib/vigilem/support/core_ext/debug_puts.rb +5 -0
  10. data/lib/vigilem/support/core_ext/enumerable.rb +25 -0
  11. data/lib/vigilem/support/key_map.rb +323 -0
  12. data/lib/vigilem/support/key_map_info.rb +85 -0
  13. data/lib/vigilem/support/lazy_simple_delegator.rb +81 -0
  14. data/lib/vigilem/support/max_size_error.rb +17 -0
  15. data/lib/vigilem/support/metadata.rb +13 -0
  16. data/lib/vigilem/support/obj_space.rb +28 -0
  17. data/lib/vigilem/support/patch/cucumber/c_lexer.rb +30 -0
  18. data/lib/vigilem/support/patch/ffi/pointer.rb +19 -0
  19. data/lib/vigilem/support/size_error.rb +6 -0
  20. data/lib/vigilem/support/sys.rb +5 -0
  21. data/lib/vigilem/support/system.rb +92 -0
  22. data/lib/vigilem/support/transmutable_hash.rb +206 -0
  23. data/lib/vigilem/support/utils.rb +224 -0
  24. data/lib/vigilem/support/version.rb +5 -0
  25. data/spec/spec_helper.rb +9 -0
  26. data/spec/vigilem/ffi/array_pointer_sync_spec.rb +728 -0
  27. data/spec/vigilem/ffi/struct_spec.rb +22 -0
  28. data/spec/vigilem/ffi/utils/struct_spec.rb +79 -0
  29. data/spec/vigilem/ffi/utils_spec.rb +240 -0
  30. data/spec/vigilem/support/core_ext/enumerable_spec.rb +38 -0
  31. data/spec/vigilem/support/data/cached.kmap +130 -0
  32. data/spec/vigilem/support/data/cached_UTF-8_del.kmap +142 -0
  33. data/spec/vigilem/support/data/cached_short.kmap +38 -0
  34. data/spec/vigilem/support/data/dump_keys_short.kmap +128 -0
  35. data/spec/vigilem/support/key_map_spec.rb +767 -0
  36. data/spec/vigilem/support/lazy_simple_delegator_spec.rb +77 -0
  37. data/spec/vigilem/support/obj_space_spec.rb +47 -0
  38. data/spec/vigilem/support/sys_spec.rb +21 -0
  39. data/spec/vigilem/support/system_spec.rb +31 -0
  40. data/spec/vigilem/support/transmutable_hash_spec.rb +175 -0
  41. data/spec/vigilem/support/utils_spec.rb +214 -0
  42. metadata +240 -0
@@ -0,0 +1,224 @@
1
+ require 'vigilem/support/core_ext'
2
+
3
+ module Vigilem
4
+ module Support
5
+ #
6
+ # @todo 2 tables one for keysyms and the other for keycodes
7
+ module ArrayAndStringUtils
8
+ # splits an Array or String into parts at a
9
+ # location (rather than content) keeping the
10
+ # i-1 item in the lower "array"
11
+ # @param [#dup, #slice!] obj
12
+ # @param [Numeric] idx
13
+ # @return [Array]
14
+ def split_at(obj, idx)
15
+ [(copy = _deep_dup(obj)).slice!(0...idx), copy]
16
+ end
17
+
18
+ # works like split where the item split
19
+ # is removed
20
+ # @see #split_at
21
+ # @param [#dup, #slice!]
22
+ # @param [Numeric] idx
23
+ # @return [Array]
24
+ def split_on(obj, idx)
25
+ [(copy = _deep_dup(obj)).slice!(0...idx), copy.slice!(1..-1)]
26
+ end
27
+
28
+ # \#slice!s the array the length, and returns what is left over
29
+ # from the len `if len > obj.length`
30
+ # @param [#slice!, (#length || #size)] obj
31
+ # @param [Integer] len
32
+ # @return [Array<Object, Integer>] [obj #slice!ed, remainder of (len - obj.size)]
33
+ def offset!(obj, len) #@todo V should be nil not ''
34
+ [_offset_from_length(obj, len), (obj.slice!(0..len-1) || '')].reverse
35
+ end
36
+
37
+ # @see #offset!
38
+ # @param [#dup, #slice!, #length] obj
39
+ # @param [Integer] len
40
+ # @return [Array<Object, Integer>] [obj #slice!ed, remainder of (len - obj.size)]
41
+ def offset(obj, len)
42
+ if obj.respond_to? :slice #@todo V should be nil not ''
43
+ sliced = (obj.slice(0..len-1) || '')
44
+ [sliced, _offset_from_length(obj, len)]
45
+ else
46
+ offset!(_deep_dup(obj), len)
47
+ end
48
+ end
49
+
50
+ # if the Array contains one item it #pop's it off
51
+ # otherwise returns the array
52
+ # @param [Array] ary
53
+ # @return [Object || Array] [contents of array || the array unchanged
54
+ def unwrap_ary(ary)
55
+ (ary.respond_to?(:pop) and ary.one?) ? ary.pop : ary
56
+ end
57
+
58
+ # takes an Array with Ranges inside and checks to see if
59
+ # the number falls within that or matches one of the Integers passed it
60
+ # @param [Array<Numeric||Range>] ranged_ary the array to check
61
+ # @param [Numeric] num the number to check
62
+ # @return [TrueClass || FalseClass] whethor or not it is in or within that array
63
+ def in_ranged_array?(ranged_ary, num)
64
+ !!ranged_ary.find {|n| n === num }
65
+ end
66
+ end
67
+
68
+ #
69
+ #
70
+ module NumericUtils
71
+ # ceiling division
72
+ # @param [Integer] num numerator
73
+ # @param [Integer] denom denominator
74
+ # @return [Integer]
75
+ def ceil_div(num, denom)
76
+ (num.to_f/denom).ceil
77
+ end
78
+
79
+ # Computes the value of the first specified argument
80
+ # clamped to a range defined by the second argument
81
+ # @param [Numeric] x the number check against
82
+ # @param [Hash] min_or_max
83
+ # @option min_or_max [Numeric] :min lower bounds
84
+ # @option min_or_max [Numeric] :max upper bounds
85
+ # @return [Numeric] the clamped number
86
+ def clamp(x , min_or_max={})
87
+ [[x, min_or_max[:max]].compact.min, min_or_max[:min]].compact.max
88
+ end
89
+ end
90
+
91
+ #
92
+ #
93
+ module GemUtils
94
+ class << self
95
+
96
+ #
97
+ # @return [String] directory of the data folder for this gem
98
+ def data_dir(file_or_dir_path)
99
+ "#{gem_path(file_or_dir_path)}#{File::SEPARATOR}data"
100
+ end
101
+
102
+ #
103
+ # @return [String]
104
+ def gem_path(file_or_dir_path)
105
+ gem_root = Gem.path.find_result do |path|
106
+ regex = %r<(#{path})(#{File::SEPARATOR}gems#{File::SEPARATOR})>
107
+ if file_or_dir_path =~ regex
108
+ paths = file_or_dir_path.split(regex).reject(&:empty?)
109
+ if paths.size > 2
110
+ "#{paths[0]}#{paths[1]}#{paths[2].split('/', 2).first}"
111
+ end
112
+ end
113
+ end
114
+ if gem_root
115
+ gem_root
116
+ else
117
+ require 'bundler'
118
+ Bundler.root.to_path
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+
126
+ #
127
+ #
128
+ module KernelUtils
129
+ # @todo change name?
130
+ # gets the class if an object
131
+ # and returns self if already a Class
132
+ # @param obj object to get Class from
133
+ # @return [Class]
134
+ def get_class(obj)
135
+ obj.is_a?(Class) ? obj : obj.class
136
+ end
137
+
138
+ # sends all the args given to it, or none based on #arity the method object passed in
139
+ # this works great for sending arguments to a list of procs
140
+ # @param [#call] method_or_proc
141
+ # @param [Array] args
142
+ # @param [Proc] block
143
+ # @return
144
+ def send_all_or_no_args(callable, *args, &block)
145
+ callable.call(*[nil, args][clamp(callable.arity, min: 0, max: 1)], &block)
146
+ end
147
+ end
148
+
149
+ #
150
+ #
151
+ module ObjectUtils
152
+ # checks if object responds to deep_dup first
153
+ # :deep_dup, :_dump :_dump_data
154
+ # @todo name change
155
+ # @param obj
156
+ # @return
157
+ def _deep_dup(obj)
158
+ if obj.respond_to?(:deep_dup) and
159
+ not method(:deep_dup).source_location.first =~ /activesupport/
160
+ obj.deep_dup
161
+ else
162
+ deep_dup(obj)
163
+ end
164
+ end
165
+
166
+ # :deep_dup, :_dump :_dump_data
167
+ # @param obj
168
+ def deep_dup(obj)
169
+ Marshal.load(Marshal.dump(obj))
170
+ end
171
+
172
+ # emulates the id in inspect
173
+ # @param [#object_id]
174
+ # @return [String]
175
+ def inspect_id(obj)
176
+ "0x#{(obj.object_id << 1).to_s(16)}"
177
+ end
178
+
179
+ # @todo test
180
+ # @param
181
+ # @param [Proc] block
182
+ # @return [Array], ['#<', instance variables split by '=', '>']
183
+ def inspect_shell(obj, &block)
184
+ ['#<', *inspect_id(obj), obj.instance_variables.map do |k, v|
185
+ ary = [k, '=', v]
186
+ yield *ary if block_given?
187
+ ary
188
+ end, '>']
189
+ end
190
+ end
191
+
192
+ #
193
+ # simple utils/standard lib type stuff
194
+ module Utils
195
+
196
+ include ArrayAndStringUtils
197
+
198
+ include NumericUtils
199
+
200
+ include GemUtils
201
+
202
+ include KernelUtils
203
+
204
+ include ObjectUtils
205
+
206
+ extend self
207
+
208
+ module_function
209
+
210
+ #
211
+ # @param [#size || #length] obj
212
+ # @param [Integer] num
213
+ # @return [Integer || nil] remainder of num if available
214
+ def _offset_from_length(obj, num)
215
+ if obj.respond_to?(:length) || obj.respond_to?(:size)
216
+ clamp(num - (obj.respond(:length) || obj.respond(:size)), min: 0)
217
+ end
218
+ end
219
+
220
+ end
221
+
222
+ extend Support::GemUtils
223
+ end
224
+ end
@@ -0,0 +1,5 @@
1
+ module Vigilem
2
+ module Support
3
+ VERSION = '0.0.9'
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'rspec/given'
5
+
6
+ require 'ffi'
7
+ require 'active_support/concern'
8
+
9
+ require 'vigilem/support/core_ext/debug_puts'
@@ -0,0 +1,728 @@
1
+ require 'spec_helper'
2
+
3
+ require 'vigilem/ffi'
4
+
5
+ describe Vigilem::FFI::ArrayPointerSync do
6
+
7
+ def ary_type
8
+ :uint
9
+ end
10
+
11
+ before :all do
12
+ InitLessHost = Class.new do
13
+ def self.ary_type
14
+ :uint
15
+ end
16
+ include Vigilem::FFI::ArrayPointerSync
17
+ end
18
+ end
19
+
20
+ let(:init_less_host) { InitLessHost.new }
21
+
22
+ context '::included' do
23
+ it 'will set the native_type to POINTER' do
24
+ expect(InitLessHost.native_type).to eql(FFI::Type::POINTER)
25
+ end
26
+ end
27
+
28
+ context 'without initialize_ary_ptr_sync' do
29
+ context 'point_cuts' do
30
+ it 'will have only array manipulation methods' do
31
+ expect(described_class.instance_variable_get(:@point_cuts)).to_not include([
32
+ *Array.instance_methods.grep(/method|taint|trust|instance_variable|send|class|respond/),
33
+ :__id__, :object_id, :extend, :freeze, :is_a?, :instance_of? ])
34
+ end
35
+ end
36
+
37
+ context described_class::ClassMethods do
38
+
39
+ it 'will include FFI::DataConverter' do
40
+ init_less_host.initialize_ary_ptr_sync(5)
41
+ expect(init_less_host.class).to be_a(FFI::DataConverter)
42
+ end
43
+
44
+ describe '::ary_type' do
45
+ let(:fail_host) do
46
+ FailInitLessHost = Class.new do
47
+ include Vigilem::FFI::ArrayPointerSync
48
+ self
49
+ end
50
+ end
51
+
52
+ it 'raises NotImplementedError when not overriden' do
53
+ expect do
54
+ fail_host.ary_type
55
+ end.to raise_error(NotImplementedError)
56
+ end
57
+ end
58
+
59
+ describe '::ary_type_size' do
60
+ context 'is a Symbol' do
61
+ it 'returns the size of ary_type' do
62
+ expect(init_less_host.class.ary_type_size).to eql(::FFI.find_type(init_less_host.class.ary_type).size)
63
+ end
64
+ end
65
+
66
+ context 'is a Class' do
67
+
68
+ before do
69
+ Coord = Class.new(::FFI::Struct) do
70
+ layout :x, :short,
71
+ :y, :short
72
+ self
73
+ end
74
+ ClassArrayTypeInitLessHost = Class.new do
75
+ def self.ary_type
76
+ Coord
77
+ end
78
+ end #Class.new
79
+ end
80
+
81
+ it 'returns the size of ary_type' do
82
+ expect(init_less_host.class.ary_type_size).to eql(Coord.size)
83
+ end
84
+ end
85
+ end
86
+
87
+ describe '::ptr_capacity' do
88
+
89
+ let(:pointer) { FFI::MemoryPointer.new(:uint, 3) }
90
+
91
+ it 'gets the capacity of the pointer based on ::ary_type_size' do
92
+ expect(InitLessHost.ptr_capacity(pointer)).to eql(3)
93
+ end
94
+ end
95
+
96
+ describe '::ary_of_type' do
97
+
98
+ let(:pointer) do
99
+ poynter = FFI::MemoryPointer.new(:uint, 3)
100
+ Vigilem::FFI::Utils.put_array_typedef(poynter, :uint, [1, 2, 3])
101
+ poynter
102
+ end
103
+
104
+ it 'converts a FFI::Pointer to an array_sync of ary_type' do
105
+ expect(InitLessHost.ary_of_type(pointer)).to eql([1, 2, 3])
106
+ end
107
+
108
+ context 'given a FFI::Struct' do
109
+
110
+ class FFIPoints < FFI::Struct
111
+ layout :x, :uint, :y, :uint
112
+ end
113
+
114
+ let(:points_array) do
115
+ 3.times.map do
116
+ arg = FFIPoints.new
117
+ arg[:x] = 1
118
+ arg[:y] = 2
119
+ arg
120
+ end
121
+ end
122
+
123
+ let(:points_bytes_array) do
124
+ points_array.map {|pa| (ptr = pa.to_ptr).read_bytes(ptr.size) }
125
+ end
126
+
127
+ let(:points_pointer) do
128
+ poynter = FFI::MemoryPointer.new(FFIPoints, 3)
129
+ poynter.write_bytes(points_bytes_array.join)
130
+ poynter
131
+ end
132
+
133
+ class FFIHost
134
+ def self.ary_type
135
+ FFIPoints
136
+ end
137
+ include Vigilem::FFI::ArrayPointerSync
138
+ end
139
+
140
+ it 'converts a FFI::Struct to an array_sync of ary_type' do
141
+ result = FFIHost.ary_of_type(points_pointer).map.with_index do |ffi_pnt, idx|
142
+ ffi_pnt.to_ptr.read_bytes(FFIHost.ary_type_size)
143
+ end
144
+
145
+ expect(result).to eql(points_bytes_array)
146
+ end
147
+ end
148
+
149
+ context 'given a VFFIStruct' do
150
+ class VFFIPoints < VFFIStruct
151
+ layout :x, :uint, :y, :uint
152
+ end
153
+
154
+ let(:points_array) do
155
+ 3.times.map do
156
+ arg = VFFIPoints.new
157
+ arg[:x] = 1
158
+ arg[:y] = 2
159
+ arg
160
+ end
161
+ end
162
+
163
+ let(:points_pointer) do
164
+ poynter = FFI::MemoryPointer.new(VFFIPoints, 3)
165
+ poynter.write_bytes(points_array.map(&:bytes).join)
166
+ poynter
167
+ end
168
+
169
+ let(:vffi_host) do
170
+ VFFIHost = Class.new do
171
+ def self.ary_type
172
+ VFFIPoints
173
+ end
174
+ include Vigilem::FFI::ArrayPointerSync
175
+ end
176
+ end
177
+
178
+ it 'converts it to an array_sync of ary_type' do
179
+ expect(vffi_host.ary_of_type(points_pointer).map(&:bytes)).to eql(points_array.map(&:bytes))
180
+ end
181
+
182
+ end
183
+
184
+ end
185
+
186
+ describe '::raise_size_error' do
187
+ it 'raises NameError "size cannot exceed #{obj.max_size} for #{obj}" ' do
188
+ expect do
189
+ Host.raise_size_error
190
+ end.to raise_error(NameError)
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ end
197
+
198
+ describe '#initialize_ary_ptr_sync' do
199
+
200
+ context 'not called' do
201
+ it 'will not have initialized the ptr_cache_hash' do
202
+ expect(init_less_host.send(:ptr_cache_hash)).to be_nil
203
+ end
204
+
205
+ it 'will not have initialized the cache_hash' do
206
+ expect(init_less_host.send(:cache_hash)).to be_nil
207
+ end
208
+
209
+ it 'will not have initialized the ptr' do
210
+ expect do
211
+ init_less_host.ptr
212
+ end.to raise_error(TypeError)
213
+ end
214
+ end
215
+ context 'called' do
216
+
217
+ before(:each) do
218
+ init_less_host.initialize_ary_ptr_sync(5)
219
+ end
220
+
221
+ let(:init_size) { 5 }
222
+
223
+ let(:init_ptr) { FFI::MemoryPointer.new(ary_type, init_size) }
224
+
225
+ let(:init_ary) { [1, 2, 3] }
226
+
227
+ it 'sets up the ptr cache' do
228
+ expect(init_less_host.send(:ptr_cache_hash)).to_not be_nil
229
+ end
230
+
231
+ it 'sets up the array cache' do
232
+ expect(init_less_host.send(:cache_hash)).to_not be_nil
233
+ end
234
+
235
+ context 'with a pointer arg' do
236
+
237
+ before(:each) { init_less_host.initialize_ary_ptr_sync(init_ptr) }
238
+
239
+ it 'will init the ptr' do
240
+ expect(init_less_host.ptr).to_not be_nil
241
+ end
242
+
243
+ it 'will get the max_len from the pointer' do
244
+ expect(init_less_host.max_size).to eql(init_size)
245
+ end
246
+
247
+ end
248
+
249
+ context 'pointer arg with init_values' do
250
+ it 'will raise error if given both a pointer and initial values' do
251
+ expect do
252
+ init_less_host.initialize_ary_ptr_sync(init_ptr, init_ary)
253
+ end.to raise_error(ArgumentError)
254
+ end
255
+ end
256
+
257
+ context 'with a max_len arg' do
258
+
259
+ it 'inits max_size' do
260
+ init_less_host.initialize_ary_ptr_sync(init_size)
261
+ expect(init_less_host.max_size).to eql(init_size)
262
+ end
263
+
264
+ it 'throws and error when init_size does not respond to #to_i' do
265
+ expect do
266
+ init_less_host.initialize_ary_ptr_sync(Object.new)
267
+ end.to raise_error(TypeError)
268
+ end
269
+
270
+ end
271
+ context 'with max_len and init_values' do
272
+
273
+ before(:each) { init_less_host.initialize_ary_ptr_sync(init_size, *init_ary) }
274
+
275
+ it 'sets the max_size' do
276
+ expect(init_less_host.max_size).to eql(init_size)
277
+ end
278
+
279
+ it 'inserts some starter values' do
280
+ expect(init_less_host.send(:ary)).to eql(init_ary)
281
+ end
282
+ end
283
+
284
+ end
285
+ end
286
+
287
+ context 'after init' do
288
+
289
+ before :all do
290
+ Host = Class.new do
291
+ def self.ary_type
292
+ :uint
293
+ end
294
+ include Vigilem::FFI::ArrayPointerSync
295
+ def initialize(max_len_or_ptr, *init_values)
296
+ initialize_ary_ptr_sync(max_len_or_ptr, *init_values)
297
+ end
298
+ end
299
+ end
300
+
301
+ let(:host) { Host.new(3) }
302
+
303
+ describe '#after_ary_method' do
304
+ let(:array_intercept_host) do
305
+ ArrayInterceptInitLessHost = Class.new do
306
+ def self.ary_type
307
+ :uint
308
+ end
309
+ include Vigilem::FFI::ArrayPointerSync
310
+ def after_ary_method(method_name, return_value, *args, &block)
311
+ 'HAHA! not the value you wanted'
312
+ end
313
+ end
314
+ ArrayInterceptInitLessHost.new.initialize_ary_ptr_sync(20)
315
+ end
316
+
317
+ it 'is executed after array methods' do
318
+ expect(array_intercept_host << 1).to eql('HAHA! not the value you wanted')
319
+ end
320
+ end
321
+
322
+ describe '#max_size, #max_size=' do
323
+ let(:len) { 1 }
324
+
325
+ it 'max_size will be updated' do
326
+ host.send(:max_size=, len)
327
+ expect(host.max_size).to eql(len)
328
+ end
329
+
330
+ it 'max_len will be the same as max_size' do
331
+ host.send(:max_size=, len)
332
+ expect(host.max_len).to eql(host.max_size)
333
+ end
334
+ end
335
+
336
+ describe '#max_len, #max_len=' do
337
+ let(:len) { 1 }
338
+
339
+ it 'max_len will be updated' do
340
+ host.send(:max_len=, len)
341
+ expect(host.max_len).to eql(len)
342
+ end
343
+
344
+ it 'max_size will be the same as max_len' do
345
+ host.send(:max_len=, len)
346
+ expect(host.max_size).to eql(host.max_len)
347
+ end
348
+ end
349
+
350
+ describe '#replace' do
351
+ # @todo
352
+ #let!(:host) { host.concat([1, 2]) } # so this fails silently
353
+
354
+ it 'will work just like Array#replace' do
355
+ expect(host.replace([3, 4]).to_a).to eql([3,4])
356
+ end
357
+ end
358
+ describe '#bytes' do
359
+ it 'reads all the bytes from the pointer' do
360
+ host.concat([1, 2])
361
+ empty = "\x00" * (host.ptr.type_size - 1)
362
+ expect(host.bytes).to eql("\x01#{empty}\x02#{empty}\x00#{empty}")
363
+ end
364
+ end
365
+
366
+ describe '::bytes_of' do
367
+ it 'gets the bytes of an item in the array specified by index' do
368
+ host.concat([1, 2])
369
+ empty = "\x00" * (host.ptr.type_size - 1)
370
+ expect(host.bytes_of(1)).to eql("\x02#{empty}")
371
+ end
372
+ end
373
+
374
+ describe '#offsets' do
375
+
376
+ it %q(returns the offset of the objects in the array on the pointer it's in) do
377
+ host.concat([1, 2, 4])
378
+ len = host.ptr.type_size
379
+ expect(host.offsets).to eql([0, len, len *2])
380
+ end
381
+ end
382
+
383
+ context 'private' do
384
+
385
+ describe '#ary' do
386
+ it 'defaults to empty' do
387
+ expect(host.send(:ary)).to eql([])
388
+ end
389
+ end
390
+
391
+ describe '#_size' do
392
+ let(:part_dble_host) do
393
+ allow(host).to receive(:array).and_call_original
394
+ allow(host).to receive(:update)
395
+ allow(host).to receive(:_size).and_call_original
396
+ host.send(:ary).concat([1, 2, 3])
397
+ host
398
+ end
399
+
400
+ it 'returns the size' do
401
+ expect(part_dble_host.send(:_size)).to eql(3)
402
+ end
403
+
404
+ it %q(won't call :update) do
405
+ expect(part_dble_host).to_not have_received(:update)
406
+ end
407
+ end
408
+
409
+ describe '#ptr_hash' do
410
+ it 'creates a hash for the values in the ptr' do
411
+ expect(host.send(:ptr_hash)).to eql(host.ptr.read_bytes(host.ptr.size).hash)
412
+ end
413
+ end
414
+
415
+ describe '#ptr_changed?' do
416
+
417
+ context 'nothing changed' do
418
+ it 'will return false when the pointer has not changed' do
419
+ expect(host.send(:ptr_changed?)).to be_falsey
420
+ end
421
+ end
422
+
423
+ context 'ptr value changed' do
424
+ it %q(will return true when the pointer value changed and update hasn't ran) do
425
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1,2])
426
+ expect(host.send(:ptr_changed?)).to be_truthy
427
+ end
428
+ end
429
+
430
+ end
431
+
432
+ describe '#update_ptr_cache' do
433
+ it 'updates the cache of the pointer with the new pointer value' do
434
+ old_hsh = host.send(:ptr_cache_hash)
435
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1,2])
436
+ expect(host.send(:update_ptr_cache)).not_to eql(old_hsh)
437
+ end
438
+ end
439
+
440
+ describe '#update_ptr' do
441
+
442
+ let(:input) { [1, 2, 3] }
443
+
444
+ it 'updates the ptr from the #ary values' do
445
+ host.send(:ary).concat(input)
446
+ expect(host.bytes).to eql(input.pack('L*'))
447
+ end
448
+ end
449
+
450
+ describe '#ary_changed?' do
451
+ context 'nothing changed' do
452
+ it 'will return false when the ary has not changed' do
453
+ expect(host.send(:ary_changed?)).to be_falsey
454
+ end
455
+ end
456
+
457
+ context 'ary value changed' do
458
+ it %q(will return true when the ary value changed and update hasn't ran) do
459
+ host.send(:ary) << 1
460
+ expect(host.send(:ary_changed?)).to be_truthy
461
+ end
462
+ end
463
+ end
464
+
465
+ describe 'update_ary_cache' do
466
+ it 'updates the cache of the ary with the new pointer value' do
467
+ old_hsh = host.send(:cache_hash)
468
+ host.send(:ary).concat([1,2,3])
469
+ expect(host.send(:update_ary_cache)).not_to eql(old_hsh)
470
+ end
471
+ end
472
+
473
+ describe '#update_ary' do
474
+
475
+ let(:input) { [1, 2, 3] }
476
+
477
+ it 'updates the #ary from the #ptr values' do
478
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, input)
479
+ host.send(:update_ary)
480
+ expect(host.send(:ary)).to eql(input)
481
+ end
482
+ end
483
+
484
+ describe '#update_cache' do
485
+ it 'updates both caches' do
486
+ old_caches = [host.send(:cache_hash), host.send(:ptr_cache_hash)]
487
+ host.send(:ary).concat([1,2,3])
488
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1,2])
489
+ expect(host.send(:update_cache)).not_to eql(old_caches)
490
+ end
491
+ end
492
+
493
+ describe '#update' do
494
+ let(:input) { [1, 2, 3] }
495
+
496
+ it 'updates the ptr if the ary was updated' do
497
+ host.send(:ary).concat(input)
498
+ host.send(:update)
499
+ expect((pt = host.send(:ptr)).read_bytes(pt.size)).to eql(input.pack('L*'))
500
+ end
501
+
502
+ it 'updates the ary if the ptr was updated' do
503
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1,2,3])
504
+ host.send(:update)
505
+ expect(host.send(:ary)).to eql(input)
506
+ end
507
+ end
508
+
509
+ describe '#raise_size_error' do
510
+ it 'raises size error' do
511
+ expect do
512
+ host.send(:_raise_size_error)
513
+ end.to raise_error(Vigilem::Support::MaxSizeError)
514
+ end
515
+ end
516
+
517
+ describe '#_ptr_offsets' do
518
+ it 'returns an array of offsets based on the size of the array and type size of the elements' do
519
+ host.send(:ary).concat([1,2,3,4])
520
+ expect(host.send(:_ptr_offsets)).to eql([0, 4, 8, 12])
521
+ end
522
+ end
523
+ end # private
524
+
525
+ # @todo change some of the wording
526
+ describe '#what_changed? and #changed?,' do
527
+
528
+ context 'nothing changed,' do
529
+ it 'then returns all false' do
530
+ expect(host.what_changed?.values.all?).to be_falsey
531
+ end
532
+
533
+ it 'then changed? is false' do
534
+ expect(host.changed?).to be_falsey
535
+ end
536
+ end
537
+
538
+ context 'ary_changed?' do
539
+ before(:each) { host.send(:ary) << 1 }
540
+
541
+ it 'then array => true' do
542
+ expect(host.what_changed?).to eql({ary: true, ptr: false})
543
+ end
544
+
545
+ it 'then changed? is true' do
546
+ expect(host.changed?).to be_truthy
547
+ end
548
+ end
549
+ context 'ptr_changed?' do
550
+ before(:each) { Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1,2]) }
551
+
552
+ it 'then :ptr => true' do
553
+ expect(host.what_changed?).to eql({ary: false, ptr: true})
554
+ end
555
+
556
+ it 'then changed? is true' do
557
+ expect(host.changed?).to be_truthy
558
+ end
559
+ end
560
+ context 'both changed' do
561
+ it 'then raises an error' do
562
+ host.send(:ary) << 1
563
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1, 2])
564
+ expect do
565
+ host.what_changed?
566
+ end.to raise_error(RuntimeError)
567
+ end
568
+
569
+ it 'then changed? is raises error' do
570
+ host.send(:ary) << 1
571
+ Vigilem::FFI::Utils.write_array_typedef(host.ptr, Host.ary_type, [1, 2])
572
+ expect do
573
+ host.changed?
574
+ end.to raise_error(RuntimeError)
575
+ end
576
+ end
577
+ end
578
+
579
+ describe '#out_of_bounds?' do
580
+ it 'returns false when array.size <= max_size' do
581
+ host.send(:ary).concat([1])
582
+ expect(host.out_of_bounds?).to be_falsey
583
+ end
584
+ it 'returns true when array.size > max_size' do
585
+ host.send(:ary).concat([1, 2, 3, 4, 5])
586
+ expect(host.out_of_bounds?).to be_truthy
587
+ end
588
+ end
589
+
590
+ describe 'out_of_bounds_check' do
591
+ it 'returns nil if not out of bounds' do
592
+ host.send(:ary).concat([1])
593
+ expect(host.out_of_bounds_check).to be_nil
594
+ end
595
+ it 'raises error when out of bounds' do
596
+ host.send(:ary).concat([1, 2, 3, 4, 5])
597
+ expect do
598
+ host.out_of_bounds_check
599
+ end.to raise_error(Vigilem::Support::MaxSizeError)
600
+ end
601
+ end
602
+
603
+ describe '#to_s' do
604
+ it 'resembles an array' do
605
+ host << 1
606
+ expect(host.to_s).to eql("[1]")
607
+ end
608
+ end
609
+
610
+ describe '#to_a' do
611
+ it 'gets the array representation of the ary_sync' do
612
+ expect(host.to_a).to eql(host.send(:ary))
613
+ end
614
+ end
615
+
616
+ describe '#dup' do
617
+ before(:each) do
618
+ host.concat([1, 2, 3])
619
+ end
620
+ let(:dup) { host.dup }
621
+ it 'duplicates the values of ary_ptr_sync' do
622
+ expect(host.send(:ary)).to eql(dup.send(:ary))
623
+ end
624
+
625
+ it 'creates a new object' do
626
+ expect(host.object_id).not_to eql(dup.object_id)
627
+ end
628
+
629
+ end
630
+
631
+ describe '#pop' do
632
+ context 'pointer array' do
633
+
634
+ before :all do
635
+ class Pnt < ::FFI::Struct
636
+ layout :x, :long,
637
+ :y, :long
638
+
639
+ class << self
640
+ def native_type
641
+ ::FFI::Pointer
642
+ end
643
+
644
+ def to_native(value, ctx)
645
+ value.to_ptr
646
+ end
647
+ end
648
+ end
649
+
650
+ class PointAry
651
+ def self.ary_type
652
+ Pnt
653
+ end
654
+ include Vigilem::FFI::ArrayPointerSync
655
+ def initialize(max_len_or_ptr, *init_values)
656
+ initialize_ary_ptr_sync(max_len_or_ptr, *init_values)
657
+ end
658
+ end
659
+ end
660
+
661
+ let(:points) do
662
+ 0.upto(5).each_slice(2).map do |n, n_1|
663
+ obj = Pnt.new
664
+ obj[:x] = n
665
+ obj[:y] = n_1
666
+ obj
667
+ end
668
+ end
669
+
670
+ let(:point_ary) do
671
+ PointAry.new(3)
672
+ end
673
+
674
+ before(:each) do
675
+ point_ary.concat(points)
676
+ end
677
+
678
+ it 'removes an item from the stack' do
679
+ expect(point_ary.pop).to be_a(Pnt) and have_attributes( x: 4, y: 5 )
680
+ end
681
+
682
+ it 'will not be Hash equal to an array' do
683
+ point_ary.pop
684
+ expect(point_ary).not_to eql(points[0..1])
685
+ end
686
+
687
+ it 'will modify the original array' do
688
+ point_ary.pop
689
+ expect(point_ary).to be_an(PointAry) and match [
690
+ an_object_having_attributes( x: 0, y: 1 ),
691
+ an_object_having_attributes( x: 2, y: 3 )
692
+ ]
693
+ end
694
+ end
695
+
696
+ context 'simple array' do
697
+ before(:each) do
698
+ host.concat([1, 2, 3])
699
+ end
700
+
701
+ it 'removes an item from the stack' do
702
+ expect(host.pop).to eql(3)
703
+ end
704
+
705
+ it 'will not be Hash equal to an array' do
706
+ host.pop
707
+ expect(host).not_to eql([1, 2])
708
+ end
709
+
710
+ it 'will modify the original array' do
711
+ host.pop
712
+ expect(host).to be == [1,2]
713
+ end
714
+ end
715
+ end
716
+
717
+ describe '#inspect' do
718
+ it 'produces a string with all variables' do
719
+ expect(host.inspect).to match(
720
+ /#<[A-Z][a-z\d]+:0x[a-z\d]+ \[.*\] @max_size=\d+ @ptr=#<FFI::MemoryPointer address=0x[a-z\d]+ size=\d+> @cache_hash=-?\d+ @ptr_cache_hash=-?\d+>/
721
+ )
722
+ end
723
+
724
+ end
725
+
726
+ end #after_init
727
+
728
+ end