vigilem-support 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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