arrayio 0.1.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/README +76 -0
- data/lib/array_io.rb +381 -0
- data/lib/inspect_array_io.rb +28 -0
- data/test/array_io/alt_sep.txt +3 -0
- data/test/array_io/cr_lf_input.txt +3 -0
- data/test/array_io/input.index +0 -0
- data/test/array_io/input.txt +1 -0
- data/test/array_io/inputb.index +0 -0
- data/test/array_io/inputb.txt +1 -0
- data/test/array_io/lf_input.txt +3 -0
- data/test/array_io/lines.txt +19 -0
- data/test/array_io/without_index.txt +1 -0
- data/test/array_io_test.rb +569 -0
- data/test/arrayio_test_helper.rb +72 -0
- data/test/arrayio_test_suite.rb +4 -0
- metadata +61 -0
data/README
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# = ArrayIO
|
2
|
+
# Array-like behavior for archival files.
|
3
|
+
#
|
4
|
+
# == Introduction
|
5
|
+
# Archival files contain many entries following a regular pattern. These files often grow very large, making them
|
6
|
+
# inconvenient or impossible to parse directly into memory. ArrayIO provides an easy way to index these files
|
7
|
+
# so that array-like calls can retrieve entries on the fly.
|
8
|
+
#
|
9
|
+
# Internally ArrayIO keeps an IO object for the archive, and an index of ranges recording where each entry
|
10
|
+
# begins and ends. The index is an array, unless ArrayIO operates in 'uncached' mode. In this case the index is
|
11
|
+
# a separate file from which the ranges can be looked up. Uncached mode is useful when dealing with an extremely
|
12
|
+
# large number of ranges (entries) that would chew up lots of memory if kept in an array.
|
13
|
+
#
|
14
|
+
# When operating in a writable mode, entries can be added, inserted, and deleted. All changes are recorded in the index
|
15
|
+
# and will not reflect the actual order of entries in the archive unless consolidated.
|
16
|
+
#
|
17
|
+
# Example: If you add an entry at index 1000, the index records the range at index 1000, but the actual entry is appended
|
18
|
+
# to the end of the archive. If you delete an entry, it remains in the archive, but the range is removed from the index.
|
19
|
+
# Consolidation re-writes entries in their proper order and removes deleted entries.
|
20
|
+
#
|
21
|
+
# Notes:
|
22
|
+
# - BE CAREFUL to specify the correct mode when you open up an ArrayIO - as with File, a 'w' mode will overwrite
|
23
|
+
# the ENTIRE archive file. It is safer to use the append mode 'a'.
|
24
|
+
#
|
25
|
+
# Copyright (c) 2007 Simon Chiang
|
26
|
+
# Version: 0.1
|
27
|
+
# Licence: MIT-Style
|
28
|
+
#
|
29
|
+
# == Usage
|
30
|
+
#
|
31
|
+
# # Open, autoindex and work with 'archive.txt'
|
32
|
+
# ArrayIO.open('archive.txt', 'r') do |aio|
|
33
|
+
# aio[100] # -> entry 100
|
34
|
+
# aio[100] = "new entry" # -> reassigns entry 100
|
35
|
+
# aio.each do |entry|
|
36
|
+
# # do something
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Open 'archive.txt' in uncached mode. This creates a file 'archive.index'
|
41
|
+
# # that will be filled with the entry ranges. You can specify where entries
|
42
|
+
# # begin and end using options and a block in +reindex+.
|
43
|
+
# # If the block returns true, the line in considered the beginning of
|
44
|
+
# # an entry. This block looks for entries delmited by '>' like:
|
45
|
+
# # > entry 0
|
46
|
+
# # still entry0
|
47
|
+
# # > entry 1
|
48
|
+
# #
|
49
|
+
# aio = ArrayIO.new('archive.txt', 'ru')
|
50
|
+
# aio.reindex do |line|
|
51
|
+
# line =~ /^>/
|
52
|
+
# end
|
53
|
+
# aio.close
|
54
|
+
#
|
55
|
+
# # Subclass ArrayIO by overwriting +str_to_entry+, +entry_to_str+, and +reindex+
|
56
|
+
# # EntryIO parses entries as above, and functions like:
|
57
|
+
# # entryio[0] # => [0, "\nstill entry 0"]
|
58
|
+
# # entryio[1] # => [1, ""]
|
59
|
+
# # entryio[1] = [100, " is the new entry"] # => writes "> entry 100 is the new entry"
|
60
|
+
# # entryio[1] # => [100, " is the new entry"]
|
61
|
+
# class EntryIO
|
62
|
+
# def str_to_entry(str)
|
63
|
+
# str =~ /^> entry (\d+)(.*)$"
|
64
|
+
# [$1, $2]
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# def entry_to_str(entry)
|
68
|
+
# "> entry #{entry[0]}#{entry[1]}"
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# def reindex(options={}, &block)
|
72
|
+
# super(options) do |line|
|
73
|
+
# block_given? ? yield(line) : line =~ /^>/
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
# end
|
data/lib/array_io.rb
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class ArrayIO
|
4
|
+
attr_reader :io, :io_index
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def open(fd, mode='r', index_file=nil, auto_index=true, &block)
|
10
|
+
aio = self.new(fd, mode, index_file)
|
11
|
+
|
12
|
+
if block_given?
|
13
|
+
aio.reindex if auto_index && aio.empty?
|
14
|
+
yield(aio)
|
15
|
+
aio.close
|
16
|
+
else
|
17
|
+
aio
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(fd, mode='r', io_index=nil)
|
23
|
+
if mode =~ /u/i
|
24
|
+
mode = mode.delete('u')
|
25
|
+
@cached = false
|
26
|
+
else
|
27
|
+
@cached = true
|
28
|
+
end
|
29
|
+
|
30
|
+
if mode =~ /s/i
|
31
|
+
mode = mode.delete('s')
|
32
|
+
@strio = true
|
33
|
+
else
|
34
|
+
@strio = false
|
35
|
+
end
|
36
|
+
|
37
|
+
if strio?
|
38
|
+
mode = mode.delete('s')
|
39
|
+
@io = StringIO.open(fd, mode)
|
40
|
+
io_index = '' if io_index.nil?
|
41
|
+
else
|
42
|
+
@io = File.open(fd, mode)
|
43
|
+
set_binmode(io)
|
44
|
+
io_index = default_index_file if io_index.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
self.io_index = io_index
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_index_file
|
51
|
+
io.path.chomp(File.extname(io.path)) + '.index'
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# array functionality
|
56
|
+
#
|
57
|
+
|
58
|
+
def size
|
59
|
+
length
|
60
|
+
end
|
61
|
+
|
62
|
+
def length
|
63
|
+
if cached?
|
64
|
+
io_index.length
|
65
|
+
else
|
66
|
+
index_length/RANGE_SIZE
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def at(index)
|
71
|
+
self[index]
|
72
|
+
end
|
73
|
+
|
74
|
+
def [](input, length=nil)
|
75
|
+
range = range(input, length)
|
76
|
+
case range
|
77
|
+
when Array
|
78
|
+
range.collect { |r| read_entry(r) }
|
79
|
+
else
|
80
|
+
read_entry(range)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def []=(input, entry)
|
85
|
+
string = entry_to_str(entry)
|
86
|
+
|
87
|
+
range_begin = strio? ? io.string.length : io.stat.size
|
88
|
+
range_end = range_begin + string.length
|
89
|
+
range = format_range(range_begin, range_end)
|
90
|
+
|
91
|
+
# do this first in case io is not open for writing.
|
92
|
+
set_pos(io, range_begin)
|
93
|
+
io << string
|
94
|
+
|
95
|
+
if cached?
|
96
|
+
io_index[input] = range
|
97
|
+
else
|
98
|
+
pos = index_pos(input)
|
99
|
+
if strio?
|
100
|
+
io_index.string[pos...pos+RANGE_SIZE] = range
|
101
|
+
else
|
102
|
+
#io_index.close
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def fetch(index, default=nil, &block)
|
108
|
+
index += index_length if index < 0
|
109
|
+
val = (index >= length ? default : self[index])
|
110
|
+
block_given? ? yield(val) : val
|
111
|
+
end
|
112
|
+
|
113
|
+
def first(n=nil)
|
114
|
+
n.nil? ? self[0] : self[0,n]
|
115
|
+
end
|
116
|
+
|
117
|
+
def each_index(&block)
|
118
|
+
raise LocalJumpError("no block given") unless block_given?
|
119
|
+
|
120
|
+
0.upto(length-1, &block)
|
121
|
+
end
|
122
|
+
|
123
|
+
def each(&block)
|
124
|
+
raise LocalJumpError("no block given") unless block_given?
|
125
|
+
|
126
|
+
if cached?
|
127
|
+
io_index.each do |range|
|
128
|
+
yield( read_entry(range) )
|
129
|
+
end
|
130
|
+
else
|
131
|
+
io_index.pos = 0
|
132
|
+
while !io_index.eof?
|
133
|
+
begin_index, end_index = io_index.read(RANGE_SIZE).unpack(FORMAT)
|
134
|
+
yield( read_entry(begin_index..end_index) )
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def empty?
|
140
|
+
length == 0
|
141
|
+
end
|
142
|
+
|
143
|
+
def last(n=nil)
|
144
|
+
return self[-1] if n.nil?
|
145
|
+
|
146
|
+
start = length-n
|
147
|
+
start = 0 if start < 0
|
148
|
+
self[start, n]
|
149
|
+
end
|
150
|
+
|
151
|
+
def values_at(*selectors)
|
152
|
+
selectors.collect {|s| self[s]}.flatten
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# arrayio functionality
|
157
|
+
#
|
158
|
+
|
159
|
+
def cached?
|
160
|
+
@cached
|
161
|
+
end
|
162
|
+
|
163
|
+
def strio?
|
164
|
+
@strio
|
165
|
+
end
|
166
|
+
|
167
|
+
def load_index(index_file)
|
168
|
+
input = strio? ? index_file : (File.exists?(index_file) ? File.read(index_file) : '')
|
169
|
+
@io_index = parse_index(input)
|
170
|
+
end
|
171
|
+
|
172
|
+
def dump_index(index_file=default_index_file)
|
173
|
+
dumping_to_io = io_index.respond_to?(:path) ?
|
174
|
+
File.expand_path(io_index.path) != File.expand_path(index_file) :
|
175
|
+
false
|
176
|
+
|
177
|
+
File.open(index_file, 'w') do |file|
|
178
|
+
set_binmode(file)
|
179
|
+
if cached?
|
180
|
+
io_index.each do |range|
|
181
|
+
file << [range.begin, range.end].pack(FORMAT)
|
182
|
+
end
|
183
|
+
else
|
184
|
+
file << io_index.read
|
185
|
+
end
|
186
|
+
end unless dumping_to_io
|
187
|
+
|
188
|
+
index_file
|
189
|
+
end
|
190
|
+
|
191
|
+
def close
|
192
|
+
io.close
|
193
|
+
io_index.close unless cached?
|
194
|
+
end
|
195
|
+
|
196
|
+
def str_to_entry(string)
|
197
|
+
string
|
198
|
+
end
|
199
|
+
|
200
|
+
def entry_to_str(entry)
|
201
|
+
entry.to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
def range(input, length=nil)
|
205
|
+
if cached?
|
206
|
+
if length.nil?
|
207
|
+
return io_index[input]
|
208
|
+
else
|
209
|
+
return io_index[input, length]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
unless length.nil?
|
214
|
+
return nil if length < 0
|
215
|
+
input = input...(input + length)
|
216
|
+
end
|
217
|
+
|
218
|
+
if input.kind_of?(Range)
|
219
|
+
begin_pos = index_pos(input.begin)
|
220
|
+
return nil if begin_pos < 0 || begin_pos >= index_length
|
221
|
+
|
222
|
+
end_pos = index_pos(input.end) + (input.exclude_end? ? 0 : RANGE_SIZE)
|
223
|
+
read_length = end_pos-begin_pos
|
224
|
+
return [] if read_length <= 0
|
225
|
+
|
226
|
+
set_pos(io_index, begin_pos)
|
227
|
+
parse_index(io_index.read(read_length))
|
228
|
+
else
|
229
|
+
begin_pos = index_pos(input)
|
230
|
+
return nil if begin_pos < 0 || begin_pos >= index_length
|
231
|
+
|
232
|
+
set_pos(io_index, begin_pos)
|
233
|
+
array = io_index.read(RANGE_SIZE).unpack(FORMAT)
|
234
|
+
array.first...array.last
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def reindex(options={}, &block)
|
239
|
+
io.rewind
|
240
|
+
|
241
|
+
options = {
|
242
|
+
:sep_string => $/,
|
243
|
+
:break_before => false,
|
244
|
+
:exclude_break => false,
|
245
|
+
:limit => nil
|
246
|
+
}.merge(options)
|
247
|
+
|
248
|
+
sep_string = options[:sep_string]
|
249
|
+
break_before = options[:break_before]
|
250
|
+
exclude_break = options[:exclude_break]
|
251
|
+
|
252
|
+
limit = options[:limit]
|
253
|
+
total_count = 0
|
254
|
+
|
255
|
+
if cached?
|
256
|
+
self.io_index = []
|
257
|
+
else
|
258
|
+
io_index.close
|
259
|
+
if strio?
|
260
|
+
self.io_index = ''
|
261
|
+
else
|
262
|
+
self.io_index = io_index.path
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
last_pos = 0
|
267
|
+
current_pos = 0
|
268
|
+
range_begin = 0
|
269
|
+
io.each_line(sep_string) do |line|
|
270
|
+
# Note positions MUST be built up using line.length
|
271
|
+
# io.pos cannot return positions greater than ~2.1e9
|
272
|
+
last_pos = current_pos
|
273
|
+
current_pos += line.length
|
274
|
+
|
275
|
+
if (block_given? ? yield(line) : true)
|
276
|
+
range_end = (break_before || exclude_break) ? last_pos : current_pos
|
277
|
+
unless range_end == range_begin
|
278
|
+
io_index << format_range(range_begin, range_end)
|
279
|
+
total_count += 1
|
280
|
+
end
|
281
|
+
range_begin = (break_before && !exclude_break) ? last_pos : current_pos
|
282
|
+
|
283
|
+
break unless limit.nil? || total_count < limit
|
284
|
+
end
|
285
|
+
end
|
286
|
+
if limit.nil? || total_count < limit
|
287
|
+
range_end = current_pos
|
288
|
+
unless range_end == range_begin
|
289
|
+
io_index << format_range(range_begin, range_end)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# this must be done to re-stat Files, assuring index_length is the length of the io_index file.
|
294
|
+
unless cached? || strio?
|
295
|
+
io_index.close
|
296
|
+
self.io_index = io_index.path
|
297
|
+
end
|
298
|
+
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
protected
|
303
|
+
|
304
|
+
def read_entry(range)
|
305
|
+
return nil if range.nil?
|
306
|
+
|
307
|
+
set_pos(io, range.begin)
|
308
|
+
str_to_entry( io.read(range.end-range.begin) )
|
309
|
+
end
|
310
|
+
|
311
|
+
POSITION_MAX = 2100000000
|
312
|
+
# Positions larger than ~2.1e9 cannot be directly given to +pos+ for File objects.
|
313
|
+
# +set_pos+ incrementally seeks to positions beyond the maximum, if necessary.
|
314
|
+
def set_pos(io, pos)
|
315
|
+
if pos < POSITION_MAX
|
316
|
+
io.pos = pos
|
317
|
+
else
|
318
|
+
# note sysseek appears to be necessary here, rather than io.seek
|
319
|
+
io.pos = 0
|
320
|
+
while pos > POSITION_MAX
|
321
|
+
pos -= POSITION_MAX
|
322
|
+
io.sysseek(POSITION_MAX, IO::SEEK_CUR)
|
323
|
+
end
|
324
|
+
io.sysseek(pos, IO::SEEK_CUR)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
FORMAT = 'I*'
|
329
|
+
RANGE_SIZE = 8
|
330
|
+
|
331
|
+
def format_range(range_begin, range_end)
|
332
|
+
cached? ? range_begin...range_end : [range_begin, range_end].pack(FORMAT)
|
333
|
+
end
|
334
|
+
|
335
|
+
def index_length
|
336
|
+
strio? ? io_index.string.length : io_index.stat.size
|
337
|
+
end
|
338
|
+
|
339
|
+
def index_pos(index)
|
340
|
+
RANGE_SIZE*index + (index < 0 ? index_length : 0)
|
341
|
+
end
|
342
|
+
|
343
|
+
def parse_index(input)
|
344
|
+
last = nil
|
345
|
+
results = []
|
346
|
+
input.unpack(FORMAT).each do |current|
|
347
|
+
if last.nil?
|
348
|
+
last = current
|
349
|
+
else
|
350
|
+
results << (last...current)
|
351
|
+
last = nil
|
352
|
+
end
|
353
|
+
end
|
354
|
+
results
|
355
|
+
end
|
356
|
+
|
357
|
+
def io_index=(input)
|
358
|
+
if input.kind_of?(Array) || input.kind_of?(StringIO) || input.kind_of?(File)
|
359
|
+
@io_index = input
|
360
|
+
else
|
361
|
+
case
|
362
|
+
when strio? && cached?
|
363
|
+
load_index(input)
|
364
|
+
when strio?
|
365
|
+
@io_index = StringIO.new(input, 'a+')
|
366
|
+
when cached?
|
367
|
+
load_index(input)
|
368
|
+
else
|
369
|
+
@io_index = File.open(input, 'a+')
|
370
|
+
set_binmode(io_index)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# ArrayIO requires Files to operate in binmode on Windows. Otherwise ranges get
|
376
|
+
# improperly shifted, and additionally the unpacking frame gets shifted due to improper
|
377
|
+
# handling of cr (\r) characters.
|
378
|
+
def set_binmode(file)
|
379
|
+
file.binmode unless RUBY_PLATFORM.index('mswin').nil?
|
380
|
+
end
|
381
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'array_io'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
class InspectArrayIO
|
5
|
+
class << self
|
6
|
+
def inspect(aio, options={})
|
7
|
+
options = {
|
8
|
+
:from_start => 100,
|
9
|
+
:from_end => 100}.merge(options)
|
10
|
+
|
11
|
+
puts "N indexed entries: #{aio.length}"
|
12
|
+
|
13
|
+
puts "****************************"
|
14
|
+
puts "First #{options[:from_start]} bytes:"
|
15
|
+
aio.io.pos = 0
|
16
|
+
pp aio.io.read(options[:from_start])
|
17
|
+
puts "First entry:"
|
18
|
+
puts aio[0]
|
19
|
+
|
20
|
+
puts "****************************"
|
21
|
+
puts "Last #{options[:from_end]} bytes:"
|
22
|
+
aio.io.seek(-options[:from_end], IO::SEEK_END)
|
23
|
+
pp aio.io.read(options[:from_end] + 10)
|
24
|
+
puts "Last entry:"
|
25
|
+
puts aio[-1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
abcdefgh
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
abcdefgh
|
@@ -0,0 +1 @@
|
|
1
|
+
abcdefgh
|
@@ -0,0 +1,569 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'arrayio_test_helper.rb')
|
2
|
+
|
3
|
+
class ArrayIOTest < Test::Unit::TestCase
|
4
|
+
include Benchmark
|
5
|
+
|
6
|
+
|
7
|
+
def aio_filepath(path)
|
8
|
+
File.join(File.dirname(__FILE__), 'array_io', path)
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# file initialize tests
|
13
|
+
#
|
14
|
+
|
15
|
+
def test_initialize_with_index
|
16
|
+
aio = ArrayIO.new(aio_filepath('input.txt'))
|
17
|
+
|
18
|
+
assert_equal 'input.txt', File.basename(aio.io.path)
|
19
|
+
assert_equal "abcdefgh", aio.io.read
|
20
|
+
|
21
|
+
assert_equal [1...1, 2...4, 6...10], aio.io_index
|
22
|
+
aio.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_initialize_uncached_with_index
|
26
|
+
# non-windows only
|
27
|
+
if RUBY_PLATFORM.index('mswin').nil?
|
28
|
+
aio = ArrayIO.new(aio_filepath('input.txt'), 'ru')
|
29
|
+
|
30
|
+
assert aio.io_index.kind_of?(File)
|
31
|
+
assert_equal 'input.index', File.basename(aio.io_index.path)
|
32
|
+
assert_equal [1,1,2,4,6,10], aio.io_index.read.unpack('I*')
|
33
|
+
|
34
|
+
aio.close
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_initialize_uncached_with_index_binary_mode
|
39
|
+
# windows only
|
40
|
+
unless RUBY_PLATFORM.index('mswin').nil?
|
41
|
+
aio = ArrayIO.new(aio_filepath('inputb.txt'), 'ru')
|
42
|
+
|
43
|
+
assert aio.io_index.kind_of?(File)
|
44
|
+
assert_equal 'inputb.index', File.basename(aio.io_index.path)
|
45
|
+
assert_equal [1,1,2,4,6,10], aio.io_index.read.unpack('I*')
|
46
|
+
|
47
|
+
aio.close
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_initialize_without_index
|
52
|
+
aio = ArrayIO.new(aio_filepath('without_index.txt'))
|
53
|
+
|
54
|
+
assert_equal [], aio.io_index
|
55
|
+
aio.close
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_initialize_with_alternate_index
|
59
|
+
aio = ArrayIO.new(aio_filepath('without_index.txt'), 'r', aio_filepath('input.index'))
|
60
|
+
|
61
|
+
assert_equal [1...1, 2...4, 6...10], aio.io_index
|
62
|
+
aio.close
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_initialize_with_array_index
|
66
|
+
array = [0...1,2...3]
|
67
|
+
aio = ArrayIO.new(aio_filepath('input.txt'), 'r', array)
|
68
|
+
assert_equal array, aio.io_index
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# string initialize tests
|
73
|
+
#
|
74
|
+
|
75
|
+
def test_initialize_string
|
76
|
+
aio = ArrayIO.new('abcdefgh', 'rs')
|
77
|
+
|
78
|
+
assert aio.io.kind_of?(StringIO)
|
79
|
+
assert_equal "abcdefgh", aio.io.read
|
80
|
+
|
81
|
+
assert_equal [], aio.io_index
|
82
|
+
aio.close
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_initialize_string_uncached
|
86
|
+
aio = ArrayIO.new('abcdefgh', 'rsu')
|
87
|
+
|
88
|
+
assert aio.io_index.kind_of?(StringIO)
|
89
|
+
assert_equal '', aio.io_index.string
|
90
|
+
aio.close
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup_aio
|
94
|
+
ArrayIO.new("abcdefgh", 'r+s', [0...3, 3...5, 5...8])
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_initialize_string_with_array
|
98
|
+
aio = setup_aio
|
99
|
+
|
100
|
+
assert aio.io.kind_of?(StringIO)
|
101
|
+
assert_equal "abcdefgh", aio.io.read
|
102
|
+
|
103
|
+
assert_equal [0...3, 3...5, 5...8], aio.io_index
|
104
|
+
aio.close
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup_uaio
|
108
|
+
ArrayIO.new("abcdefgh", 'r+su', [0,3,3,5,5,8].pack('I*'))
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_initialize_string_uncached_with_index
|
112
|
+
uaio = setup_uaio
|
113
|
+
|
114
|
+
assert uaio.io.kind_of?(StringIO)
|
115
|
+
assert_equal "abcdefgh", uaio.io.read
|
116
|
+
|
117
|
+
assert uaio.io_index.kind_of?(StringIO)
|
118
|
+
assert_equal [0,3,3,5,5,8], uaio.io_index.read.unpack('I*')
|
119
|
+
uaio.close
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# range tests
|
124
|
+
#
|
125
|
+
|
126
|
+
def test_range
|
127
|
+
[setup_aio, setup_uaio].each do |aio|
|
128
|
+
assert_equal 0...3, aio.range(0)
|
129
|
+
assert_equal 3...5, aio.range(1)
|
130
|
+
assert_equal 5...8, aio.range(2)
|
131
|
+
assert_nil aio.range(3)
|
132
|
+
|
133
|
+
assert_equal 5...8, aio.range(-1)
|
134
|
+
assert_equal 3...5, aio.range(-2)
|
135
|
+
assert_equal 0...3, aio.range(-3)
|
136
|
+
assert_nil aio.range(-4)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_range_when_input_is_start_length
|
141
|
+
array = [0...3, 3...5, 5...8]
|
142
|
+
|
143
|
+
[setup_aio, setup_uaio].each do |aio|
|
144
|
+
[0..0, 0...0, 0..1, 0..2, 0..-1, 0..3, 0...3, -4..1, 5..10, -3..-5].each do |r|
|
145
|
+
start = r.begin
|
146
|
+
length = r.end - r.begin
|
147
|
+
|
148
|
+
assert_equal array[start, length], aio.range(start, length), r.to_s
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def test__range_when_input_is_range
|
154
|
+
array = [0...3, 3...5, 5...8]
|
155
|
+
|
156
|
+
[setup_aio, setup_uaio].each do |aio|
|
157
|
+
[0..0, 0...0, 0..1, 0..2, 0..-1, 0..3, 0...3, -4..1, 5..10, -3..-5].each do |r|
|
158
|
+
assert_equal array[r], aio.range(r), r.to_s
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_range_speed
|
164
|
+
benchmark_test do
|
165
|
+
aio = setup_aio
|
166
|
+
uaio = setup_uaio
|
167
|
+
bm(12) do |x|
|
168
|
+
x.report("10k index") { 10000.times { aio.range(1) } }
|
169
|
+
x.report("10k range") { 10000.times { aio.range(1..1) } }
|
170
|
+
x.report("10k s,l") { 10000.times { aio.range(1, 1) } }
|
171
|
+
|
172
|
+
x.report("10k uindex") { 10000.times { uaio.range(1) } }
|
173
|
+
x.report("10k urange") { 10000.times { uaio.range(1..1) } }
|
174
|
+
x.report("10k us,l") { 10000.times { uaio.range(1, 1) } }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# get tests
|
181
|
+
#
|
182
|
+
|
183
|
+
def test_get_when_input_is_index
|
184
|
+
[setup_aio, setup_uaio].each do |aio|
|
185
|
+
assert_equal "abc", aio[0]
|
186
|
+
assert_equal "de", aio[1]
|
187
|
+
assert_equal "fgh", aio[2]
|
188
|
+
assert_nil aio[3]
|
189
|
+
|
190
|
+
assert_equal "fgh", aio[-1]
|
191
|
+
assert_equal "de", aio[-2]
|
192
|
+
assert_equal "abc", aio[-3]
|
193
|
+
assert_nil aio[-4]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_get_when_input_is_start_length
|
198
|
+
array = ["abc", "de", "fgh"]
|
199
|
+
|
200
|
+
[setup_aio, setup_uaio].each do |aio|
|
201
|
+
[0..0, 0...0, 0..1, 0..2, 0..-1, 0..3, 0...3, -4..1, 5..10, -3..-5, -1..5].each do |r|
|
202
|
+
start = r.begin
|
203
|
+
length = r.end - r.begin
|
204
|
+
|
205
|
+
assert_equal array[start, length], aio[start, length], r.to_s
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_get_when_input_is_range
|
211
|
+
array = ["abc", "de", "fgh"]
|
212
|
+
|
213
|
+
[setup_aio, setup_uaio].each do |aio|
|
214
|
+
[0..0, 0...0, 0..1, 0..2, 0..-1, 0..3, 0...3, -4..1, 5..10, -3..-5].each do |r|
|
215
|
+
assert_equal array[r], aio[r], r.to_s
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_get_speed
|
221
|
+
benchmark_test do
|
222
|
+
array = []
|
223
|
+
0.upto(10000-1) {|i| array << "line #{i}\n" }
|
224
|
+
|
225
|
+
aio = ArrayIO.new(array.join(''), 'rs')
|
226
|
+
aio.reindex
|
227
|
+
|
228
|
+
uaio = ArrayIO.new(array.join(''), 'rsu')
|
229
|
+
uaio.reindex
|
230
|
+
|
231
|
+
assert_equal "line 1000\n", aio[1000]
|
232
|
+
bm(12) do |x|
|
233
|
+
x.report("10k index") { 10000.times { aio[1000] } }
|
234
|
+
x.report("10k range") { 10000.times { aio[1000...1000] } }
|
235
|
+
x.report("10k s,l") { 10000.times { aio[1000, 1] } }
|
236
|
+
|
237
|
+
x.report("10k uindex") { 10000.times { uaio[1000] } }
|
238
|
+
x.report("10k urange") { 10000.times { uaio[1000..1000] } }
|
239
|
+
x.report("10k us,l") { 10000.times { uaio[1000, 1] } }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# test insert
|
246
|
+
#
|
247
|
+
|
248
|
+
def test_insert
|
249
|
+
[setup_aio, setup_uaio].each do |aio|
|
250
|
+
assert_equal "abc", aio[0]
|
251
|
+
assert_equal "fgh", aio[-1]
|
252
|
+
assert_equal "de", aio[1]
|
253
|
+
assert_equal 3, aio.length
|
254
|
+
|
255
|
+
aio[0] = 'xyz'
|
256
|
+
aio[-1] = 'pq'
|
257
|
+
aio[1] = 'mno'
|
258
|
+
|
259
|
+
assert_equal "xyz",aio[0]
|
260
|
+
assert_equal "pq", aio[-1]
|
261
|
+
assert_equal "mno", aio[1]
|
262
|
+
assert_equal 3, aio.length
|
263
|
+
|
264
|
+
assert_equal 'abcdefghxyzpqmno', aio.io.string
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_insert_raises_error_if_io_is_not_writable
|
269
|
+
aio = ArrayIO.new('', 'rs')
|
270
|
+
assert_raise(IOError) { aio[0] = 'abc'}
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_insert_speed
|
274
|
+
benchmark_test do
|
275
|
+
array = []
|
276
|
+
0.upto(10000-1) {|i| array << "line #{i}\n" }
|
277
|
+
|
278
|
+
aio = ArrayIO.new(array.join(''), 'r+s')
|
279
|
+
aio.reindex
|
280
|
+
|
281
|
+
uaio = ArrayIO.new(array.join(''), 'r+su')
|
282
|
+
uaio.reindex
|
283
|
+
|
284
|
+
bm(12) do |x|
|
285
|
+
x.report("10k index=") { 10000.times { aio[1000] = "line 1000\n" } }
|
286
|
+
#x.report("10k range=") { 10000.times { aio[1000..1000] = "line 1000\n" } }
|
287
|
+
#x.report("10k s,l=") { 10000.times { aio[1000, 1] = "line 1000\n" } }
|
288
|
+
|
289
|
+
x.report("10k uindex=") { 10000.times { uaio[1000] = "line 1000\n" } }
|
290
|
+
#x.report("10k crange=") { 10000.times { caio[1000..1000] = "line 1000\n" } }
|
291
|
+
#x.report("10k cs,l=") { 10000.times { caio[1000, 1] = "line 1000\n" } }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
#
|
297
|
+
# indexing tests
|
298
|
+
#
|
299
|
+
|
300
|
+
def reindex_test(expected, options={}, &block)
|
301
|
+
cases = {
|
302
|
+
:end_midline => "012\n\n56\n\n9",
|
303
|
+
:end_on_line => "012\n\n56\n\n9\n",
|
304
|
+
:end_on_break => "012\n\n56\n\n9\n\n",
|
305
|
+
:no_break => "0123456789",
|
306
|
+
:backing_breaks => "012\n\n\n\n\n\n9",
|
307
|
+
:cr_lf => "012\r\n\r\n56\r\n\r\n9"}
|
308
|
+
|
309
|
+
cases.each_pair do |key, string|
|
310
|
+
next unless expected.has_key?(key)
|
311
|
+
|
312
|
+
['rs', 'rsu'].each do |mode|
|
313
|
+
aio = ArrayIO.new(string, mode)
|
314
|
+
aio.reindex(options, &block)
|
315
|
+
|
316
|
+
assert_equal expected[key].length, aio.length, "#{mode} #{key}"
|
317
|
+
assert_equal expected[key], aio[0..-1], "#{mode} #{key}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def test_reindex_treats_each_line_as_break_by_default
|
323
|
+
reindex_test(
|
324
|
+
:end_midline => ["012\n", "\n", "56\n", "\n", "9"],
|
325
|
+
:end_on_line => ["012\n", "\n", "56\n", "\n", "9\n"],
|
326
|
+
:end_on_break => ["012\n", "\n", "56\n", "\n", "9\n", "\n"],
|
327
|
+
:no_break => ["0123456789"],
|
328
|
+
:backing_breaks => ["012\n", "\n", "\n", "\n", "\n", "\n", "9"],
|
329
|
+
:cr_lf => ["012\r\n", "\r\n", "56\r\n", "\r\n", "9"])
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_reindex_block_determines_if_line_is_a_break
|
333
|
+
reindex_test(
|
334
|
+
:end_midline => ["012\n\n", "56\n\n", "9"],
|
335
|
+
:end_on_line => ["012\n\n", "56\n\n", "9\n"],
|
336
|
+
:end_on_break => ["012\n\n", "56\n\n", "9\n\n"],
|
337
|
+
:no_break => ["0123456789"],
|
338
|
+
:backing_breaks => ["012\n\n", "\n", "\n", "\n", "\n", "9"],
|
339
|
+
:cr_lf => ["012\r\n\r\n", "56\r\n\r\n", "9"]) do |line|
|
340
|
+
line.strip.empty?
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def test_reindex_breaking_before
|
345
|
+
reindex_test({
|
346
|
+
:end_midline => ["012\n", "\n56\n", "\n9"],
|
347
|
+
:end_on_line => ["012\n", "\n56\n", "\n9\n"],
|
348
|
+
:end_on_break => ["012\n", "\n56\n", "\n9\n", "\n"],
|
349
|
+
:no_break => ["0123456789"],
|
350
|
+
:backing_breaks => ["012\n", "\n", "\n", "\n", "\n", "\n9"],
|
351
|
+
:cr_lf => ["012\r\n", "\r\n56\r\n", "\r\n9"]},
|
352
|
+
:break_before => true) do |line|
|
353
|
+
line.strip.empty?
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def test_reindex_excluding_break
|
358
|
+
reindex_test({
|
359
|
+
:end_midline => ["012\n", "56\n", "9"],
|
360
|
+
:end_on_line => ["012\n", "56\n", "9\n"],
|
361
|
+
:end_on_break => ["012\n", "56\n", "9\n"],
|
362
|
+
:no_break => ["0123456789"],
|
363
|
+
:backing_breaks => ["012\n", "9"],
|
364
|
+
:cr_lf => ["012\r\n", "56\r\n", "9"]},
|
365
|
+
:exclude_break => true) do |line|
|
366
|
+
line.strip.empty?
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_reindex_breaking_before_and_excluding_break
|
371
|
+
# note this is the same as simply excluding the break
|
372
|
+
reindex_test({
|
373
|
+
:end_midline => ["012\n", "56\n", "9"],
|
374
|
+
:end_on_line => ["012\n", "56\n", "9\n"],
|
375
|
+
:end_on_break => ["012\n", "56\n", "9\n"],
|
376
|
+
:no_break => ["0123456789"],
|
377
|
+
:backing_breaks => ["012\n", "9"],
|
378
|
+
:cr_lf => ["012\r\n", "56\r\n", "9"]},
|
379
|
+
:exclude_break => true,
|
380
|
+
:break_before => true) do |line|
|
381
|
+
line.strip.empty?
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_reindex_with_alt_sep_string
|
386
|
+
reindex_test({
|
387
|
+
:end_midline => ["012\n\n", "56\n\n", "9"],
|
388
|
+
:end_on_line => ["012\n\n", "56\n\n", "9\n"],
|
389
|
+
:end_on_break => ["012\n\n", "56\n\n", "9\n\n"],
|
390
|
+
:no_break => ["0123456789"],
|
391
|
+
:backing_breaks => ["012\n\n", "\n\n", "\n\n", "9"],
|
392
|
+
:cr_lf => ["012\r\n\r\n56\r\n\r\n9"]},
|
393
|
+
:sep_string => "\n\n")
|
394
|
+
|
395
|
+
reindex_test({
|
396
|
+
:end_midline => ["012\n\n56", "\n\n9"],
|
397
|
+
:end_on_line => ["012\n\n56", "\n\n9\n"],
|
398
|
+
:end_on_break => ["012\n\n56", "\n\n9\n\n"],
|
399
|
+
:no_break => ["0123456", "789"],
|
400
|
+
:backing_breaks => ["012\n\n\n\n\n\n9"],
|
401
|
+
:cr_lf => ["012\r\n\r\n56", "\r\n\r\n9"]},
|
402
|
+
:sep_string => "56")
|
403
|
+
end
|
404
|
+
|
405
|
+
#
|
406
|
+
# file tests
|
407
|
+
#
|
408
|
+
|
409
|
+
def index_test(path, &block)
|
410
|
+
aio = ArrayIO.open(aio_filepath(path), 'ru')
|
411
|
+
|
412
|
+
begin
|
413
|
+
yield(aio)
|
414
|
+
ensure
|
415
|
+
aio.close
|
416
|
+
index_path = aio.io_index.path
|
417
|
+
File.delete(index_path) if index_path && File.exists?(index_path)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def test_cr_lf_file
|
422
|
+
index_test('cr_lf_input.txt') do |aio|
|
423
|
+
aio.reindex
|
424
|
+
|
425
|
+
assert_equal "012\r\n", aio[0]
|
426
|
+
assert_equal "56\r\n", aio[1]
|
427
|
+
assert_equal "9", aio[2]
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def test_parse_from_lf_file
|
432
|
+
index_test('lf_input.txt') do |aio|
|
433
|
+
aio.reindex
|
434
|
+
|
435
|
+
assert_equal "012\n", aio[0]
|
436
|
+
assert_equal "56\n", aio[1]
|
437
|
+
assert_equal "9", aio[2]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def test_parse_from_alt_sep
|
442
|
+
index_test('alt_sep.txt') do |aio|
|
443
|
+
aio.reindex do |line|
|
444
|
+
line =~ /^>/
|
445
|
+
end
|
446
|
+
|
447
|
+
assert_equal ">abc\r\n", aio[0]
|
448
|
+
assert_equal ">def\r\n", aio[1]
|
449
|
+
assert_equal ">gh", aio[2]
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
def test_reindex_speed
|
454
|
+
benchmark_test do
|
455
|
+
n = 10000
|
456
|
+
filepath = aio_filepath("reindex_speed.txt")
|
457
|
+
|
458
|
+
bm(12) do |x|
|
459
|
+
x.report("building file") do
|
460
|
+
File.open(filepath, 'w') do |file|
|
461
|
+
0.upto(n-1) {|i| file << "line #{i}\n" }
|
462
|
+
end
|
463
|
+
end
|
464
|
+
puts " #{n} lines"
|
465
|
+
|
466
|
+
begin
|
467
|
+
aio = ArrayIO.new(filepath, 'r')
|
468
|
+
uaio = ArrayIO.new(filepath, 'ru')
|
469
|
+
|
470
|
+
x.report("reindex") { aio.reindex }
|
471
|
+
assert_equal n, aio.length, 'aio'
|
472
|
+
assert_equal "line 1000\r\n", aio[1000], 'aio'
|
473
|
+
|
474
|
+
x.report("ureindex") { uaio.reindex}
|
475
|
+
assert_equal n, uaio.length, 'uaio'
|
476
|
+
assert_equal "line 1000\r\n", uaio[1000], 'uaio'
|
477
|
+
ensure
|
478
|
+
aio.close
|
479
|
+
uaio.close
|
480
|
+
index_path = uaio.io_index.path
|
481
|
+
File.delete(index_path) if index_path && File.exists?(index_path)
|
482
|
+
File.delete(filepath)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
#
|
489
|
+
# array behavior tests
|
490
|
+
#
|
491
|
+
|
492
|
+
def array_test_setup
|
493
|
+
[setup_aio, ["abc", "de", "fgh"]]
|
494
|
+
end
|
495
|
+
|
496
|
+
def test_length_and_size
|
497
|
+
aio, array = array_test_setup
|
498
|
+
|
499
|
+
assert_equal array.length, aio.length
|
500
|
+
assert_equal array.size, aio.size
|
501
|
+
end
|
502
|
+
|
503
|
+
def test_fetch
|
504
|
+
aio, array = array_test_setup
|
505
|
+
|
506
|
+
assert_equal array.fetch(0), aio.fetch(0)
|
507
|
+
assert_equal array.fetch(0, "default"), aio.fetch(0, "default")
|
508
|
+
assert_equal array.fetch(3, "default"), aio.fetch(3, "default")
|
509
|
+
|
510
|
+
array.fetch(1) do |arr|
|
511
|
+
aio.fetch(1) do |a|
|
512
|
+
assert_equal arr, a
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def test_first
|
518
|
+
aio, array = array_test_setup
|
519
|
+
|
520
|
+
assert_equal array.first, aio.first
|
521
|
+
assert_equal array.first(2), aio.first(2)
|
522
|
+
assert_equal array.first(10), aio.first(10)
|
523
|
+
end
|
524
|
+
|
525
|
+
def test_each_index
|
526
|
+
aio, array = array_test_setup
|
527
|
+
|
528
|
+
aio_result = []
|
529
|
+
aio.each_index {|i| aio_result << i}
|
530
|
+
array_result = []
|
531
|
+
array.each_index {|i| array_result << i}
|
532
|
+
|
533
|
+
assert_equal array_result, aio_result
|
534
|
+
end
|
535
|
+
|
536
|
+
def test_each
|
537
|
+
aio, array = array_test_setup
|
538
|
+
|
539
|
+
aio_result = []
|
540
|
+
aio.each {|i| aio_result << i}
|
541
|
+
array_result = []
|
542
|
+
array.each {|i| array_result << i}
|
543
|
+
|
544
|
+
assert_equal array_result, aio_result
|
545
|
+
end
|
546
|
+
|
547
|
+
def test_empty
|
548
|
+
aio, array = array_test_setup
|
549
|
+
|
550
|
+
assert_equal array.empty?, aio.empty?
|
551
|
+
assert_equal [].empty?, ArrayIO.new('', 'rs', []).empty?
|
552
|
+
end
|
553
|
+
|
554
|
+
def test_last
|
555
|
+
aio, array = array_test_setup
|
556
|
+
|
557
|
+
assert_equal array.last, aio.last
|
558
|
+
assert_equal array.last(2), aio.last(2)
|
559
|
+
assert_equal array.last(10), aio.last(10)
|
560
|
+
end
|
561
|
+
|
562
|
+
def btest_values_at
|
563
|
+
aio, array = array_test_setup
|
564
|
+
|
565
|
+
assert_equal array.values_at, aio.values_at
|
566
|
+
assert_equal array.values_at(2), aio.values_at(2)
|
567
|
+
assert_equal array.values_at(1, 10, 3..3, 1..2), aio.values_at(1, 10, 3..3, 1..2)
|
568
|
+
end
|
569
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'array_io'
|
3
|
+
require 'benchmark'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
# The testing subset code here is taken from an as-yet unreleased gem 'prosperity'
|
7
|
+
#
|
8
|
+
# These subsets facilitate testing by using the ENV variables specified on the command line
|
9
|
+
# to indicate which tests to run. The ENV variables are set by rake, so this code implicitly
|
10
|
+
# assumes that you're running your tests through rake.
|
11
|
+
#
|
12
|
+
class Test::Unit::TestCase
|
13
|
+
def run_subset?(type)
|
14
|
+
ENV[type] == "true" || ENV["ALL"] == "true"
|
15
|
+
end
|
16
|
+
|
17
|
+
def match_regexp?(type, obj, default=true)
|
18
|
+
return default if ENV["ALL"] == "true"
|
19
|
+
return default unless ENV[type]
|
20
|
+
|
21
|
+
str = ""
|
22
|
+
PP.singleline_pp(obj, str)
|
23
|
+
str =~ Regexp.new(ENV[type])
|
24
|
+
end
|
25
|
+
|
26
|
+
def extended_test(&block)
|
27
|
+
subset_test("EXTENDED", "x", &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def benchmark_test(&block)
|
31
|
+
subset_test("BENCHMARK", "b") do
|
32
|
+
puts calling_method
|
33
|
+
block.call
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def case_test(hash, &block)
|
38
|
+
if match_regexp?("CASE_TEST", calling_method)
|
39
|
+
hash.each_pair do |testcase, expected|
|
40
|
+
yield(testcase, expected) if match_regexp?("CASE", testcase)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Calling method iterates over the call stack, and returns the first calling
|
48
|
+
# method name that matches the input pattern (by default /^test/)
|
49
|
+
def calling_method(pattern=/^test/)
|
50
|
+
0.upto(caller.length) do |i|
|
51
|
+
caller[i] =~ /:in `(.*)'$/
|
52
|
+
method_name = $1
|
53
|
+
return method_name if method_name =~ pattern
|
54
|
+
end
|
55
|
+
|
56
|
+
''
|
57
|
+
end
|
58
|
+
|
59
|
+
def subset_test(type, skip, &block)
|
60
|
+
type = type.upcase
|
61
|
+
type_test = "#{type}_TEST"
|
62
|
+
if run_subset?(type) || ENV[type_test]
|
63
|
+
if match_regexp?(type_test, calling_method)
|
64
|
+
block.call
|
65
|
+
else
|
66
|
+
print skip
|
67
|
+
end
|
68
|
+
else
|
69
|
+
print skip
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: arrayio
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-03-15 00:00:00 -06:00
|
8
|
+
summary: Array-like behavior for archival files.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: simon.chiang@uchsc.edu
|
12
|
+
homepage: http://rubyforge.org/projects/arrayio/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: arrayio
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Simon Chiang
|
31
|
+
files:
|
32
|
+
- test/arrayio_test_helper.rb
|
33
|
+
- test/arrayio_test_suite.rb
|
34
|
+
- test/array_io
|
35
|
+
- test/array_io_test.rb
|
36
|
+
- test/array_io/alt_sep.txt
|
37
|
+
- test/array_io/cr_lf_input.txt
|
38
|
+
- test/array_io/input.index
|
39
|
+
- test/array_io/input.txt
|
40
|
+
- test/array_io/inputb.index
|
41
|
+
- test/array_io/inputb.txt
|
42
|
+
- test/array_io/lf_input.txt
|
43
|
+
- test/array_io/lines.txt
|
44
|
+
- test/array_io/without_index.txt
|
45
|
+
- lib/array_io.rb
|
46
|
+
- lib/inspect_array_io.rb
|
47
|
+
- README
|
48
|
+
test_files:
|
49
|
+
- test/arrayio_test_suite.rb
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
extra_rdoc_files:
|
53
|
+
- README
|
54
|
+
executables: []
|
55
|
+
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
dependencies: []
|
61
|
+
|