1require 'tempfile'
2require 'fileutils'
3require 'yaml'
4require 'uconv'
5
6$KCODE = "u"
7
8module TomoeTestUtils
9  def self.included(base)
10    base.class_eval do
11      include Base
12      include Path
13      include Config
14      include Dictionary
15      include Unicode
16      include Assertions
17    end
18  end
19
20  module Base
21    def setup
22      super
23    end
24
25    def teardown
26      super
27    end
28  end
29
30  module Path
31    module_function
32    def base_dir
33      File.expand_path(File.dirname(__FILE__))
34    end
35
36    def tmp_dir
37      File.join(base_dir, "tmp")
38    end
39
40    def top_dir
41      File.expand_path(File.join(base_dir, ".."))
42    end
43
44    def data_dir
45      File.join(top_dir, "data")
46    end
47
48    def test_data_dir
49      File.join(base_dir, "data")
50    end
51
52    def module_dir
53      File.join(top_dir, "module")
54    end
55
56    def recognizer_dir
57      File.join(module_dir, "recognizer", ".libs")
58    end
59
60    def dict_dir
61      File.join(module_dir, "dict", ".libs")
62    end
63
64    def db_dir
65      File.join(top_dir, "db")
66    end
67
68    def db_config_file
69      File.join(db_dir, "config.yml")
70    end
71
72    def test_data_files
73      Dir.glob(File.join(test_data_dir, "*.data"))
74    end
75
76    def dictionary
77      File.join(data_dir, "kanjidic2.xml")
78    end
79  end
80
81  module Config
82    extend Path
83
84    module_function
85    def db_config_for_active_record(type=nil)
86      YAML.load(File.read(db_config_file))[type || ENV["TOMOE_ENV"] || "test"]
87    end
88
89    def db_config(type=nil)
90      config = db_config_for_active_record(type)
91      config.delete("adapter")
92      config.delete("encoding")
93      config["user"] = config.delete("username") if config["username"]
94      config
95    end
96
97    def setup
98      super
99      FileUtils.mkdir_p(tmp_dir)
100      @config_file = make_config_file
101    end
102
103    def teardown
104      super
105      FileUtils.rm_rf(tmp_dir)
106    end
107
108    def dict_module_type
109      ENV["TOMOE_DICT_MODULE"] || "xml"
110    end
111
112    def make_config_file(dict_type=nil)
113      dict_type ||= dict_module_type
114      name ||= "tomoe-#{dict_type}"
115      config_file = Tempfile.new(name)
116      config_file.open
117      config_file.puts(<<-EOC)
118[config]
119use-system-dictionaries = false
120EOC
121
122      config_maker = "make_config_file_for_#{dict_type}"
123      unless respond_to?(config_maker, true)
124        raise "unknown dictionary type: #{dict_type}"
125      end
126      config_file.puts(send(config_maker))
127
128      config_file.close
129      config_file
130    end
131
132    def make_config_file_for_unihan
133      <<-EOC
134[unihan-dictionary]
135type = unihan
136EOC
137    end
138
139    def est_db
140      File.join(tmp_dir, File.basename(dictionary).sub(/\.xml$/, ''))
141    end
142
143    def ensure_dict_est
144      unless File.exists?(est_db)
145        tmp_est_db = "#{est_db}.tmp"
146        FileUtils.rm_rf(tmp_est_db)
147        xml_dict = Tomoe::DictXML.new("filename" => dictionary,
148                                      "editable" => false)
149        est_dict = Tomoe::DictEst.new("database" => tmp_est_db,
150                                      "editable" => true)
151        xml_dict.search(Tomoe::Query.new).each_with_index do |cand, i|
152          est_dict.register(cand.char)
153        end
154        est_dict.flush
155        FileUtils.cp_r(tmp_est_db, est_db)
156      end
157    end
158
159    def ensure_dict_mysql
160      sql_purge("test")
161      xml_dict = Tomoe::DictXML.new("filename" => dictionary,
162                                    "editable" => false)
163      mysql_dict = Tomoe::DictMySQL.new(db_config("test"))
164      xml_dict.search(Tomoe::Query.new).each_with_index do |cand, i|
165        mysql_dict.register(cand.char)
166      end
167    end
168
169    def make_config_file_for_est
170      dict_basename = File.basename(dictionary).sub(/\.xml$/, '')
171      <<-EOC
172[#{dict_basename}-dictionary]
173type = est
174name = #{dict_basename}
175database = #{est_db}
176EOC
177    end
178
179    def make_config_file_for_xml
180      <<-EOC
181[#{File.basename(dictionary)}-dictionary]
182type = xml
183file = #{dictionary}
184EOC
185    end
186
187    def make_config_file_for_mysql
188      config = <<-EOC
189[mysql-dictionary]
190type = mysql
191EOC
192      db_config.each do |key, value|
193        config << "#{key} = #{value}\n"
194      end
195      config
196    end
197  end
198
199  module Dictionary
200    module_function
201    def make_dict(dict_type=nil, config=nil)
202      dict_type ||= dict_module_type
203      dict_maker = "make_dict_#{dict_type}"
204      unless respond_to?(dict_maker, true)
205        raise "unknown dictionary type: #{dict_type}"
206      end
207      send(dict_maker, config)
208    end
209
210    def make_temporary_dict(original, dict_type=nil, config=nil, &block)
211      dict_type ||= dict_module_type
212      temporary_dict_maker = "make_temporary_dict_#{dict_type}"
213      unless respond_to?(temporary_dict_maker, true)
214        raise "unknown dictionary type: #{dict_type}"
215      end
216      send(temporary_dict_maker, config) do |dict|
217        original.search(Tomoe::Query.new).each do |cand|
218          dict.register(cand.char)
219        end
220        block.call(dict)
221      end
222    end
223
224    def make_dict_unihan(config=nil)
225      check_dict_module_availability("Unihan")
226      Tomoe::DictUnihan.new(config || {})
227    end
228
229    def make_dict_xml(config=nil)
230      check_dict_module_availability("XML")
231      config ||= {}
232      config = config.dup
233      config["filename"] ||= dictionary
234      Tomoe::DictXML.new(config)
235    end
236
237    def make_temporary_dict_xml(config=nil)
238      check_dict_module_availability("XML")
239      dict = nil
240      begin
241        tmp_dict_dir = File.join(tmp_dir, "dict")
242        FileUtils.mkdir_p(tmp_dict_dir)
243        dict_file = File.join(tmp_dict_dir, "dict.xml")
244        dict = Tomoe::DictXML.new("filename" => dict_file, "editable" => true)
245        yield dict
246      ensure
247        dict.flush if dict
248        FileUtils.rm_rf(tmp_dict_dir)
249      end
250    end
251
252    def make_dict_est(config=nil)
253      check_dict_module_availability("Est")
254      config ||= {}
255      config = config.dup
256      config["database"] ||= dictionary.sub(/\.xml/, '')
257      config["editable"] = true unless config.has_key?("editable")
258      Tomoe::DictEst.new(config)
259    end
260
261    def make_temporary_dict_est(config=nil)
262      check_dict_module_availability("Est")
263      begin
264        tmp_dict_dir = File.join(tmp_dir, "est")
265        yield Tomoe::DictEst.new("database" => tmp_dict_dir, "editable" => true)
266      ensure
267        FileUtils.rm_rf(tmp_dict_dir)
268      end
269    end
270
271    def make_dict_mysql(config=nil)
272      check_dict_module_availability("MySQL")
273      config ||= db_config
274      config = config.dup
275      Tomoe::DictMySQL.new(config)
276    end
277
278    def make_temporary_dict_mysql(config=nil)
279      check_dict_module_availability("MySQL")
280      sql_purge("temp")
281      yield Tomoe::DictMySQL.new(db_config("temp"))
282    end
283
284    def check_dict_module_availability(type)
285      begin
286        Tomoe.const_get("Dict#{type}")
287      rescue NameError
288        raise "Tomoe doesn't support the dictionary type: #{type}"
289      end
290    end
291
292    def sql_migrate(type=nil, version=nil)
293      migrate = File.join(db_dir, "migrate.rb")
294      tomoe_env = ENV["TOMOE_ENV"]
295      ENV["TOMOE_ENV"] = type if type
296      unless `#{migrate} #{version}`
297        message = "failed to migrate"
298        message << " to #{version}" if version
299        raise message
300      end
301    ensure
302      ENV["TOMOE_ENV"] = tomoe_env
303    end
304
305    def sql_purge(type=nil)
306      sql_migrate(type, 0)
307      sql_migrate(type)
308    end
309  end
310
311  module TestData
312    module_function
313    def parse(file)
314      expected = nil
315      writing = Tomoe::Writing.new
316      File.open(file) do |f|
317        expected = f.gets.split
318        f.each do |line|
319          next if /\A\s*\z/ =~ line
320          begin
321            first_point, *rest_points = line.split(/,/)
322            numbered_first_point = numbers_to_point(first_point)
323            writing.move_to(*numbered_first_point)
324            rest_points.each do |point|
325              writing.line_to(*numbers_to_point(point))
326            end
327          rescue ArgumentError
328            raise "invalid format in #{file} at #{f.lineno}: #{line}"
329          end
330        end
331      end
332      [expected, writing]
333    end
334
335    def numbers_to_point(str)
336      point = str.split.collect {|x| Integer(x)}
337      raise ArgumentError if point.size != 2
338      point
339    end
340  end
341
342  module Unicode
343    module_function
344    def ucs4_to_utf8(ucs4)
345      Uconv.u4tou8([ucs4].pack("I*"))
346    end
347
348    def utf8_to_ucs4(utf8)
349      Uconv.u8tou4(utf8).unpack("I*")[0]
350    end
351  end
352
353  module Assertions
354    def assert_true(actual, *args)
355      assert_equal(true, actual, *args)
356    end
357
358    def assert_false(actual, *args)
359      assert_equal(false, actual, *args)
360    end
361  end
362end
363
364require 'tomoe'
365
366Tomoe::Dict.default_module_dir = TomoeTestUtils::Path.dict_dir
367Tomoe::Recognizer.default_module_dir = TomoeTestUtils::Path.recognizer_dir
368