ferret 0.1.3 → 0.1.4

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 (57) hide show
  1. data/Rakefile +1 -1
  2. data/TODO +3 -0
  3. data/ext/dummy.exe +0 -0
  4. data/lib/ferret.rb +1 -1
  5. data/lib/ferret/analysis/token.rb +6 -0
  6. data/lib/ferret/analysis/tokenizers.rb +5 -5
  7. data/lib/ferret/document/document.rb +10 -13
  8. data/lib/ferret/index/compound_file_io.rb +12 -9
  9. data/lib/ferret/index/field_infos.rb +0 -6
  10. data/lib/ferret/index/index.rb +220 -102
  11. data/lib/ferret/index/index_reader.rb +22 -2
  12. data/lib/ferret/index/index_writer.rb +55 -14
  13. data/lib/ferret/index/multi_reader.rb +279 -279
  14. data/lib/ferret/index/segment_infos.rb +3 -3
  15. data/lib/ferret/index/segment_merger.rb +7 -6
  16. data/lib/ferret/index/segment_reader.rb +23 -7
  17. data/lib/ferret/index/segment_term_enum.rb +6 -7
  18. data/lib/ferret/index/term_buffer.rb +3 -5
  19. data/lib/ferret/index/term_doc_enum.rb +7 -2
  20. data/lib/ferret/index/term_infos_io.rb +15 -8
  21. data/lib/ferret/query_parser/query_parser.tab.rb +49 -45
  22. data/lib/ferret/search/boolean_query.rb +3 -4
  23. data/lib/ferret/search/boolean_scorer.rb +11 -11
  24. data/lib/ferret/search/caching_wrapper_filter.rb +1 -1
  25. data/lib/ferret/search/disjunction_sum_scorer.rb +9 -7
  26. data/lib/ferret/search/field_cache.rb +1 -2
  27. data/lib/ferret/search/field_sorted_hit_queue.rb +1 -1
  28. data/lib/ferret/search/fuzzy_term_enum.rb +64 -58
  29. data/lib/ferret/search/index_searcher.rb +16 -9
  30. data/lib/ferret/search/prefix_query.rb +7 -0
  31. data/lib/ferret/search/query_filter.rb +1 -1
  32. data/lib/ferret/search/term_scorer.rb +5 -1
  33. data/lib/ferret/search/top_docs.rb +12 -0
  34. data/lib/ferret/store/buffered_index_io.rb +5 -6
  35. data/lib/ferret/store/fs_store.rb +47 -33
  36. data/lib/ferret/store/ram_store.rb +2 -2
  37. data/lib/ferret/utils.rb +1 -0
  38. data/lib/ferret/utils/bit_vector.rb +20 -2
  39. data/lib/ferret/utils/thread_local.rb +28 -0
  40. data/lib/ferret/utils/weak_key_hash.rb +11 -2
  41. data/test/benchmark/tb_rw_vint.rb +1 -1
  42. data/test/functional/thread_safety_index_test.rb +81 -0
  43. data/test/functional/thread_safety_test.rb +137 -0
  44. data/test/test_all.rb +3 -7
  45. data/test/test_helper.rb +2 -1
  46. data/test/unit/index/tc_compound_file_io.rb +2 -2
  47. data/test/unit/index/tc_index.rb +128 -6
  48. data/test/unit/index/tc_index_reader.rb +1 -1
  49. data/test/unit/index/tc_segment_infos.rb +1 -1
  50. data/test/unit/index/th_doc.rb +1 -1
  51. data/test/unit/search/tc_index_searcher.rb +6 -0
  52. data/test/unit/store/tc_fs_store.rb +3 -3
  53. data/test/unit/utils/tc_bit_vector.rb +8 -0
  54. data/test/unit/utils/tc_thread.rb +61 -0
  55. data/test/unit/utils/tc_weak_key_hash.rb +2 -2
  56. data/test/utils/number_to_spoken.rb +132 -0
  57. metadata +7 -2
@@ -99,7 +99,7 @@ module Ferret::Store
99
99
 
100
100
  def to_s
101
101
  str = "The files in this directory are: \n"
102
- @files.each do |path,file|
102
+ @files.each do |path, file|
103
103
  str << path + " - " + file.size.to_s + "\n"
104
104
  end
105
105
  str
@@ -230,12 +230,12 @@ module Ferret::Store
230
230
  attr_accessor :mtime
231
231
  #attr_accessor :name
232
232
  attr_accessor :length
233
+ alias :size :length
233
234
 
234
235
 
235
236
  def initialize(name)
236
237
  @buffers = Array.new
237
238
  @mtime = Time.now
238
- #@name = name
239
239
  @length = 0
240
240
  end
241
241
  end
data/lib/ferret/utils.rb CHANGED
@@ -5,3 +5,4 @@ require 'ferret/utils/bit_vector'
5
5
  require 'ferret/utils/date_tools'
6
6
  require 'ferret/utils/number_tools'
7
7
  require 'ferret/utils/weak_key_hash'
8
+ require 'ferret/utils/thread_local'
@@ -74,7 +74,7 @@ module Ferret::Utils
74
74
  def write(d, name)
75
75
  output = d.create_output(name)
76
76
  begin
77
- output.write_vint(@bits)
77
+ output.write_string(self.class.bignum_to_string(@bits))
78
78
  ensure
79
79
  output.close()
80
80
  end
@@ -86,7 +86,7 @@ module Ferret::Utils
86
86
  bv = BitVector.new
87
87
  input = d.open_input(name)
88
88
  begin
89
- bv.bits = input.read_vint()
89
+ bv.bits = string_to_bignum(input.read_string())
90
90
  ensure
91
91
  input.close()
92
92
  end
@@ -101,5 +101,23 @@ module Ferret::Utils
101
101
  end
102
102
  puts ""
103
103
  end
104
+
105
+ # converts a BigNum into a string
106
+ def BitVector.bignum_to_string(num)
107
+ str = []
108
+ while (num > 0)
109
+ str << (num & 0xff)
110
+ num >>= 8
111
+ end
112
+ return str.pack("C*")
113
+ end
114
+
115
+ # converts a string into a bignum
116
+ def BitVector.string_to_bignum(str)
117
+ str = str.unpack("C*")
118
+ num = 0
119
+ str.reverse.each {|c| num = ((num << 8) | c) }
120
+ return num
121
+ end
104
122
  end
105
123
  end
@@ -0,0 +1,28 @@
1
+ require 'thread'
2
+ class Thread
3
+ def make_deleter
4
+ lambda{|id| @ferret_cache.delete(id)}
5
+ end
6
+
7
+ # Set the local value for the thread
8
+ def set_local(key, value)
9
+ @del ||= make_deleter
10
+ @ferret_cache ||= {}
11
+ ObjectSpace.define_finalizer(key, @del)
12
+ @ferret_cache[key.object_id] = value
13
+ end
14
+
15
+ # Get the local value for the thread
16
+ def get_local(key)
17
+ return (@ferret_cache ||= {})[key.object_id]
18
+ end
19
+
20
+ # Returns the number of local variables stored. Useful for testing.
21
+ def local_size
22
+ return (@ferret_cache ||= {}).size
23
+ end
24
+
25
+ def clear_local
26
+ (@ferret_cache ||= {}).clear
27
+ end
28
+ end
@@ -1,6 +1,7 @@
1
1
  module Ferret::Utils
2
2
 
3
3
  require 'weakref'
4
+ require 'monitor'
4
5
 
5
6
  # This class implements a weak key hash. ie all keys that are stored in this
6
7
  # hash can still be garbage collected, and if they are garbage collected
@@ -18,29 +19,37 @@ module Ferret::Utils
18
19
  # # have been removed from the hash
19
20
  # puts last_names["david"] #=>nil
20
21
  #
21
- # ===NOTE
22
+ # WeakKeyHash subclasses Monitor so it can be synchronized on.
23
+ #
24
+ # === NOTE
22
25
  # Unfortunately the ruby garbage collector is not always predictable so your
23
26
  # results may differ but each key should eventually be freed when all other
24
27
  # references have been removed and the garbage collector is ready.
25
- class WeakKeyHash
28
+ class WeakKeyHash < Monitor
29
+ # Create a new WeakKeyHash.
26
30
  def initialize
31
+ super()
27
32
  @hash = {}
28
33
  @deleter = lambda{|id| @hash.delete(id)}
29
34
  end
30
35
 
36
+ # Set the value for the key just like a Hash
31
37
  def []=(key, value)
32
38
  ObjectSpace.define_finalizer(key, @deleter)
33
39
  @hash[key.object_id] = value
34
40
  end
35
41
 
42
+ # Get the value for the key
36
43
  def [](key)
37
44
  return @hash[key.object_id]
38
45
  end
39
46
 
47
+ # Return the number of elements in the Hash
40
48
  def size
41
49
  @hash.size
42
50
  end
43
51
 
52
+ # Print a string representation the WeakKeyHash
44
53
  def to_s
45
54
  buffer = ""
46
55
  @hash.each_pair {|key, value| buffer << "<#{ObjectSpace._id2ref(key)}=>#{value}>"}
@@ -9,7 +9,7 @@ t = Time.new
9
9
  10.times do
10
10
  dpath = File.join(File.dirname(__FILE__),
11
11
  'fsdir')
12
- dir = Ferret::Store::FSDirectory.get_directory(dpath, true)
12
+ dir = Ferret::Store::FSDirectory.new(dpath, true)
13
13
 
14
14
  100.times do
15
15
  ostream = dir.create_output("rw_vint.test")
@@ -0,0 +1,81 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+ require File.dirname(__FILE__) + "/../utils/number_to_spoken.rb"
3
+ require 'thread'
4
+
5
+ class IndexThreadSafetyTest < Test::Unit::TestCase
6
+ include Ferret::Index
7
+ include Ferret::Document
8
+
9
+ INDEX_DIR = File.expand_path(File.join(File.dirname(__FILE__), "index"))
10
+ ITERATIONS = 100000
11
+ NUM_THREADS = 2
12
+ ANALYZER = Ferret::Analysis::Analyzer.new()
13
+
14
+ def setup
15
+ @index = Index.new(:path => 'index2',
16
+ :create => true,
17
+ :analyzer => ANALYZER,
18
+ :default_field => 'contents')
19
+ end
20
+
21
+ def indexing_thread()
22
+ ITERATIONS.times do
23
+ choice = rand()
24
+
25
+ if choice > 0.98
26
+ do_optimize
27
+ elsif choice > 0.9
28
+ do_delete_doc
29
+ elsif choice > 0.7
30
+ do_search
31
+ else
32
+ do_add_doc
33
+ end
34
+ end
35
+ rescue => e
36
+ puts e
37
+ puts e.backtrace
38
+ @index = nil
39
+ raise e
40
+ end
41
+
42
+ def do_optimize
43
+ puts "Optimizing the index"
44
+ @index.optimize
45
+ end
46
+
47
+ def do_delete_doc
48
+ return if @index.size == 0
49
+ doc_num = rand(@index.size)
50
+ puts "Deleting #{doc_num} from index which has#{@index.has_deletions? ? "" : " no"} deletions"
51
+ puts "document was already deleted" if (@index.deleted?(doc_num))
52
+ @index.delete(doc_num)
53
+ end
54
+
55
+ def do_add_doc
56
+ d = Document.new()
57
+ n = rand(0xFFFFFFFF)
58
+ d << Field.new("id", n.to_s, Field::Store::YES, Field::Index::UNTOKENIZED)
59
+ d << Field.new("contents", n.to_spoken, Field::Store::NO, Field::Index::TOKENIZED)
60
+ puts("Adding #{n}")
61
+ @index << d
62
+ end
63
+
64
+ def do_search
65
+ n = rand(0xFFFFFFFF)
66
+ puts("Searching for #{n}")
67
+ hits = @index.search_each(n.to_spoken, :num_docs => 3) do |d, s|
68
+ puts "Hit for #{n}: #{@index[d]["id"]} - #{s}"
69
+ end
70
+ puts("Searched for #{n}: total = #{hits}")
71
+ end
72
+
73
+ def test_threading
74
+ threads = []
75
+ NUM_THREADS.times do
76
+ threads << Thread.new { indexing_thread }
77
+ end
78
+
79
+ threads.each {|t| t.join}
80
+ end
81
+ end
@@ -0,0 +1,137 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+ require File.dirname(__FILE__) + "/../utils/number_to_spoken.rb"
3
+ require 'thread'
4
+
5
+ class ThreadSafetyTest
6
+ include Ferret::Index
7
+ include Ferret::Search
8
+ include Ferret::Store
9
+ include Ferret::Document
10
+
11
+ def initialize(options)
12
+ @options = options
13
+ end
14
+
15
+ INDEX_DIR = File.expand_path(File.join(File.dirname(__FILE__), "index"))
16
+ ANALYZER = Ferret::Analysis::Analyzer.new()
17
+ ITERATIONS = 19
18
+ @@searcher = nil
19
+
20
+ def run_index_thread(writer)
21
+ reopen_interval = 30 + rand(60)
22
+
23
+ use_compound_file = false
24
+
25
+ (400*ITERATIONS).times do |i|
26
+ d = Document.new()
27
+ n = rand(0xFFFFFFFF)
28
+ d << Field.new("id", n.to_s, Field::Store::YES, Field::Index::UNTOKENIZED)
29
+ d << Field.new("contents", n.to_spoken, Field::Store::NO, Field::Index::TOKENIZED)
30
+ puts("Adding #{n}")
31
+
32
+ # Switch between single and multiple file segments
33
+ use_compound_file = (rand < 0.5)
34
+ writer.use_compound_file = use_compound_file
35
+
36
+ writer << d
37
+
38
+ if (i % reopen_interval == 0)
39
+ writer.close()
40
+ writer = IndexWriter.new(INDEX_DIR, :analyzer => ANALYZER)
41
+ end
42
+ end
43
+
44
+ writer.close()
45
+ rescue => e
46
+ puts e
47
+ puts e.backtrace
48
+ raise e
49
+ end
50
+
51
+ def run_search_thread(use_global)
52
+ reopen_interval = 10 + rand(20)
53
+
54
+ unless use_global
55
+ searcher = IndexSearcher.new(INDEX_DIR)
56
+ end
57
+
58
+ (50*ITERATIONS).times do |i|
59
+ search_for(rand(0xFFFFFFFF), (searcher.nil? ? @@searcher : searcher))
60
+ if (i%reopen_interval == 0)
61
+ if (searcher == nil)
62
+ @@searcher = IndexSearcher.new(INDEX_DIR)
63
+ else
64
+ searcher.close()
65
+ searcher = IndexSearcher.new(INDEX_DIR)
66
+ end
67
+ end
68
+ end
69
+ rescue => e
70
+ puts e
71
+ puts e.backtrace
72
+ raise e
73
+ end
74
+
75
+ def search_for(n, searcher)
76
+ puts("Searching for #{n}")
77
+ hits =
78
+ searcher.search(Ferret::QueryParser.parse(n.to_spoken, "contents", :analyzer => ANALYZER),
79
+ :num_docs => 3)
80
+ puts("Search for #{n}: total = #{hits.size}")
81
+ hits.each do |d, s|
82
+ puts "Hit for #{n}: #{searcher.reader.get_document(d)["id"]} - #{s}"
83
+ end
84
+ end
85
+
86
+ def run_test_threads
87
+
88
+ threads = []
89
+ unless @options[:read_only]
90
+ writer = IndexWriter.new(INDEX_DIR, :analyzer => ANALYZER,
91
+ :create => !@options[:add])
92
+
93
+ threads << Thread.new { run_index_thread(writer) }
94
+
95
+ sleep(1)
96
+ end
97
+
98
+ threads << Thread.new { run_search_thread(false)}
99
+
100
+ @@searcher = IndexSearcher.new(INDEX_DIR)
101
+ threads << Thread.new { run_search_thread(true)}
102
+
103
+ threads << Thread.new { run_search_thread(true)}
104
+
105
+ threads.each {|t| t.join}
106
+ end
107
+ end
108
+
109
+
110
+ if $0 == __FILE__
111
+ require 'optparse'
112
+
113
+ OPTIONS = {
114
+ :all => false,
115
+ :read_only => false,
116
+ }
117
+
118
+ ARGV.options do |opts|
119
+ script_name = File.basename($0)
120
+ opts.banner = "Usage: ruby #{script_name} [options]"
121
+
122
+ opts.separator ""
123
+
124
+ opts.on("-r", "--read-only", "Read Only.") { OPTIONS[:all] = true }
125
+ opts.on("-a", "--all", "All.") { OPTIONS[:read_only] = true }
126
+
127
+ opts.separator ""
128
+
129
+ opts.on("-h", "--help",
130
+ "Show this help message.") { puts opts; exit }
131
+
132
+ opts.parse!
133
+ end
134
+
135
+ tst = ThreadSafetyTest.new(OPTIONS)
136
+ tst.run_test_threads
137
+ end
data/test/test_all.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
+ require 'test_helper.rb'
2
3
 
3
- require 'unit/ts_analysis.rb'
4
- require 'unit/ts_document.rb'
5
- require 'unit/ts_index.rb'
6
- require 'unit/ts_query_parser.rb'
7
- require 'unit/ts_search.rb'
8
- require 'unit/ts_store.rb'
9
- require 'unit/ts_utils.rb'
4
+ load_test_dir("unit")
5
+ #load_test_dir("functional")
data/test/test_helper.rb CHANGED
@@ -1,9 +1,10 @@
1
+ $:.unshift File.dirname(__FILE__)
1
2
  $:.unshift File.join(File.dirname(__FILE__), '../lib')
2
3
  $:.unshift File.join(File.dirname(__FILE__), '../ext')
3
4
 
4
5
  require 'test/unit'
5
6
  require 'ferret'
6
- require 'test/unit/index/th_doc'
7
+ require 'unit/index/th_doc'
7
8
 
8
9
  def load_test_dir(dir)
9
10
  dir = File.join(File.dirname(__FILE__), dir)
@@ -60,8 +60,8 @@ class CompoundFileReaderTest < Test::Unit::TestCase
60
60
  cfile.close()
61
61
 
62
62
  cfile_reader = CompoundFileReader.new(@dir, "cfile")
63
- assert_equal(4, cfile_reader.file_length('file1'))
64
- assert_equal(15, cfile_reader.file_length('file2'))
63
+ assert_equal(4, cfile_reader.length('file1'))
64
+ assert_equal(15, cfile_reader.length('file2'))
65
65
  file1 = cfile_reader.open_input('file1')
66
66
  file2 = cfile_reader.open_input('file2')
67
67
  assert_equal(20, file1.read_int())
@@ -4,6 +4,7 @@ require File.dirname(__FILE__) + "/../../test_helper"
4
4
  class IndexTest < Test::Unit::TestCase
5
5
  include Ferret::Index
6
6
  include Ferret::Analysis
7
+ include Ferret::Store
7
8
 
8
9
  def setup()
9
10
  @qp = Ferret::QueryParser.new()
@@ -134,12 +135,12 @@ class IndexTest < Test::Unit::TestCase
134
135
  end
135
136
 
136
137
  def test_fs_index
137
- fs_path = File.join(File.dirname(__FILE__), '../../temp/fsdir')
138
- `rm -rf #{fs_path}`
138
+ fs_path = File.expand_path(File.join(File.dirname(__FILE__), '../../temp/fsdir'))
139
+ `rm -rf #{File.join(fs_path, "*")}`
139
140
  assert_raise(Errno::ENOENT) {Index.new(:path => fs_path, :create_if_missing => false, :default_field => "def_field")}
140
141
  index = Index.new(:path => fs_path, :default_field => "def_field")
141
142
  do_test_index_with_array(index)
142
- `rm -rf #{fs_path}`
143
+ `rm -rf #{File.join(fs_path, "*")}`
143
144
  index = Index.new(:path => fs_path, :create => true, :default_field => "def_field")
144
145
  do_test_index_with_hash(index)
145
146
  index = Index.new(:path => fs_path, :create => true, :default_field => "def_field")
@@ -147,8 +148,8 @@ class IndexTest < Test::Unit::TestCase
147
148
  end
148
149
 
149
150
  def test_fs_index_is_persistant
150
- fs_path = File.join(File.dirname(__FILE__), '../../temp/fsdir')
151
- `rm -rf #{fs_path}`
151
+ fs_path = File.expand_path(File.join(File.dirname(__FILE__), '../../temp/fsdir'))
152
+ `rm -rf #{File.join(fs_path, "*")}`
152
153
  data = [
153
154
  {"def_field" => "one two", :id => "me"},
154
155
  {"def_field" => "one", :field2 => "three"},
@@ -163,8 +164,129 @@ class IndexTest < Test::Unit::TestCase
163
164
  data.each {|doc| index << doc }
164
165
  assert_equal(8, index.size)
165
166
  index.close
166
- index = Index.new(:path => fs_path, :default_field => "def_field")
167
+ index = Index.new(:path => fs_path, :create_if_missing => false)
167
168
  assert_equal(8, index.size)
168
169
  assert_equal("four", index[5]["field3"])
169
170
  end
171
+
172
+ def test_merging_indexes
173
+ data = [
174
+ {"f" => "zero"},
175
+ {"f" => "one"},
176
+ {"f" => "two"}
177
+ ]
178
+ index1 = Index.new(:default_field => "f")
179
+ data.each {|doc| index1 << doc }
180
+ data = [
181
+ {"f" => "three"},
182
+ {"f" => "four"},
183
+ {"f" => "five"}
184
+ ]
185
+ index2 = Index.new(:default_field => "f")
186
+ data.each {|doc| index2 << doc }
187
+ data = [
188
+ {"f" => "six"},
189
+ {"f" => "seven"},
190
+ {"f" => "eight"}
191
+ ]
192
+ index3 = Index.new(:default_field => "f")
193
+ data.each {|doc| index3 << doc }
194
+
195
+ index = Index.new(:default_field => "f")
196
+ index.add_indexes(index1)
197
+ assert_equal(3, index.size)
198
+ assert_equal("zero", index[0]["f"])
199
+ index.add_indexes([index2, index3])
200
+ assert_equal(9, index.size)
201
+ assert_equal("zero", index[0]["f"])
202
+ assert_equal("eight", index[8]["f"])
203
+ index1.close
204
+ index2.close
205
+ index3.close
206
+ assert_equal("seven", index[7]["f"])
207
+ data = [
208
+ {"f" => "alpha"},
209
+ {"f" => "beta"},
210
+ {"f" => "charlie"}
211
+ ]
212
+ dir1 = RAMDirectory.new
213
+ index1 = Index.new(:dir => dir1, :default_field => "f")
214
+ data.each {|doc| index1 << doc }
215
+ index1.flush
216
+ data = [
217
+ {"f" => "delta"},
218
+ {"f" => "echo"},
219
+ {"f" => "foxtrot"}
220
+ ]
221
+ dir2 = RAMDirectory.new
222
+ index2 = Index.new(:dir => dir2, :default_field => "f")
223
+ data.each {|doc| index2 << doc }
224
+ index2.flush
225
+ data = [
226
+ {"f" => "golf"},
227
+ {"f" => "india"},
228
+ {"f" => "juliet"}
229
+ ]
230
+ dir3 = RAMDirectory.new
231
+ index3 = Index.new(:dir => dir3, :default_field => "f")
232
+ data.each {|doc| index3 << doc }
233
+ index3.flush
234
+
235
+ index.add_indexes(dir1)
236
+ assert_equal(12, index.size)
237
+ assert_equal("alpha", index[9]["f"])
238
+ index.add_indexes([dir2, dir3])
239
+ assert_equal(18, index.size)
240
+ assert_equal("juliet", index[17]["f"])
241
+ index1.close
242
+ dir1.close
243
+ index2.close
244
+ dir2.close
245
+ index3.close
246
+ dir3.close
247
+ assert_equal("golf", index[15]["f"])
248
+ end
249
+
250
+ def test_persist_index
251
+ data = [
252
+ {"f" => "zero"},
253
+ {"f" => "one"},
254
+ {"f" => "two"}
255
+ ]
256
+ index = Index.new(:default_field => "f")
257
+ data.each {|doc| index << doc }
258
+ fs_path = File.expand_path(File.join(File.dirname(__FILE__), '../../temp/fsdir'))
259
+ index.persist(fs_path, true)
260
+ assert_equal(3, index.size)
261
+ assert_equal("zero", index[0]["f"])
262
+ index.close
263
+
264
+ index = Index.new(:path => fs_path)
265
+ assert_equal(3, index.size)
266
+ assert_equal("zero", index[0]["f"])
267
+ index.close
268
+
269
+
270
+ data = [
271
+ {"f" => "romeo"},
272
+ {"f" => "sierra"},
273
+ {"f" => "tango"}
274
+ ]
275
+ index = Index.new(:default_field => "f")
276
+ data.each {|doc| index << doc }
277
+ assert_equal(3, index.size)
278
+ assert_equal("romeo", index[0]["f"])
279
+ dir = FSDirectory.new(fs_path, false)
280
+ index.persist(dir)
281
+ assert_equal(6, index.size)
282
+ assert_equal("zero", index[0]["f"])
283
+ assert_equal("romeo", index[3]["f"])
284
+ index.close
285
+
286
+ index = Index.new(:path => fs_path)
287
+ assert_equal(6, index.size)
288
+ assert_equal("zero", index[0]["f"])
289
+ assert_equal("romeo", index[3]["f"])
290
+ index.close
291
+ end
170
292
  end