ferret 0.1.3 → 0.1.4

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