1#!/usr/bin/env ruby -w
2# encoding: UTF-8
3
4# tc_features.rb
5#
6#  Created by James Edward Gray II on 2005-10-31.
7#  Copyright 2005 James Edward Gray II. You can redistribute or modify this code
8#  under the terms of Ruby's license.
9
10begin
11  require "zlib"
12rescue LoadError
13end
14
15require_relative "base"
16require "tempfile"
17
18class TestCSV::Features < TestCSV
19  extend DifferentOFS
20
21  TEST_CASES = [ [%Q{a,b},               ["a", "b"]],
22                 [%Q{a,"""b"""},         ["a", "\"b\""]],
23                 [%Q{a,"""b"},           ["a", "\"b"]],
24                 [%Q{a,"b"""},           ["a", "b\""]],
25                 [%Q{a,"\nb"""},         ["a", "\nb\""]],
26                 [%Q{a,"""\nb"},         ["a", "\"\nb"]],
27                 [%Q{a,"""\nb\n"""},     ["a", "\"\nb\n\""]],
28                 [%Q{a,"""\nb\n""",\nc}, ["a", "\"\nb\n\"", nil]],
29                 [%Q{a,,,},              ["a", nil, nil, nil]],
30                 [%Q{,},                 [nil, nil]],
31                 [%Q{"",""},             ["", ""]],
32                 [%Q{""""},              ["\""]],
33                 [%Q{"""",""},           ["\"",""]],
34                 [%Q{,""},               [nil,""]],
35                 [%Q{,"\r"},             [nil,"\r"]],
36                 [%Q{"\r\n,"},           ["\r\n,"]],
37                 [%Q{"\r\n,",},          ["\r\n,", nil]] ]
38
39  def setup
40    super
41    @sample_data = <<-END_DATA.gsub(/^ +/, "")
42    line,1,abc
43    line,2,"def\nghi"
44
45    line,4,jkl
46    END_DATA
47    @csv = CSV.new(@sample_data)
48  end
49
50  def test_col_sep
51    [";", "\t"].each do |sep|
52      TEST_CASES.each do |test_case|
53        assert_equal( test_case.last.map { |t| t.tr(",", sep) unless t.nil? },
54                      CSV.parse_line( test_case.first.tr(",", sep),
55                                      col_sep: sep ) )
56      end
57    end
58    assert_equal([",,,", nil], CSV.parse_line(",,,;", col_sep: ";"))
59  end
60
61  def test_row_sep
62    assert_raise(CSV::MalformedCSVError) do
63        CSV.parse_line("1,2,3\n,4,5\r\n", row_sep: "\r\n")
64    end
65    assert_equal( ["1", "2", "3\n", "4", "5"],
66                  CSV.parse_line(%Q{1,2,"3\n",4,5\r\n}, row_sep: "\r\n"))
67  end
68
69  def test_quote_char
70    TEST_CASES.each do |test_case|
71      assert_equal( test_case.last.map { |t| t.tr('"', "'") unless t.nil? },
72                    CSV.parse_line( test_case.first.tr('"', "'"),
73                                    quote_char: "'" ) )
74    end
75  end
76
77  def test_bug_8405
78    TEST_CASES.each do |test_case|
79      assert_equal( test_case.last.map { |t| t.tr('"', "|") unless t.nil? },
80                    CSV.parse_line( test_case.first.tr('"', "|"),
81                                    quote_char: "|" ) )
82    end
83  end
84
85  def test_csv_char_readers
86    %w[col_sep row_sep quote_char].each do |reader|
87      csv = CSV.new("abc,def", reader.to_sym => "|")
88      assert_equal("|", csv.send(reader))
89    end
90  end
91
92  def test_row_sep_auto_discovery
93    ["\r\n", "\n", "\r"].each do |line_end|
94      data       = "1,2,3#{line_end}4,5#{line_end}"
95      discovered = CSV.new(data).row_sep
96      assert_equal(line_end, discovered)
97    end
98
99    assert_equal("\n", CSV.new("\n\r\n\r").row_sep)
100
101    assert_equal($/, CSV.new("").row_sep)
102
103    assert_equal($/, CSV.new(STDERR).row_sep)
104  end
105
106  def test_lineno
107    assert_equal(5, @sample_data.lines.to_a.size)
108
109    4.times do |line_count|
110      assert_equal(line_count, @csv.lineno)
111      assert_not_nil(@csv.shift)
112      assert_equal(line_count + 1, @csv.lineno)
113    end
114    assert_nil(@csv.shift)
115  end
116
117  def test_readline
118    test_lineno
119
120    @csv.rewind
121
122    test_lineno
123  end
124
125  def test_unknown_options
126    assert_raise(ArgumentError) { CSV.new(String.new, unknown: :error) }
127  end
128
129  def test_skip_blanks
130    assert_equal(4, @csv.to_a.size)
131
132    @csv  = CSV.new(@sample_data, skip_blanks: true)
133
134    count = 0
135    @csv.each do |row|
136      count += 1
137      assert_equal("line", row.first)
138    end
139    assert_equal(3, count)
140  end
141
142  def test_csv_behavior_readers
143    %w[ unconverted_fields return_headers write_headers
144        skip_blanks        force_quotes ].each do |behavior|
145      assert( !CSV.new("abc,def").send("#{behavior}?"),
146              "Behavior defaulted to on." )
147      csv = CSV.new("abc,def", behavior.to_sym => true)
148      assert(csv.send("#{behavior}?"), "Behavior change now registered.")
149    end
150  end
151
152  def test_converters_reader
153    # no change
154    assert_equal( [:integer],
155                  CSV.new("abc,def", converters: [:integer]).converters )
156
157    # just one
158    assert_equal( [:integer],
159                  CSV.new("abc,def", converters: :integer).converters )
160
161    # expanded
162    assert_equal( [:integer, :float],
163                  CSV.new("abc,def", converters: :numeric).converters )
164
165    # custom
166    csv = CSV.new("abc,def", converters: [:integer, lambda {  }])
167    assert_equal(2, csv.converters.size)
168    assert_equal(:integer, csv.converters.first)
169    assert_instance_of(Proc, csv.converters.last)
170  end
171
172  def test_header_converters_reader
173    # no change
174    hc = :header_converters
175    assert_equal([:downcase], CSV.new("abc,def", hc => [:downcase]).send(hc))
176
177    # just one
178    assert_equal([:downcase], CSV.new("abc,def", hc => :downcase).send(hc))
179
180    # custom
181    csv = CSV.new("abc,def", hc => [:symbol, lambda {  }])
182    assert_equal(2, csv.send(hc).size)
183    assert_equal(:symbol, csv.send(hc).first)
184    assert_instance_of(Proc, csv.send(hc).last)
185  end
186
187  # reported by Kev Jackson
188  def test_failing_to_escape_col_sep_bug_fix
189    assert_nothing_raised(Exception) { CSV.new(String.new, col_sep: "|") }
190  end
191
192  # reported by Chris Roos
193  def test_failing_to_reset_headers_in_rewind_bug_fix
194    csv = CSV.new("forename,surname", headers: true, return_headers: true)
195    csv.each { |row| assert row.header_row? }
196    csv.rewind
197    csv.each { |row| assert row.header_row? }
198  end
199
200  # reported by Dave Burt
201  def test_leading_empty_fields_with_multibyte_col_sep_bug_fix
202    data = <<-END_DATA.gsub(/^\s+/, "")
203    <=><=>A<=>B<=>C
204    1<=>2<=>3
205    END_DATA
206    parsed = CSV.parse(data, col_sep: "<=>")
207    assert_equal([[nil, nil, "A", "B", "C"], ["1", "2", "3"]], parsed)
208  end
209
210  def test_gzip_reader_bug_fix
211    zipped = nil
212    assert_nothing_raised(NoMethodError) do
213      zipped = CSV.new(
214                 Zlib::GzipReader.open(
215                   File.join(File.dirname(__FILE__), "line_endings.gz")
216                 )
217               )
218    end
219    assert_equal("\r\n", zipped.row_sep)
220  end if defined?(Zlib::GzipReader)
221
222  def test_gzip_writer_bug_fix
223    tempfile = Tempfile.new(%w"temp .gz")
224    tempfile.close
225    file = tempfile.path
226    zipped = nil
227    assert_nothing_raised(NoMethodError) do
228      zipped = CSV.new(Zlib::GzipWriter.open(file))
229    end
230    zipped << %w[one two three]
231    zipped << [1, 2, 3]
232    zipped.close
233
234    assert( Zlib::GzipReader.open(file) { |f| f.read }.
235                             include?($INPUT_RECORD_SEPARATOR),
236            "@row_sep did not default" )
237    tempfile.close(true)
238  end if defined?(Zlib::GzipWriter)
239
240  def test_inspect_is_smart_about_io_types
241    str = CSV.new("string,data").inspect
242    assert(str.include?("io_type:StringIO"), "IO type not detected.")
243
244    str = CSV.new($stderr).inspect
245    assert(str.include?("io_type:$stderr"), "IO type not detected.")
246
247    tempfile = Tempfile.new(%w"temp .csv")
248    tempfile.close
249    path = tempfile.path
250    File.open(path, "w") { |csv| csv << "one,two,three\n1,2,3\n" }
251    str  = CSV.open(path) { |csv| csv.inspect }
252    assert(str.include?("io_type:File"), "IO type not detected.")
253    tempfile.close(true)
254  end
255
256  def test_inspect_shows_key_attributes
257    str = @csv.inspect
258    %w[lineno col_sep row_sep quote_char].each do |attr_name|
259      assert_match(/\b#{attr_name}:[^\s>]+/, str)
260    end
261  end
262
263  def test_inspect_shows_headers_when_available
264    CSV.new("one,two,three\n1,2,3\n", headers: true) do |csv|
265      assert(csv.inspect.include?("headers:true"), "Header hint not shown.")
266      csv.shift  # load headers
267      assert_match(/headers:\[[^\]]+\]/, csv.inspect)
268    end
269  end
270
271  def test_inspect_encoding_is_ascii_compatible
272    CSV.new("one,two,three\n1,2,3\n".encode("UTF-16BE")) do |csv|
273      assert( Encoding.compatible?( Encoding.find("US-ASCII"),
274                                    csv.inspect.encoding ),
275              "inspect() was not ASCII compatible." )
276    end
277  end
278
279  def test_version
280    assert_not_nil(CSV::VERSION)
281    assert_instance_of(String, CSV::VERSION)
282    assert(CSV::VERSION.frozen?)
283    assert_match(/\A\d\.\d\.\d\Z/, CSV::VERSION)
284  end
285
286  def test_accepts_comment_skip_lines_option
287    assert_nothing_raised(ArgumentError) do
288      CSV.new nil, :skip_lines => /\A\s*#/
289    end
290  end
291
292  def test_accepts_comment_defaults_to_nil
293    c = CSV.new nil
294    assert_equal c.skip_lines, nil
295  end
296
297  class RegexStub
298  end
299
300  def test_requires_skip_lines_to_call_match
301    regex_stub = RegexStub.new
302    assert_raise(ArgumentError) do
303      CSV.new nil, :skip_lines => regex_stub
304    end
305  end
306
307  def test_comment_rows_are_ignored
308    sample_data = "line,1,a\n#not,a,line\nline,2,b\n   #also,no,line"
309    c = CSV.new sample_data, :skip_lines => /\A\s*#/
310    assert_equal c.each.to_a, [["line", "1", "a"], ["line", "2", "b"]]
311  end
312
313  def test_quoted_skip_line_markers_are_ignored
314    sample_data = "line,1,a\n\"#not\",a,line\nline,2,b"
315    c = CSV.new sample_data, :skip_lines => /\A\s*#/
316    assert_equal c.each.to_a, [["line", "1", "a"], ["#not", "a", "line"], ["line", "2", "b"]]
317  end
318end
319