summaryrefslogtreecommitdiff
path: root/test/csv
diff options
context:
space:
mode:
Diffstat (limited to 'test/csv')
-rw-r--r--test/csv/base.rb8
-rw-r--r--test/csv/helper.rb42
-rw-r--r--test/csv/interface/test_delegation.rb47
-rw-r--r--test/csv/interface/test_read.rb381
-rw-r--r--test/csv/interface/test_read_write.rb124
-rw-r--r--test/csv/interface/test_write.rb217
-rw-r--r--test/csv/parse/test_column_separator.rb40
-rw-r--r--test/csv/parse/test_convert.rb165
-rw-r--r--test/csv/parse/test_each.rb23
-rw-r--r--[-rwxr-xr-x]test/csv/parse/test_general.rb (renamed from test/csv/test_csv_parsing.rb)221
-rw-r--r--[-rwxr-xr-x]test/csv/parse/test_header.rb (renamed from test/csv/test_headers.rb)95
-rw-r--r--test/csv/parse/test_inputs_scanner.rb63
-rw-r--r--test/csv/parse/test_invalid.rb52
-rw-r--r--test/csv/parse/test_liberal_parsing.rb171
-rw-r--r--test/csv/parse/test_quote_char_nil.rb93
-rw-r--r--test/csv/parse/test_rewind.rb40
-rw-r--r--test/csv/parse/test_row_separator.rb16
-rw-r--r--test/csv/parse/test_skip_lines.rb118
-rw-r--r--test/csv/parse/test_strip.rb112
-rw-r--r--test/csv/parse/test_unconverted_fields.rb117
-rwxr-xr-xtest/csv/test_csv_writing.rb97
-rw-r--r--[-rwxr-xr-x]test/csv/test_data_converters.rb285
-rw-r--r--[-rwxr-xr-x]test/csv/test_encodings.rb110
-rw-r--r--[-rwxr-xr-x]test/csv/test_features.rb182
-rwxr-xr-xtest/csv/test_interface.rb368
-rw-r--r--test/csv/test_patterns.rb27
-rw-r--r--[-rwxr-xr-x]test/csv/test_row.rb118
-rw-r--r--[-rwxr-xr-x]test/csv/test_table.rb361
-rw-r--r--test/csv/ts_all.rb20
-rw-r--r--test/csv/write/test_converters.rb53
-rw-r--r--test/csv/write/test_force_quotes.rb78
-rw-r--r--test/csv/write/test_general.rb246
-rw-r--r--test/csv/write/test_quote_empty.rb70
33 files changed, 3241 insertions, 919 deletions
diff --git a/test/csv/base.rb b/test/csv/base.rb
deleted file mode 100644
index 621569e013..0000000000
--- a/test/csv/base.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require "test/unit"
-
-require "csv"
-
-require_relative "../lib/with_different_ofs.rb"
-
-class TestCSV < Test::Unit::TestCase
-end
diff --git a/test/csv/helper.rb b/test/csv/helper.rb
new file mode 100644
index 0000000000..1f9cf96979
--- /dev/null
+++ b/test/csv/helper.rb
@@ -0,0 +1,42 @@
+require "tempfile"
+require "test/unit"
+
+require "csv"
+
+require_relative "../lib/with_different_ofs"
+
+module Helper
+ def with_chunk_size(chunk_size)
+ chunk_size_keep = ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"]
+ begin
+ ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] = chunk_size
+ yield
+ ensure
+ ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] = chunk_size_keep
+ end
+ end
+
+ def with_verbose(verbose)
+ original = $VERBOSE
+ begin
+ $VERBOSE = verbose
+ yield
+ ensure
+ $VERBOSE = original
+ end
+ end
+
+ def with_default_internal(encoding)
+ original = Encoding.default_internal
+ begin
+ with_verbose(false) do
+ Encoding.default_internal = encoding
+ end
+ yield
+ ensure
+ with_verbose(false) do
+ Encoding.default_internal = original
+ end
+ end
+ end
+end
diff --git a/test/csv/interface/test_delegation.rb b/test/csv/interface/test_delegation.rb
new file mode 100644
index 0000000000..349257633b
--- /dev/null
+++ b/test/csv/interface/test_delegation.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVInterfaceDelegation < Test::Unit::TestCase
+ class TestStringIO < self
+ def setup
+ @csv = CSV.new("h1,h2")
+ end
+
+ def test_flock
+ assert_raise(NotImplementedError) do
+ @csv.flock(File::LOCK_EX)
+ end
+ end
+
+ def test_ioctl
+ assert_raise(NotImplementedError) do
+ @csv.ioctl(0)
+ end
+ end
+
+ def test_stat
+ assert_raise(NotImplementedError) do
+ @csv.stat
+ end
+ end
+
+ def test_to_i
+ assert_raise(NotImplementedError) do
+ @csv.to_i
+ end
+ end
+
+ def test_binmode?
+ assert_equal(false, @csv.binmode?)
+ end
+
+ def test_path
+ assert_equal(nil, @csv.path)
+ end
+
+ def test_to_io
+ assert_instance_of(StringIO, @csv.to_io)
+ end
+ end
+end
diff --git a/test/csv/interface/test_read.rb b/test/csv/interface/test_read.rb
new file mode 100644
index 0000000000..4266ef0578
--- /dev/null
+++ b/test/csv/interface/test_read.rb
@@ -0,0 +1,381 @@
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVInterfaceRead < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def setup
+ super
+ @data = ""
+ @data << "1\t2\t3\r\n"
+ @data << "4\t5\r\n"
+ @input = Tempfile.new(["interface-read", ".csv"], binmode: true)
+ @input << @data
+ @input.rewind
+ @rows = [
+ ["1", "2", "3"],
+ ["4", "5"],
+ ]
+ end
+
+ def teardown
+ @input.close(true)
+ super
+ end
+
+ def test_foreach
+ rows = []
+ CSV.foreach(@input.path, col_sep: "\t", row_sep: "\r\n").each do |row|
+ rows << row
+ end
+ assert_equal(@rows, rows)
+ end
+
+ if respond_to?(:ractor)
+ ractor
+ def test_foreach_in_ractor
+ ractor = Ractor.new(@input.path) do |path|
+ rows = []
+ CSV.foreach(path, col_sep: "\t", row_sep: "\r\n").each do |row|
+ rows << row
+ end
+ rows
+ end
+ rows = [
+ ["1", "2", "3"],
+ ["4", "5"],
+ ]
+ assert_equal(rows, ractor.take)
+ end
+ end
+
+ def test_foreach_mode
+ rows = []
+ CSV.foreach(@input.path, "r", col_sep: "\t", row_sep: "\r\n").each do |row|
+ rows << row
+ end
+ assert_equal(@rows, rows)
+ end
+
+ def test_foreach_enumurator
+ rows = CSV.foreach(@input.path, col_sep: "\t", row_sep: "\r\n").to_a
+ assert_equal(@rows, rows)
+ end
+
+ def test_closed?
+ csv = CSV.open(@input.path, "r+", col_sep: "\t", row_sep: "\r\n")
+ assert_not_predicate(csv, :closed?)
+ csv.close
+ assert_predicate(csv, :closed?)
+ end
+
+ def test_open_auto_close
+ csv = nil
+ CSV.open(@input.path) do |_csv|
+ csv = _csv
+ end
+ assert_predicate(csv, :closed?)
+ end
+
+ def test_open_closed
+ csv = nil
+ CSV.open(@input.path) do |_csv|
+ csv = _csv
+ csv.close
+ end
+ assert_predicate(csv, :closed?)
+ end
+
+ def test_open_block_return_value
+ return_value = CSV.open(@input.path) do
+ "Return value."
+ end
+ assert_equal("Return value.", return_value)
+ end
+
+ def test_open_encoding_valid
+ # U+1F600 GRINNING FACE
+ # U+1F601 GRINNING FACE WITH SMILING EYES
+ File.open(@input.path, "w") do |file|
+ file << "\u{1F600},\u{1F601}"
+ end
+ CSV.open(@input.path, encoding: "utf-8") do |csv|
+ assert_equal([["\u{1F600}", "\u{1F601}"]],
+ csv.to_a)
+ end
+ end
+
+ def test_open_encoding_invalid
+ # U+1F600 GRINNING FACE
+ # U+1F601 GRINNING FACE WITH SMILING EYES
+ File.open(@input.path, "w") do |file|
+ file << "\u{1F600},\u{1F601}"
+ end
+ CSV.open(@input.path, encoding: "EUC-JP") do |csv|
+ error = assert_raise(CSV::MalformedCSVError) do
+ csv.shift
+ end
+ assert_equal("Invalid byte sequence in EUC-JP in line 1.",
+ error.message)
+ end
+ end
+
+ def test_open_encoding_nonexistent
+ _output, error = capture_output do
+ CSV.open(@input.path, encoding: "nonexistent") do
+ end
+ end
+ assert_equal("path:0: warning: Unsupported encoding nonexistent ignored\n",
+ error.gsub(/\A.+:\d+: /, "path:0: "))
+ end
+
+ def test_open_encoding_utf_8_with_bom
+ # U+FEFF ZERO WIDTH NO-BREAK SPACE, BOM
+ # U+1F600 GRINNING FACE
+ # U+1F601 GRINNING FACE WITH SMILING EYES
+ File.open(@input.path, "w") do |file|
+ file << "\u{FEFF}\u{1F600},\u{1F601}"
+ end
+ CSV.open(@input.path, encoding: "bom|utf-8") do |csv|
+ assert_equal([["\u{1F600}", "\u{1F601}"]],
+ csv.to_a)
+ end
+ end
+
+ def test_open_invalid_byte_sequence_in_utf_8
+ CSV.open(@input.path, "w", encoding: Encoding::CP932) do |rows|
+ error = assert_raise(Encoding::InvalidByteSequenceError) do
+ rows << ["\x82\xa0"]
+ end
+ assert_equal('"\x82" on UTF-8',
+ error.message)
+ end
+ end
+
+ def test_open_with_invalid_nil
+ CSV.open(@input.path, "w", encoding: Encoding::CP932, invalid: nil) do |rows|
+ error = assert_raise(Encoding::InvalidByteSequenceError) do
+ rows << ["\x82\xa0"]
+ end
+ assert_equal('"\x82" on UTF-8',
+ error.message)
+ end
+ end
+
+ def test_open_with_invalid_replace
+ CSV.open(@input.path, "w", encoding: Encoding::CP932, invalid: :replace) do |rows|
+ rows << ["\x82\xa0".force_encoding(Encoding::UTF_8)]
+ end
+ CSV.open(@input.path, encoding: Encoding::CP932) do |csv|
+ assert_equal([["??"]],
+ csv.to_a)
+ end
+ end
+
+ def test_open_with_invalid_replace_and_replace_string
+ CSV.open(@input.path, "w", encoding: Encoding::CP932, invalid: :replace, replace: "X") do |rows|
+ rows << ["\x82\xa0".force_encoding(Encoding::UTF_8)]
+ end
+ CSV.open(@input.path, encoding: Encoding::CP932) do |csv|
+ assert_equal([["XX"]],
+ csv.to_a)
+ end
+ end
+
+ def test_open_with_undef_replace
+ # U+00B7 Middle Dot
+ CSV.open(@input.path, "w", encoding: Encoding::CP932, undef: :replace) do |rows|
+ rows << ["\u00B7"]
+ end
+ CSV.open(@input.path, encoding: Encoding::CP932) do |csv|
+ assert_equal([["?"]],
+ csv.to_a)
+ end
+ end
+
+ def test_open_with_undef_replace_and_replace_string
+ # U+00B7 Middle Dot
+ CSV.open(@input.path, "w", encoding: Encoding::CP932, undef: :replace, replace: "X") do |rows|
+ rows << ["\u00B7"]
+ end
+ CSV.open(@input.path, encoding: Encoding::CP932) do |csv|
+ assert_equal([["X"]],
+ csv.to_a)
+ end
+ end
+
+ def test_open_with_newline
+ CSV.open(@input.path, col_sep: "\t", universal_newline: true) do |csv|
+ assert_equal(@rows, csv.to_a)
+ end
+ File.binwrite(@input.path, "1,2,3\r\n" "4,5\n")
+ CSV.open(@input.path, newline: :universal) do |csv|
+ assert_equal(@rows, csv.to_a)
+ end
+ end
+
+ def test_parse
+ assert_equal(@rows,
+ CSV.parse(@data, col_sep: "\t", row_sep: "\r\n"))
+ end
+
+ def test_parse_block
+ rows = []
+ CSV.parse(@data, col_sep: "\t", row_sep: "\r\n") do |row|
+ rows << row
+ end
+ assert_equal(@rows, rows)
+ end
+
+ def test_parse_enumerator
+ rows = CSV.parse(@data, col_sep: "\t", row_sep: "\r\n").to_a
+ assert_equal(@rows, rows)
+ end
+
+ def test_parse_headers_only
+ table = CSV.parse("a,b,c", headers: true)
+ assert_equal([
+ ["a", "b", "c"],
+ [],
+ ],
+ [
+ table.headers,
+ table.each.to_a,
+ ])
+ end
+
+ def test_parse_line
+ assert_equal(["1", "2", "3"],
+ CSV.parse_line("1;2;3", col_sep: ";"))
+ end
+
+ def test_parse_line_shortcut
+ assert_equal(["1", "2", "3"],
+ "1;2;3".parse_csv(col_sep: ";"))
+ end
+
+ def test_parse_line_empty
+ assert_equal(nil, CSV.parse_line("")) # to signal eof
+ end
+
+ def test_parse_line_empty_line
+ assert_equal([], CSV.parse_line("\n1,2,3"))
+ end
+
+ def test_read
+ assert_equal(@rows,
+ CSV.read(@input.path, col_sep: "\t", row_sep: "\r\n"))
+ end
+
+ if respond_to?(:ractor)
+ ractor
+ def test_read_in_ractor
+ ractor = Ractor.new(@input.path) do |path|
+ CSV.read(path, col_sep: "\t", row_sep: "\r\n")
+ end
+ rows = [
+ ["1", "2", "3"],
+ ["4", "5"],
+ ]
+ assert_equal(rows, ractor.take)
+ end
+ end
+
+ def test_readlines
+ assert_equal(@rows,
+ CSV.readlines(@input.path, col_sep: "\t", row_sep: "\r\n"))
+ end
+
+ def test_open_read
+ rows = CSV.open(@input.path, col_sep: "\t", row_sep: "\r\n") do |csv|
+ csv.read
+ end
+ assert_equal(@rows, rows)
+ end
+
+ def test_open_readlines
+ rows = CSV.open(@input.path, col_sep: "\t", row_sep: "\r\n") do |csv|
+ csv.readlines
+ end
+ assert_equal(@rows, rows)
+ end
+
+ def test_table
+ table = CSV.table(@input.path, col_sep: "\t", row_sep: "\r\n")
+ assert_equal(CSV::Table.new([
+ CSV::Row.new([:"1", :"2", :"3"], [4, 5, nil]),
+ ]),
+ table)
+ end
+
+ def test_shift # aliased as gets() and readline()
+ CSV.open(@input.path, "rb+", col_sep: "\t", row_sep: "\r\n") do |csv|
+ rows = [
+ csv.shift,
+ csv.shift,
+ csv.shift,
+ ]
+ assert_equal(@rows + [nil],
+ rows)
+ end
+ end
+
+ def test_enumerator
+ CSV.open(@input.path, col_sep: "\t", row_sep: "\r\n") do |csv|
+ assert_equal(@rows, csv.each.to_a)
+ end
+ end
+
+ def test_shift_and_each
+ CSV.open(@input.path, col_sep: "\t", row_sep: "\r\n") do |csv|
+ rows = []
+ rows << csv.shift
+ rows.concat(csv.each.to_a)
+ assert_equal(@rows, rows)
+ end
+ end
+
+ def test_each_twice
+ CSV.open(@input.path, col_sep: "\t", row_sep: "\r\n") do |csv|
+ assert_equal([
+ @rows,
+ [],
+ ],
+ [
+ csv.each.to_a,
+ csv.each.to_a,
+ ])
+ end
+ end
+
+ def test_eof?
+ eofs = []
+ CSV.open(@input.path, col_sep: "\t", row_sep: "\r\n") do |csv|
+ eofs << csv.eof?
+ csv.shift
+ eofs << csv.eof?
+ csv.shift
+ eofs << csv.eof?
+ end
+ assert_equal([false, false, true],
+ eofs)
+ end
+
+ def test_new_nil
+ assert_raise_with_message ArgumentError, "Cannot parse nil as CSV" do
+ CSV.new(nil)
+ end
+ end
+
+ def test_options_not_modified
+ options = {}.freeze
+ CSV.foreach(@input.path, **options)
+ CSV.open(@input.path, **options) {}
+ CSV.parse("", **options)
+ CSV.parse_line("", **options)
+ CSV.read(@input.path, **options)
+ CSV.readlines(@input.path, **options)
+ CSV.table(@input.path, **options)
+ end
+end
diff --git a/test/csv/interface/test_read_write.rb b/test/csv/interface/test_read_write.rb
new file mode 100644
index 0000000000..c371e9c5fc
--- /dev/null
+++ b/test/csv/interface/test_read_write.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVInterfaceReadWrite < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def test_filter
+ input = <<-CSV.freeze
+1;2;3
+4;5
+ CSV
+ output = ""
+ CSV.filter(input, output,
+ in_col_sep: ";",
+ out_col_sep: ",",
+ converters: :all) do |row|
+ row.map! {|n| n * 2}
+ row << "Added\r"
+ end
+ assert_equal(<<-CSV, output)
+2,4,6,"Added\r"
+8,10,"Added\r"
+ CSV
+ end
+
+ def test_filter_headers_true
+ input = <<-CSV.freeze
+Name,Value
+foo,0
+bar,1
+baz,2
+ CSV
+ output = ""
+ CSV.filter(input, output, headers: true) do |row|
+ row[0] += "X"
+ row[1] = row[1].to_i + 1
+ end
+ assert_equal(<<-CSV, output)
+fooX,1
+barX,2
+bazX,3
+ CSV
+ end
+
+ def test_filter_headers_true_write_headers
+ input = <<-CSV.freeze
+Name,Value
+foo,0
+bar,1
+baz,2
+ CSV
+ output = ""
+ CSV.filter(input, output, headers: true, out_write_headers: true) do |row|
+ if row.is_a?(Array)
+ row[0] += "X"
+ row[1] += "Y"
+ else
+ row[0] += "X"
+ row[1] = row[1].to_i + 1
+ end
+ end
+ assert_equal(<<-CSV, output)
+NameX,ValueY
+fooX,1
+barX,2
+bazX,3
+ CSV
+ end
+
+ def test_filter_headers_array_write_headers
+ input = <<-CSV.freeze
+foo,0
+bar,1
+baz,2
+ CSV
+ output = ""
+ CSV.filter(input, output,
+ headers: ["Name", "Value"],
+ out_write_headers: true) do |row|
+ row[0] += "X"
+ row[1] = row[1].to_i + 1
+ end
+ assert_equal(<<-CSV, output)
+Name,Value
+fooX,1
+barX,2
+bazX,3
+ CSV
+ end
+
+ def test_instance_same
+ data = ""
+ assert_equal(CSV.instance(data, col_sep: ";").object_id,
+ CSV.instance(data, col_sep: ";").object_id)
+ end
+
+ def test_instance_append
+ output = ""
+ CSV.instance(output, col_sep: ";") << ["a", "b", "c"]
+ assert_equal(<<-CSV, output)
+a;b;c
+ CSV
+ CSV.instance(output, col_sep: ";") << [1, 2, 3]
+ assert_equal(<<-CSV, output)
+a;b;c
+1;2;3
+ CSV
+ end
+
+ def test_instance_shortcut
+ assert_equal(CSV.instance,
+ CSV {|csv| csv})
+ end
+
+ def test_instance_shortcut_with_io
+ io = StringIO.new
+ from_instance = CSV.instance(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
+ from_shortcut = CSV(io, col_sep: ";") { |csv| csv << ["e", "f", "g"] }
+
+ assert_equal(from_instance, from_shortcut)
+ assert_equal(from_instance.string, "a;b;c\ne;f;g\n")
+ end
+end
diff --git a/test/csv/interface/test_write.rb b/test/csv/interface/test_write.rb
new file mode 100644
index 0000000000..0cd39a7663
--- /dev/null
+++ b/test/csv/interface/test_write.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVInterfaceWrite < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def setup
+ super
+ @output = Tempfile.new(["interface-write", ".csv"])
+ end
+
+ def teardown
+ @output.close(true)
+ super
+ end
+
+ def test_generate_default
+ csv_text = CSV.generate do |csv|
+ csv << [1, 2, 3] << [4, nil, 5]
+ end
+ assert_equal(<<-CSV, csv_text)
+1,2,3
+4,,5
+ CSV
+ end
+
+ if respond_to?(:ractor)
+ ractor
+ def test_generate_default_in_ractor
+ ractor = Ractor.new do
+ CSV.generate do |csv|
+ csv << [1, 2, 3] << [4, nil, 5]
+ end
+ end
+ assert_equal(<<-CSV, ractor.take)
+1,2,3
+4,,5
+ CSV
+ end
+ end
+
+ def test_generate_append
+ csv_text = <<-CSV
+1,2,3
+4,,5
+ CSV
+ CSV.generate(csv_text) do |csv|
+ csv << ["last", %Q{"row"}]
+ end
+ assert_equal(<<-CSV, csv_text)
+1,2,3
+4,,5
+last,"""row"""
+ CSV
+ end
+
+ def test_generate_no_new_line
+ csv_text = CSV.generate("test") do |csv|
+ csv << ["row"]
+ end
+ assert_equal(<<-CSV, csv_text)
+testrow
+ CSV
+ end
+
+ def test_generate_line_col_sep
+ line = CSV.generate_line(["1", "2", "3"], col_sep: ";")
+ assert_equal(<<-LINE, line)
+1;2;3
+ LINE
+ end
+
+ def test_generate_line_row_sep
+ line = CSV.generate_line(["1", "2"], row_sep: nil)
+ assert_equal(<<-LINE.chomp, line)
+1,2
+ LINE
+ end
+
+ def test_generate_line_shortcut
+ line = ["1", "2", "3"].to_csv(col_sep: ";")
+ assert_equal(<<-LINE, line)
+1;2;3
+ LINE
+ end
+
+ def test_generate_lines
+ lines = CSV.generate_lines([["foo", "bar"], [1, 2], [3, 4]])
+ assert_equal(<<-LINES, lines)
+foo,bar
+1,2
+3,4
+ LINES
+ end
+
+ def test_headers_detection
+ headers = ["a", "b", "c"]
+ CSV.open(@output.path, "w", headers: true) do |csv|
+ csv << headers
+ csv << ["1", "2", "3"]
+ assert_equal(headers, csv.headers)
+ end
+ end
+
+ def test_lineno
+ CSV.open(@output.path, "w") do |csv|
+ n_lines = 20
+ n_lines.times do
+ csv << ["a", "b", "c"]
+ end
+ assert_equal(n_lines, csv.lineno)
+ end
+ end
+
+ def test_append_row
+ CSV.open(@output.path, "wb") do |csv|
+ csv <<
+ CSV::Row.new([], ["1", "2", "3"]) <<
+ CSV::Row.new([], ["a", "b", "c"])
+ end
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+1,2,3
+a,b,c
+ CSV
+ end
+
+
+ if respond_to?(:ractor)
+ ractor
+ def test_append_row_in_ractor
+ ractor = Ractor.new(@output.path) do |path|
+ CSV.open(path, "wb") do |csv|
+ csv <<
+ CSV::Row.new([], ["1", "2", "3"]) <<
+ CSV::Row.new([], ["a", "b", "c"])
+ end
+ end
+ ractor.take
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+1,2,3
+a,b,c
+ CSV
+ end
+ end
+
+ def test_append_hash
+ CSV.open(@output.path, "wb", headers: true) do |csv|
+ csv << [:a, :b, :c]
+ csv << {a: 1, b: 2, c: 3}
+ csv << {a: 4, b: 5, c: 6}
+ end
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+a,b,c
+1,2,3
+4,5,6
+ CSV
+ end
+
+ def test_append_hash_headers_array
+ CSV.open(@output.path, "wb", headers: [:b, :a, :c]) do |csv|
+ csv << {a: 1, b: 2, c: 3}
+ csv << {a: 4, b: 5, c: 6}
+ end
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+2,1,3
+5,4,6
+ CSV
+ end
+
+ def test_append_hash_headers_string
+ CSV.open(@output.path, "wb", headers: "b|a|c", col_sep: "|") do |csv|
+ csv << {"a" => 1, "b" => 2, "c" => 3}
+ csv << {"a" => 4, "b" => 5, "c" => 6}
+ end
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+2|1|3
+5|4|6
+ CSV
+ end
+
+ def test_write_headers
+ CSV.open(@output.path,
+ "wb",
+ headers: "b|a|c",
+ write_headers: true,
+ col_sep: "|" ) do |csv|
+ csv << {"a" => 1, "b" => 2, "c" => 3}
+ csv << {"a" => 4, "b" => 5, "c" => 6}
+ end
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+b|a|c
+2|1|3
+5|4|6
+ CSV
+ end
+
+ def test_write_headers_empty
+ CSV.open(@output.path,
+ "wb",
+ headers: "b|a|c",
+ write_headers: true,
+ col_sep: "|" ) do |csv|
+ end
+ assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
+b|a|c
+ CSV
+ end
+
+ def test_options_not_modified
+ options = {}.freeze
+ CSV.generate(**options) {}
+ CSV.generate_line([], **options)
+ CSV.filter("", "", **options)
+ CSV.instance("", **options)
+ end
+end
diff --git a/test/csv/parse/test_column_separator.rb b/test/csv/parse/test_column_separator.rb
new file mode 100644
index 0000000000..d6eaa7b6de
--- /dev/null
+++ b/test/csv/parse/test_column_separator.rb
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseColumnSeparator < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def test_comma
+ assert_equal([["a", "b", nil, "d"]],
+ CSV.parse("a,b,,d", col_sep: ","))
+ end
+
+ def test_space
+ assert_equal([["a", "b", nil, "d"]],
+ CSV.parse("a b d", col_sep: " "))
+ end
+
+ def test_tab
+ assert_equal([["a", "b", nil, "d"]],
+ CSV.parse("a\tb\t\td", col_sep: "\t"))
+ end
+
+ def test_multiple_characters_include_sub_separator
+ assert_equal([["a b", nil, "d"]],
+ CSV.parse("a b d", col_sep: " "))
+ end
+
+ def test_multiple_characters_leading_empty_fields
+ data = <<-CSV
+<=><=>A<=>B<=>C
+1<=>2<=>3
+ CSV
+ assert_equal([
+ [nil, nil, "A", "B", "C"],
+ ["1", "2", "3"],
+ ],
+ CSV.parse(data, col_sep: "<=>"))
+ end
+end
diff --git a/test/csv/parse/test_convert.rb b/test/csv/parse/test_convert.rb
new file mode 100644
index 0000000000..c9195c71d9
--- /dev/null
+++ b/test/csv/parse/test_convert.rb
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseConvert < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def setup
+ super
+ @data = "Numbers,:integer,1,:float,3.015"
+ @parser = CSV.new(@data)
+
+ @custom = lambda {|field| /\A:(\S.*?)\s*\Z/ =~ field ? $1.to_sym : field}
+
+ @time = Time.utc(2018, 12, 30, 6, 41, 29)
+ @windows_safe_time_data = @time.strftime("%a %b %d %H:%M:%S %Y")
+
+ @preserving_converter = lambda do |field, info|
+ f = field.encode(CSV::ConverterEncoding)
+ return f if info.quoted?
+ begin
+ Integer(f, 10)
+ rescue
+ f
+ end
+ end
+
+ @quoted_header_converter = lambda do |field, info|
+ f = field.encode(CSV::ConverterEncoding)
+ return f if info.quoted?
+ f.to_sym
+ end
+ end
+
+ def test_integer
+ @parser.convert(:integer)
+ assert_equal(["Numbers", ":integer", 1, ":float", "3.015"],
+ @parser.shift)
+ end
+
+ def test_float
+ @parser.convert(:float)
+ assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015],
+ @parser.shift)
+ end
+
+ def test_float_integer
+ @parser.convert(:float)
+ @parser.convert(:integer)
+ assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015],
+ @parser.shift)
+ end
+
+ def test_integer_float
+ @parser.convert(:integer)
+ @parser.convert(:float)
+ assert_equal(["Numbers", ":integer", 1, ":float", 3.015],
+ @parser.shift)
+ end
+
+ def test_numeric
+ @parser.convert(:numeric)
+ assert_equal(["Numbers", ":integer", 1, ":float", 3.015],
+ @parser.shift)
+ end
+
+ def test_all
+ @data << ",#{@windows_safe_time_data}"
+ @parser = CSV.new(@data)
+ @parser.convert(:all)
+ assert_equal(["Numbers", ":integer", 1, ":float", 3.015, @time.to_datetime],
+ @parser.shift)
+ end
+
+ def test_custom
+ @parser.convert do |field|
+ /\A:(\S.*?)\s*\Z/ =~ field ? $1.to_sym : field
+ end
+ assert_equal(["Numbers", :integer, "1", :float, "3.015"],
+ @parser.shift)
+ end
+
+ def test_builtin_custom
+ @parser.convert(:numeric)
+ @parser.convert(&@custom)
+ assert_equal(["Numbers", :integer, 1, :float, 3.015],
+ @parser.shift)
+ end
+
+ def test_custom_field_info_line
+ @parser.convert do |field, info|
+ assert_equal(1, info.line)
+ info.index == 4 ? Float(field).floor : field
+ end
+ assert_equal(["Numbers", ":integer", "1", ":float", 3],
+ @parser.shift)
+ end
+
+ def test_custom_field_info_header
+ headers = ["one", "two", "three", "four", "five"]
+ @parser = CSV.new(@data, headers: headers)
+ @parser.convert do |field, info|
+ info.header == "three" ? Integer(field) * 100 : field
+ end
+ assert_equal(CSV::Row.new(headers,
+ ["Numbers", ":integer", 100, ":float", "3.015"]),
+ @parser.shift)
+ end
+
+ def test_custom_blank_field
+ converter = lambda {|field| field.nil?}
+ row = CSV.parse_line('nil,', converters: converter)
+ assert_equal([false, true], row)
+ end
+
+ def test_nil_value
+ assert_equal(["nil", "", "a"],
+ CSV.parse_line(',"",a', nil_value: "nil"))
+ end
+
+ def test_empty_value
+ assert_equal([nil, "empty", "a"],
+ CSV.parse_line(',"",a', empty_value: "empty"))
+ end
+
+ def test_quoted_parse_line
+ row = CSV.parse_line('1,"2",3', converters: @preserving_converter)
+ assert_equal([1, "2", 3], row)
+ end
+
+ def test_quoted_parse
+ expected = [["quoted", "unquoted"], ["109", 1], ["10A", 2]]
+ rows = CSV.parse(<<~CSV, converters: @preserving_converter)
+ "quoted",unquoted
+ "109",1
+ "10A",2
+ CSV
+ assert_equal(expected, rows)
+ end
+
+ def test_quoted_alternating_quote
+ row = CSV.parse_line('"1",2,"3"', converters: @preserving_converter)
+ assert_equal(['1', 2, '3'], row)
+ end
+
+ def test_quoted_parse_headers
+ expected = [["quoted", :unquoted], ["109", "1"], ["10A", "2"]]
+ table = CSV.parse(<<~CSV, headers: true, header_converters: @quoted_header_converter)
+ "quoted",unquoted
+ "109",1
+ "10A",2
+ CSV
+ assert_equal(expected, table.to_a)
+ end
+
+ def test_quoted_parse_with_string_headers
+ expected = [["quoted", :unquoted], %w[109 1], %w[10A 2]]
+ table = CSV.parse(<<~CSV, headers: '"quoted",unquoted', header_converters: @quoted_header_converter)
+ "109",1
+ "10A",2
+ CSV
+ assert_equal(expected, table.to_a)
+ end
+end
diff --git a/test/csv/parse/test_each.rb b/test/csv/parse/test_each.rb
new file mode 100644
index 0000000000..ce0b71d058
--- /dev/null
+++ b/test/csv/parse/test_each.rb
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseEach < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def test_twice
+ data = <<-CSV
+Ruby,2.6.0,script
+ CSV
+ csv = CSV.new(data)
+ assert_equal([
+ [["Ruby", "2.6.0", "script"]],
+ [],
+ ],
+ [
+ csv.to_a,
+ csv.to_a,
+ ])
+ end
+end
diff --git a/test/csv/test_csv_parsing.rb b/test/csv/parse/test_general.rb
index 319f3f392f..902be2ce4a 100755..100644
--- a/test/csv/test_csv_parsing.rb
+++ b/test/csv/parse/test_general.rb
@@ -1,15 +1,9 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
-
-# tc_csv_parsing.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
require "timeout"
-require_relative "base"
+require_relative "../helper"
#
# Following tests are my interpretation of the
@@ -17,10 +11,10 @@ require_relative "base"
# document in one place (intentionally) and that is to make the default row
# separator <tt>$/</tt>.
#
-class TestCSV::Parsing < TestCSV
+class TestCSVParseGeneral < Test::Unit::TestCase
extend DifferentOFS
- BIG_DATA = "123456789\n" * 1024
+ BIG_DATA = "123456789\n" * 512
def test_mastering_regex_example
ex = %Q{Ten Thousand,10000, 2710 ,,"10,000","It's ""10 Grand"", baby",10K}
@@ -144,55 +138,52 @@ class TestCSV::Parsing < TestCSV
end
end
- def test_malformed_csv
- assert_raise(CSV::MalformedCSVError) do
+ def test_malformed_csv_cr_first_line
+ error = assert_raise(CSV::MalformedCSVError) do
CSV.parse_line("1,2\r,3", row_sep: "\n")
end
+ assert_equal("Unquoted fields do not allow new line <\"\\r\"> in line 1.",
+ error.message)
+ end
- bad_data = <<-END_DATA.gsub(/^ +/, "")
- line,1,abc
- line,2,"def\nghi"
-
- line,4,some\rjunk
- line,5,jkl
- END_DATA
- lines = bad_data.lines.to_a
- assert_equal(6, lines.size)
- assert_match(/\Aline,4/, lines.find { |l| l =~ /some\rjunk/ })
-
- csv = CSV.new(bad_data)
- begin
- loop do
- assert_not_nil(csv.shift)
- assert_send([csv.lineno, :<, 4])
- end
- rescue CSV::MalformedCSVError
- assert_equal( "Unquoted fields do not allow \\r or \\n (line 4).",
- $!.message )
+ def test_malformed_csv_cr_middle_line
+ csv = <<-CSV
+line,1,abc
+line,2,"def\nghi"
+
+line,4,some\rjunk
+line,5,jkl
+ CSV
+
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse(csv)
end
+ assert_equal("Unquoted fields do not allow new line <\"\\r\"> in line 4.",
+ error.message)
+ end
- assert_raise(CSV::MalformedCSVError) { CSV.parse_line('1,2,"3...') }
+ def test_malformed_csv_unclosed_quote
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse_line('1,2,"3...')
+ end
+ assert_equal("Unclosed quoted field in line 1.",
+ error.message)
+ end
- bad_data = <<-END_DATA.gsub(/^ +/, "")
- line,1,abc
- line,2,"def\nghi"
+ def test_malformed_csv_illegal_quote_middle_line
+ csv = <<-CSV
+line,1,abc
+line,2,"def\nghi"
- line,4,8'10"
- line,5,jkl
- END_DATA
- lines = bad_data.lines.to_a
- assert_equal(6, lines.size)
- assert_match(/\Aline,4/, lines.find { |l| l =~ /8'10"/ })
+line,4,8'10"
+line,5,jkl
+ CSV
- csv = CSV.new(bad_data)
- begin
- loop do
- assert_not_nil(csv.shift)
- assert_send([csv.lineno, :<, 4])
- end
- rescue CSV::MalformedCSVError
- assert_equal("Illegal quoting in line 4.", $!.message)
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse(csv)
end
+ assert_equal("Illegal quoting in line 4.",
+ error.message)
end
def test_the_parse_fails_fast_when_it_can_for_unquoted_fields
@@ -208,12 +199,134 @@ class TestCSV::Parsing < TestCSV
field_size_limit: 2048 )
end
- private
+ def test_field_size_limit_max_allowed
+ column = "abcde"
+ assert_equal([[column]],
+ CSV.parse("\"#{column}\"",
+ field_size_limit: column.size + 1))
+ end
- def assert_parse_errors_out(*args)
+ def test_field_size_limit_quote_simple
+ column = "abcde"
+ assert_parse_errors_out("\"#{column}\"",
+ field_size_limit: column.size)
+ end
+
+ def test_field_size_limit_no_quote_implicitly
+ column = "abcde"
+ assert_parse_errors_out("#{column}",
+ field_size_limit: column.size)
+ end
+
+ def test_field_size_limit_no_quote_explicitly
+ column = "abcde"
+ assert_parse_errors_out("#{column}",
+ field_size_limit: column.size,
+ quote_char: nil)
+ end
+
+ def test_field_size_limit_in_extended_column_not_exceeding
+ data = <<~DATA
+ "a","b"
+ "
+ 2
+ ",""
+ DATA
+ assert_nothing_raised(CSV::MalformedCSVError) do
+ CSV.parse(data, field_size_limit: 4)
+ end
+ end
+
+ def test_field_size_limit_in_extended_column_exceeding
+ data = <<~DATA
+ "a","b"
+ "
+ 2345
+ ",""
+ DATA
+ assert_parse_errors_out(data, field_size_limit: 5)
+ end
+
+ def test_max_field_size_controls_lookahead
+ assert_parse_errors_out( 'valid,fields,"' + BIG_DATA + '"',
+ max_field_size: 2048 )
+ end
+
+ def test_max_field_size_max_allowed
+ column = "abcde"
+ assert_equal([[column]],
+ CSV.parse("\"#{column}\"",
+ max_field_size: column.size))
+ end
+
+ def test_max_field_size_quote_simple
+ column = "abcde"
+ assert_parse_errors_out("\"#{column}\"",
+ max_field_size: column.size - 1)
+ end
+
+ def test_max_field_size_no_quote_implicitly
+ column = "abcde"
+ assert_parse_errors_out("#{column}",
+ max_field_size: column.size - 1)
+ end
+
+ def test_max_field_size_no_quote_explicitly
+ column = "abcde"
+ assert_parse_errors_out("#{column}",
+ max_field_size: column.size - 1,
+ quote_char: nil)
+ end
+
+ def test_max_field_size_in_extended_column_not_exceeding
+ data = <<~DATA
+ "a","b"
+ "
+ 2
+ ",""
+ DATA
+ assert_nothing_raised(CSV::MalformedCSVError) do
+ CSV.parse(data, max_field_size: 3)
+ end
+ end
+
+ def test_max_field_size_in_extended_column_exceeding
+ data = <<~DATA
+ "a","b"
+ "
+ 2345
+ ",""
+ DATA
+ assert_parse_errors_out(data, max_field_size: 4)
+ end
+
+ def test_row_sep_auto_cr
+ assert_equal([["a"]], CSV.parse("a\r"))
+ end
+
+ def test_row_sep_auto_lf
+ assert_equal([["a"]], CSV.parse("a\n"))
+ end
+
+ def test_row_sep_auto_cr_lf
+ assert_equal([["a"]], CSV.parse("a\r\n"))
+ end
+
+ def test_seeked_string_io
+ input_with_bom = StringIO.new("\ufeffあ,い,う\r\na,b,c\r\n")
+ input_with_bom.read(3)
+ assert_equal([
+ ["あ", "い", "う"],
+ ["a", "b", "c"],
+ ],
+ CSV.new(input_with_bom).each.to_a)
+ end
+
+ private
+ def assert_parse_errors_out(data, **options)
assert_raise(CSV::MalformedCSVError) do
Timeout.timeout(0.2) do
- CSV.parse(*args)
+ CSV.parse(data, **options)
fail("Parse didn't error out")
end
end
diff --git a/test/csv/test_headers.rb b/test/csv/parse/test_header.rb
index 79ccd20c74..e8c3786d68 100755..100644
--- a/test/csv/test_headers.rb
+++ b/test/csv/parse/test_header.rb
@@ -1,24 +1,18 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
-# tc_headers.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+require_relative "../helper"
-require_relative "base"
-
-class TestCSV::Headers < TestCSV
+class TestCSVHeaders < Test::Unit::TestCase
extend DifferentOFS
def setup
super
- @data = <<-END_CSV.gsub(/^\s+/, "")
- first,second,third
- A,B,C
- 1,2,3
- END_CSV
+ @data = <<-CSV
+first,second,third
+A,B,C
+1,2,3
+ CSV
end
def test_first_row
@@ -184,10 +178,10 @@ class TestCSV::Headers < TestCSV
def test_converters
# create test data where headers and fields look alike
- data = <<-END_MATCHING_CSV.gsub(/^\s+/, "")
- 1,2,3
- 1,2,3
- END_MATCHING_CSV
+ data = <<-CSV
+1,2,3
+1,2,3
+ CSV
# normal converters do not affect headers
csv = CSV.parse( data, headers: true,
@@ -224,10 +218,24 @@ class TestCSV::Headers < TestCSV
assert_equal([:one, :two_three], csv.headers)
end
+ def test_builtin_symbol_raw_converter
+ csv = CSV.parse( "a b,c d", headers: true,
+ return_headers: true,
+ header_converters: :symbol_raw )
+ assert_equal([:"a b", :"c d"], csv.headers)
+ end
+
+ def test_builtin_symbol_converter_with_punctuation
+ csv = CSV.parse( "One, Two & Three ($)", headers: true,
+ return_headers: true,
+ header_converters: :symbol )
+ assert_equal([:one, :two_three], csv.headers)
+ end
+
def test_builtin_converters_with_blank_header
csv = CSV.parse( "one,,three", headers: true,
return_headers: true,
- header_converters: [:downcase, :symbol] )
+ header_converters: [:downcase, :symbol, :symbol_raw] )
assert_equal([:one, nil, :three], csv.headers)
end
@@ -250,16 +258,16 @@ class TestCSV::Headers < TestCSV
end
def test_skip_blanks
- @data = <<-END_CSV.gsub(/^ +/, "")
+ @data = <<-CSV
- A,B,C
+A,B,C
- 1,2,3
+1,2,3
- END_CSV
+ CSV
expected = [%w[1 2 3]]
CSV.parse(@data, headers: true, skip_blanks: true) do |row|
@@ -286,7 +294,7 @@ class TestCSV::Headers < TestCSV
assert_equal(%w[first second third], csv.headers) # after headers are read
end
- def test_blank_row_bug_fix
+ def test_blank_row
@data += "\n#{@data}" # add a blank row
# ensure that everything returned is a Row object
@@ -294,4 +302,41 @@ class TestCSV::Headers < TestCSV
assert_instance_of(CSV::Row, row)
end
end
+
+ def test_nil_row_header
+ @data = <<-CSV
+A
+
+1
+ CSV
+
+ csv = CSV.parse(@data, headers: true)
+
+ # ensure nil row creates Row object with headers
+ row = csv[0]
+ assert_equal([["A"], [nil]],
+ [row.headers, row.fields])
+ end
+
+ def test_parse_empty
+ assert_equal(CSV::Table.new([]),
+ CSV.parse("", headers: true))
+ end
+
+ def test_parse_empty_line
+ assert_equal(CSV::Table.new([]),
+ CSV.parse("\n", headers: true))
+ end
+
+ def test_specified_empty
+ assert_equal(CSV::Table.new([],
+ headers: ["header1"]),
+ CSV.parse("", headers: ["header1"]))
+ end
+
+ def test_specified_empty_line
+ assert_equal(CSV::Table.new([CSV::Row.new(["header1"], [])],
+ headers: ["header1"]),
+ CSV.parse("\n", headers: ["header1"]))
+ end
end
diff --git a/test/csv/parse/test_inputs_scanner.rb b/test/csv/parse/test_inputs_scanner.rb
new file mode 100644
index 0000000000..06e1c845d5
--- /dev/null
+++ b/test/csv/parse/test_inputs_scanner.rb
@@ -0,0 +1,63 @@
+require_relative "../helper"
+
+class TestCSVParseInputsScanner < Test::Unit::TestCase
+ include Helper
+
+ def test_scan_keep_over_chunks_nested_back
+ input = CSV::Parser::UnoptimizedStringIO.new("abcdefghijklmnl")
+ scanner = CSV::Parser::InputsScanner.new([input],
+ Encoding::UTF_8,
+ nil,
+ chunk_size: 2)
+ scanner.keep_start
+ assert_equal("abc", scanner.scan_all(/[a-c]+/))
+ scanner.keep_start
+ assert_equal("def", scanner.scan_all(/[d-f]+/))
+ scanner.keep_back
+ scanner.keep_back
+ assert_equal("abcdefg", scanner.scan_all(/[a-g]+/))
+ end
+
+ def test_scan_keep_over_chunks_nested_drop_back
+ input = CSV::Parser::UnoptimizedStringIO.new("abcdefghijklmnl")
+ scanner = CSV::Parser::InputsScanner.new([input],
+ Encoding::UTF_8,
+ nil,
+ chunk_size: 3)
+ scanner.keep_start
+ assert_equal("ab", scanner.scan(/../))
+ scanner.keep_start
+ assert_equal("c", scanner.scan(/./))
+ assert_equal("d", scanner.scan(/./))
+ scanner.keep_drop
+ scanner.keep_back
+ assert_equal("abcdefg", scanner.scan_all(/[a-g]+/))
+ end
+
+ def test_each_line_keep_over_chunks_multibyte
+ input = CSV::Parser::UnoptimizedStringIO.new("ab\n\u{3000}a\n")
+ scanner = CSV::Parser::InputsScanner.new([input],
+ Encoding::UTF_8,
+ nil,
+ chunk_size: 1)
+ each_line = scanner.each_line("\n")
+ assert_equal("ab\n", each_line.next)
+ scanner.keep_start
+ assert_equal("\u{3000}a\n", each_line.next)
+ scanner.keep_back
+ assert_equal("\u{3000}a\n", scanner.scan_all(/[^,]+/))
+ end
+
+ def test_each_line_keep_over_chunks_fit_chunk_size
+ input = CSV::Parser::UnoptimizedStringIO.new("\na")
+ scanner = CSV::Parser::InputsScanner.new([input],
+ Encoding::UTF_8,
+ nil,
+ chunk_size: 1)
+ each_line = scanner.each_line("\n")
+ assert_equal("\n", each_line.next)
+ scanner.keep_start
+ assert_equal("a", each_line.next)
+ scanner.keep_back
+ end
+end
diff --git a/test/csv/parse/test_invalid.rb b/test/csv/parse/test_invalid.rb
new file mode 100644
index 0000000000..ddb59e2b9a
--- /dev/null
+++ b/test/csv/parse/test_invalid.rb
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseInvalid < Test::Unit::TestCase
+ def test_no_column_mixed_new_lines
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse("\n" +
+ "\r")
+ end
+ assert_equal("New line must be <\"\\n\"> not <\"\\r\"> in line 2.",
+ error.message)
+ end
+
+ def test_ignore_invalid_line
+ csv = CSV.new(<<-CSV, headers: true, return_headers: true)
+head1,head2,head3
+aaa,bbb,ccc
+ddd,ee"e.fff
+ggg,hhh,iii
+ CSV
+ headers = ["head1", "head2", "head3"]
+ assert_equal(CSV::Row.new(headers, headers),
+ csv.shift)
+ assert_equal(CSV::Row.new(headers, ["aaa", "bbb", "ccc"]),
+ csv.shift)
+ assert_equal(false, csv.eof?)
+ error = assert_raise(CSV::MalformedCSVError) do
+ csv.shift
+ end
+ assert_equal("Illegal quoting in line 3.",
+ error.message)
+ assert_equal(false, csv.eof?)
+ assert_equal(CSV::Row.new(headers, ["ggg", "hhh", "iii"]),
+ csv.shift)
+ assert_equal(true, csv.eof?)
+ end
+
+ def test_ignore_invalid_line_cr_lf
+ data = <<-CSV
+"1","OK"\r
+"2",""NOT" OK"\r
+"3","OK"\r
+CSV
+ csv = CSV.new(data)
+
+ assert_equal(['1', 'OK'], csv.shift)
+ assert_raise(CSV::MalformedCSVError) { csv.shift }
+ assert_equal(['3', 'OK'], csv.shift)
+ end
+end
diff --git a/test/csv/parse/test_liberal_parsing.rb b/test/csv/parse/test_liberal_parsing.rb
new file mode 100644
index 0000000000..5796d10828
--- /dev/null
+++ b/test/csv/parse/test_liberal_parsing.rb
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseLiberalParsing < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def test_middle_quote_start
+ input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson'
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse_line(input)
+ end
+ assert_equal("Illegal quoting in line 1.",
+ error.message)
+ assert_equal(["Johnson, Dwayne", 'Dwayne "The Rock" Johnson'],
+ CSV.parse_line(input, liberal_parsing: true))
+ end
+
+ def test_middle_quote_end
+ input = '"quoted" field'
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse_line(input)
+ end
+ assert_equal("Any value after quoted field isn't allowed in line 1.",
+ error.message)
+ assert_equal(['"quoted" field'],
+ CSV.parse_line(input, liberal_parsing: true))
+ end
+
+ def test_endline_after_quoted_field_end
+ csv = CSV.new("A\r\n\"B\"\nC\r\n", liberal_parsing: true)
+ assert_equal(["A"], csv.gets)
+ error = assert_raise(CSV::MalformedCSVError) do
+ csv.gets
+ end
+ assert_equal('Illegal end-of-line sequence outside of a quoted field <"\n"> in line 2.',
+ error.message)
+ assert_equal(["C"], csv.gets)
+ end
+
+ def test_quote_after_column_separator
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse_line('is,this "three," or four,fields', liberal_parsing: true)
+ end
+ assert_equal("Unclosed quoted field in line 1.",
+ error.message)
+ end
+
+ def test_quote_before_column_separator
+ assert_equal(["is", 'this "three', ' or four"', "fields"],
+ CSV.parse_line('is,this "three, or four",fields',
+ liberal_parsing: true))
+ end
+
+ def test_backslash_quote
+ assert_equal([
+ "1",
+ "\"Hamlet says, \\\"Seems",
+ "\\\" madam! Nay it is; I know not \\\"seems.\\\"\"",
+ ],
+ CSV.parse_line('1,' +
+ '"Hamlet says, \"Seems,' +
+ '\" madam! Nay it is; I know not \"seems.\""',
+ liberal_parsing: true))
+ end
+
+ def test_space_quote
+ input = <<~CSV
+ Los Angeles, 34°03'N, 118°15'W
+ New York City, 40°42'46"N, 74°00'21"W
+ Paris, 48°51'24"N, 2°21'03"E
+ CSV
+ assert_equal(
+ [
+ ["Los Angeles", " 34°03'N", " 118°15'W"],
+ ["New York City", " 40°42'46\"N", " 74°00'21\"W"],
+ ["Paris", " 48°51'24\"N", " 2°21'03\"E"],
+ ],
+ CSV.parse(input, liberal_parsing: true))
+ end
+
+ def test_double_quote_outside_quote
+ data = %Q{a,""b""}
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse(data)
+ end
+ assert_equal("Any value after quoted field isn't allowed in line 1.",
+ error.message)
+ assert_equal([
+ [["a", %Q{""b""}]],
+ [["a", %Q{"b"}]],
+ ],
+ [
+ CSV.parse(data, liberal_parsing: true),
+ CSV.parse(data,
+ liberal_parsing: {
+ double_quote_outside_quote: true,
+ }),
+ ])
+ end
+
+ class TestBackslashQuote < Test::Unit::TestCase
+ extend ::DifferentOFS
+
+ def test_double_quote_outside_quote
+ data = %Q{a,""b""}
+ assert_equal([
+ [["a", %Q{""b""}]],
+ [["a", %Q{"b"}]],
+ ],
+ [
+ CSV.parse(data,
+ liberal_parsing: {
+ backslash_quote: true
+ }),
+ CSV.parse(data,
+ liberal_parsing: {
+ backslash_quote: true,
+ double_quote_outside_quote: true
+ }),
+ ])
+ end
+
+ def test_unquoted_value
+ data = %q{\"\"a\"\"}
+ assert_equal([
+ [[%q{\"\"a\"\"}]],
+ [[%q{""a""}]],
+ ],
+ [
+ CSV.parse(data, liberal_parsing: true),
+ CSV.parse(data,
+ liberal_parsing: {
+ backslash_quote: true
+ }),
+ ])
+ end
+
+ def test_unquoted_value_multiple_characters_col_sep
+ data = %q{a<\\"b<=>x}
+ assert_equal([[%Q{a<"b}, "x"]],
+ CSV.parse(data,
+ col_sep: "<=>",
+ liberal_parsing: {
+ backslash_quote: true
+ }))
+ end
+
+ def test_quoted_value
+ data = %q{"\"\"a\"\""}
+ assert_equal([
+ [[%q{"\"\"a\"\""}]],
+ [[%q{""a""}]],
+ [[%q{""a""}]],
+ ],
+ [
+ CSV.parse(data, liberal_parsing: true),
+ CSV.parse(data,
+ liberal_parsing: {
+ backslash_quote: true
+ }),
+ CSV.parse(data,
+ liberal_parsing: {
+ backslash_quote: true,
+ double_quote_outside_quote: true
+ }),
+ ])
+ end
+ end
+end
diff --git a/test/csv/parse/test_quote_char_nil.rb b/test/csv/parse/test_quote_char_nil.rb
new file mode 100644
index 0000000000..fc3b646759
--- /dev/null
+++ b/test/csv/parse/test_quote_char_nil.rb
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseQuoteCharNil < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def test_full
+ assert_equal(["a", "b"], CSV.parse_line(%Q{a,b}, quote_char: nil))
+ end
+
+ def test_end_with_nil
+ assert_equal(["a", nil, nil, nil], CSV.parse_line(%Q{a,,,}, quote_char: nil))
+ end
+
+ def test_nil_nil
+ assert_equal([nil, nil], CSV.parse_line(%Q{,}, quote_char: nil))
+ end
+
+ def test_unquoted_value_multiple_characters_col_sep
+ data = %q{a<b<=>x}
+ assert_equal([[%Q{a<b}, "x"]], CSV.parse(data, col_sep: "<=>", quote_char: nil))
+ end
+
+ def test_csv_header_string
+ data = <<~DATA
+ first,second,third
+ A,B,C
+ 1,2,3
+ DATA
+ assert_equal(
+ CSV::Table.new([
+ CSV::Row.new(["my", "new", "headers"], ["first", "second", "third"]),
+ CSV::Row.new(["my", "new", "headers"], ["A", "B", "C"]),
+ CSV::Row.new(["my", "new", "headers"], ["1", "2", "3"])
+ ]),
+ CSV.parse(data, headers: "my,new,headers", quote_char: nil)
+ )
+ end
+
+ def test_comma
+ assert_equal([["a", "b", nil, "d"]],
+ CSV.parse("a,b,,d", col_sep: ",", quote_char: nil))
+ end
+
+ def test_space
+ assert_equal([["a", "b", nil, "d"]],
+ CSV.parse("a b d", col_sep: " ", quote_char: nil))
+ end
+
+ def encode_array(array, encoding)
+ array.collect do |element|
+ element ? element.encode(encoding) : element
+ end
+ end
+
+ def test_space_no_ascii
+ encoding = Encoding::UTF_16LE
+ assert_equal([encode_array(["a", "b", nil, "d"], encoding)],
+ CSV.parse("a b d".encode(encoding),
+ col_sep: " ".encode(encoding),
+ quote_char: nil))
+ end
+
+ def test_multiple_space
+ assert_equal([["a b", nil, "d"]],
+ CSV.parse("a b d", col_sep: " ", quote_char: nil))
+ end
+
+ def test_multiple_characters_leading_empty_fields
+ data = <<-CSV
+<=><=>A<=>B<=>C
+1<=>2<=>3
+ CSV
+ assert_equal([
+ [nil, nil, "A", "B", "C"],
+ ["1", "2", "3"],
+ ],
+ CSV.parse(data, col_sep: "<=>", quote_char: nil))
+ end
+
+ def test_line
+ lines = [
+ "abc,def\n",
+ ]
+ csv = CSV.new(lines.join(""), quote_char: nil)
+ lines.each do |line|
+ csv.shift
+ assert_equal(line, csv.line)
+ end
+ end
+end
diff --git a/test/csv/parse/test_rewind.rb b/test/csv/parse/test_rewind.rb
new file mode 100644
index 0000000000..0aa403b756
--- /dev/null
+++ b/test/csv/parse/test_rewind.rb
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseRewind < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def parse(data, **options)
+ csv = CSV.new(data, **options)
+ records = csv.to_a
+ csv.rewind
+ [records, csv.to_a]
+ end
+
+ def test_default
+ data = <<-CSV
+Ruby,2.6.0,script
+ CSV
+ assert_equal([
+ [["Ruby", "2.6.0", "script"]],
+ [["Ruby", "2.6.0", "script"]],
+ ],
+ parse(data))
+ end
+
+ def test_have_headers
+ data = <<-CSV
+Language,Version,Type
+Ruby,2.6.0,script
+ CSV
+ assert_equal([
+ [CSV::Row.new(["Language", "Version", "Type"],
+ ["Ruby", "2.6.0", "script"])],
+ [CSV::Row.new(["Language", "Version", "Type"],
+ ["Ruby", "2.6.0", "script"])],
+ ],
+ parse(data, headers: true))
+ end
+end
diff --git a/test/csv/parse/test_row_separator.rb b/test/csv/parse/test_row_separator.rb
new file mode 100644
index 0000000000..eaf6adc910
--- /dev/null
+++ b/test/csv/parse/test_row_separator.rb
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseRowSeparator < Test::Unit::TestCase
+ extend DifferentOFS
+ include Helper
+
+ def test_multiple_characters
+ with_chunk_size("1") do
+ assert_equal([["a"], ["b"]],
+ CSV.parse("a\r\nb\r\n", row_sep: "\r\n"))
+ end
+ end
+end
diff --git a/test/csv/parse/test_skip_lines.rb b/test/csv/parse/test_skip_lines.rb
new file mode 100644
index 0000000000..98d67ae51c
--- /dev/null
+++ b/test/csv/parse/test_skip_lines.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseSkipLines < Test::Unit::TestCase
+ extend DifferentOFS
+ include Helper
+
+ def test_default
+ csv = CSV.new("a,b,c\n")
+ assert_nil(csv.skip_lines)
+ end
+
+ def test_regexp
+ csv = <<-CSV
+1
+#2
+ #3
+4
+ CSV
+ assert_equal([
+ ["1"],
+ ["4"],
+ ],
+ CSV.parse(csv, :skip_lines => /\A\s*#/))
+ end
+
+ def test_regexp_quoted
+ csv = <<-CSV
+1
+#2
+"#3"
+4
+ CSV
+ assert_equal([
+ ["1"],
+ ["#3"],
+ ["4"],
+ ],
+ CSV.parse(csv, :skip_lines => /\A\s*#/))
+ end
+
+ def test_string
+ csv = <<-CSV
+1
+.2
+3.
+4
+ CSV
+ assert_equal([
+ ["1"],
+ ["4"],
+ ],
+ CSV.parse(csv, :skip_lines => "."))
+ end
+
+ class RegexStub
+ end
+
+ def test_not_matchable
+ regex_stub = RegexStub.new
+ csv = CSV.new("1\n", :skip_lines => regex_stub)
+ error = assert_raise(ArgumentError) do
+ csv.shift
+ end
+ assert_equal(":skip_lines has to respond to #match: #{regex_stub.inspect}",
+ error.message)
+ end
+
+ class Matchable
+ def initialize(pattern)
+ @pattern = pattern
+ end
+
+ def match(line)
+ @pattern.match(line)
+ end
+ end
+
+ def test_matchable
+ csv = <<-CSV
+1
+# 2
+3
+# 4
+ CSV
+ assert_equal([
+ ["1"],
+ ["3"],
+ ],
+ CSV.parse(csv, :skip_lines => Matchable.new(/\A#/)))
+ end
+
+ def test_multibyte_data
+ # U+3042 HIRAGANA LETTER A
+ # U+3044 HIRAGANA LETTER I
+ # U+3046 HIRAGANA LETTER U
+ value = "\u3042\u3044\u3046"
+ with_chunk_size("5") do
+ assert_equal([[value], [value]],
+ CSV.parse("#{value}\n#{value}\n",
+ :skip_lines => /\A#/))
+ end
+ end
+
+ def test_empty_line_and_liberal_parsing
+ assert_equal([["a", "b"]],
+ CSV.parse("a,b\n",
+ :liberal_parsing => true,
+ :skip_lines => /^$/))
+ end
+
+ def test_crlf
+ assert_equal([["a", "b"]],
+ CSV.parse("a,b\r\n,\r\n",
+ :skip_lines => /^,+$/))
+ end
+end
diff --git a/test/csv/parse/test_strip.rb b/test/csv/parse/test_strip.rb
new file mode 100644
index 0000000000..c5e35209cc
--- /dev/null
+++ b/test/csv/parse/test_strip.rb
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseStrip < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def test_both
+ assert_equal(["a", "b"],
+ CSV.parse_line(%Q{ a , b }, strip: true))
+ end
+
+ def test_left
+ assert_equal(["a", "b"],
+ CSV.parse_line(%Q{ a, b}, strip: true))
+ end
+
+ def test_right
+ assert_equal(["a", "b"],
+ CSV.parse_line(%Q{a ,b }, strip: true))
+ end
+
+ def test_middle
+ assert_equal(["a b"],
+ CSV.parse_line(%Q{a b}, strip: true))
+ end
+
+ def test_quoted
+ assert_equal([" a ", " b "],
+ CSV.parse_line(%Q{" a "," b "}, strip: true))
+ end
+
+ def test_liberal_parsing
+ assert_equal([" a ", "b", " c ", " d "],
+ CSV.parse_line(%Q{" a ", b , " c "," d " },
+ strip: true,
+ liberal_parsing: true))
+ end
+
+ def test_string
+ assert_equal(["a", " b"],
+ CSV.parse_line(%Q{ a , " b" },
+ strip: " "))
+ end
+
+ def test_no_quote
+ assert_equal([" a ", " b "],
+ CSV.parse_line(%Q{" a ", b },
+ strip: %Q{"},
+ quote_char: nil))
+ end
+
+ def test_do_not_strip_cr
+ assert_equal([
+ ["a", "b "],
+ ["a", "b "],
+ ],
+ CSV.parse(%Q{"a" ,"b " \r} +
+ %Q{"a" ,"b " \r},
+ strip: true))
+ end
+
+ def test_do_not_strip_lf
+ assert_equal([
+ ["a", "b "],
+ ["a", "b "],
+ ],
+ CSV.parse(%Q{"a" ,"b " \n} +
+ %Q{"a" ,"b " \n},
+ strip: true))
+ end
+
+ def test_do_not_strip_crlf
+ assert_equal([
+ ["a", "b "],
+ ["a", "b "],
+ ],
+ CSV.parse(%Q{"a" ,"b " \r\n} +
+ %Q{"a" ,"b " \r\n},
+ strip: true))
+ end
+
+ def test_col_sep_incompatible_true
+ message = "The provided strip (true) and " \
+ "col_sep (\\t) options are incompatible."
+ assert_raise_with_message(ArgumentError, message) do
+ CSV.parse_line(%Q{"a"\t"b"\n},
+ col_sep: "\t",
+ strip: true)
+ end
+ end
+
+ def test_col_sep_incompatible_string
+ message = "The provided strip (\\t) and " \
+ "col_sep (\\t) options are incompatible."
+ assert_raise_with_message(ArgumentError, message) do
+ CSV.parse_line(%Q{"a"\t"b"\n},
+ col_sep: "\t",
+ strip: "\t")
+ end
+ end
+
+ def test_col_sep_compatible_string
+ assert_equal(
+ ["a", "b"],
+ CSV.parse_line(%Q{\va\tb\v\n},
+ col_sep: "\t",
+ strip: "\v")
+ )
+ end
+end
diff --git a/test/csv/parse/test_unconverted_fields.rb b/test/csv/parse/test_unconverted_fields.rb
new file mode 100644
index 0000000000..437124ebd3
--- /dev/null
+++ b/test/csv/parse/test_unconverted_fields.rb
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+class TestCSVParseUnconvertedFields < Test::Unit::TestCase
+ extend DifferentOFS
+
+ def setup
+ super
+ @custom = lambda {|field| /\A:(\S.*?)\s*\Z/ =~ field ? $1.to_sym : field}
+
+ @headers = ["first", "second", "third"]
+ @data = <<-CSV
+first,second,third
+1,2,3
+ CSV
+ end
+
+
+ def test_custom
+ row = CSV.parse_line("Numbers,:integer,1,:float,3.015",
+ converters: [:numeric, @custom],
+ unconverted_fields: true)
+ assert_equal([
+ ["Numbers", :integer, 1, :float, 3.015],
+ ["Numbers", ":integer", "1", ":float", "3.015"],
+ ],
+ [
+ row,
+ row.unconverted_fields,
+ ])
+ end
+
+ def test_no_fields
+ row = CSV.parse_line("\n",
+ converters: [:numeric, @custom],
+ unconverted_fields: true)
+ assert_equal([
+ [],
+ [],
+ ],
+ [
+ row,
+ row.unconverted_fields,
+ ])
+ end
+
+ def test_parsed_header
+ row = CSV.parse_line(@data,
+ converters: :numeric,
+ unconverted_fields: true,
+ headers: :first_row)
+ assert_equal([
+ CSV::Row.new(@headers,
+ [1, 2, 3]),
+ ["1", "2", "3"],
+ ],
+ [
+ row,
+ row.unconverted_fields,
+ ])
+ end
+
+ def test_return_headers
+ row = CSV.parse_line(@data,
+ converters: :numeric,
+ unconverted_fields: true,
+ headers: :first_row,
+ return_headers: true)
+ assert_equal([
+ CSV::Row.new(@headers,
+ @headers),
+ @headers,
+ ],
+ [
+ row,
+ row.unconverted_fields,
+ ])
+ end
+
+ def test_header_converters
+ row = CSV.parse_line(@data,
+ converters: :numeric,
+ unconverted_fields: true,
+ headers: :first_row,
+ return_headers: true,
+ header_converters: :symbol)
+ assert_equal([
+ CSV::Row.new(@headers.collect(&:to_sym),
+ @headers),
+ @headers,
+ ],
+ [
+ row,
+ row.unconverted_fields,
+ ])
+ end
+
+ def test_specified_headers
+ row = CSV.parse_line("\n",
+ converters: :numeric,
+ unconverted_fields: true,
+ headers: %w{my new headers},
+ return_headers: true,
+ header_converters: :symbol)
+ assert_equal([
+ CSV::Row.new([:my, :new, :headers],
+ ["my", "new", "headers"]),
+ [],
+ ],
+ [
+ row,
+ row.unconverted_fields,
+ ])
+ end
+end
diff --git a/test/csv/test_csv_writing.rb b/test/csv/test_csv_writing.rb
deleted file mode 100755
index 704c1d7ddc..0000000000
--- a/test/csv/test_csv_writing.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
-
-# tc_csv_writing.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
-
-require_relative "base"
-
-class TestCSV::Writing < TestCSV
- extend DifferentOFS
-
- def test_writing
- [ ["\t", ["\t"]],
- ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]],
- ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]],
- ["\"\"\"\n\",\"\"\"\n\"", ["\"\n", "\"\n"]],
- ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]],
- ["\"\"", [""]],
- ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]],
- ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]],
- ["foo,\"\r\",baz", ["foo", "\r", "baz"]],
- ["foo,\"\",baz", ["foo", "", "baz"]],
- ["\",\"", [","]],
- ["foo", ["foo"]],
- [",,", [nil, nil, nil]],
- [",", [nil, nil]],
- ["foo,\"\n\",baz", ["foo", "\n", "baz"]],
- ["foo,,baz", ["foo", nil, "baz"]],
- ["\"\"\"\r\",\"\"\"\r\"", ["\"\r", "\"\r"]],
- ["\",\",\",\"", [",", ","]],
- ["foo,bar,", ["foo", "bar", nil]],
- [",foo,bar", [nil, "foo", "bar"]],
- ["foo,bar", ["foo", "bar"]],
- [";", [";"]],
- ["\t,\t", ["\t", "\t"]],
- ["foo,\"\r\n\r\",baz", ["foo", "\r\n\r", "baz"]],
- ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]],
- ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]],
- [";,;", [";", ";"]],
- ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]],
- ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]],
- ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]],
- ["\"\"", [""]],
- ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]],
- ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]],
- ["foo,\"\r\",baz", ["foo", "\r", "baz"]],
- ["foo,\"\",baz", ["foo", "", "baz"]],
- ["foo", ["foo"]],
- [",,", [nil, nil, nil]],
- [",", [nil, nil]],
- ["foo,\"\n\",baz", ["foo", "\n", "baz"]],
- ["foo,,baz", ["foo", nil, "baz"]],
- ["foo,bar", ["foo", "bar"]],
- ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]],
- ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]],
- [%Q{a,b}, ["a", "b"]],
- [%Q{a,"""b"""}, ["a", "\"b\""]],
- [%Q{a,"""b"}, ["a", "\"b"]],
- [%Q{a,"b"""}, ["a", "b\""]],
- [%Q{a,"\nb"""}, ["a", "\nb\""]],
- [%Q{a,"""\nb"}, ["a", "\"\nb"]],
- [%Q{a,"""\nb\n"""}, ["a", "\"\nb\n\""]],
- [%Q{a,"""\nb\n""",}, ["a", "\"\nb\n\"", nil]],
- [%Q{a,,,}, ["a", nil, nil, nil]],
- [%Q{,}, [nil, nil]],
- [%Q{"",""}, ["", ""]],
- [%Q{""""}, ["\""]],
- [%Q{"""",""}, ["\"",""]],
- [%Q{,""}, [nil,""]],
- [%Q{,"\r"}, [nil,"\r"]],
- [%Q{"\r\n,"}, ["\r\n,"]],
- [%Q{"\r\n,",}, ["\r\n,", nil]] ].each do |test_case|
- assert_equal(test_case.first + $/, CSV.generate_line(test_case.last))
- end
- end
-
- def test_col_sep
- assert_equal( "a;b;;c\n", CSV.generate_line( ["a", "b", nil, "c"],
- col_sep: ";" ) )
- assert_equal( "a\tb\t\tc\n", CSV.generate_line( ["a", "b", nil, "c"],
- col_sep: "\t" ) )
- end
-
- def test_row_sep
- assert_equal( "a,b,,c\r\n", CSV.generate_line( ["a", "b", nil, "c"],
- row_sep: "\r\n" ) )
- end
-
- def test_force_quotes
- assert_equal( %Q{"1","b","","already ""quoted"""\n},
- CSV.generate_line( [1, "b", nil, %Q{already "quoted"}],
- force_quotes: true ) )
- end
-end
diff --git a/test/csv/test_data_converters.rb b/test/csv/test_data_converters.rb
index 89b6dd1dfd..c20a5d1f4b 100755..100644
--- a/test/csv/test_data_converters.rb
+++ b/test/csv/test_data_converters.rb
@@ -1,24 +1,13 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
-# tc_data_converters.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+require_relative "helper"
-require_relative "base"
-
-class TestCSV::DataConverters < TestCSV
+class TestCSVDataConverters < Test::Unit::TestCase
extend DifferentOFS
def setup
super
- @data = "Numbers,:integer,1,:float,3.015"
- @parser = CSV.new(@data)
-
- @custom = lambda { |field| field =~ /\A:(\S.*?)\s*\Z/ ? $1.to_sym : field }
-
@win_safe_time_str = Time.now.strftime("%a %b %d %H:%M:%S %Y")
end
@@ -66,198 +55,136 @@ class TestCSV::DataConverters < TestCSV
assert_instance_of(String, CSV::Converters[:date_time]["junk"])
end
- def test_convert_with_builtin_integer
- # setup parser...
- assert_respond_to(@parser, :convert)
- assert_nothing_raised(Exception) { @parser.convert(:integer) }
-
- # and use
- assert_equal(["Numbers", ":integer", 1, ":float", "3.015"], @parser.shift)
+ def test_builtin_date_time_converter_iso8601_date
+ iso8601_string = "2018-01-14"
+ datetime = DateTime.new(2018, 1, 14)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_convert_with_builtin_float
- # setup parser...
- assert_respond_to(@parser, :convert)
- assert_nothing_raised(Exception) { @parser.convert(:float) }
-
- # and use
- assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015], @parser.shift)
+ def test_builtin_date_time_converter_iso8601_minute
+ iso8601_string = "2018-01-14T22:25"
+ datetime = DateTime.new(2018, 1, 14, 22, 25)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_convert_order_float_integer
- # floats first, then integers...
- assert_nothing_raised(Exception) do
- @parser.convert(:float)
- @parser.convert(:integer)
- end
-
- # gets us nothing but floats
- assert_equal( [String, String, Float, String, Float],
- @parser.shift.map { |field| field.class } )
+ def test_builtin_date_time_converter_iso8601_second
+ iso8601_string = "2018-01-14T22:25:19"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_convert_order_integer_float
- # integers have precendance...
- assert_nothing_raised(Exception) do
- @parser.convert(:integer)
- @parser.convert(:float)
- end
-
- # gives us proper number conversion
- assert_equal( [String, String, Fixnum, String, Float],
- @parser.shift.map { |field| field.class } )
+ def test_builtin_date_time_converter_iso8601_under_second
+ iso8601_string = "2018-01-14T22:25:19.1"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19.1)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_builtin_numeric_combo_converter
- # setup parser...
- assert_nothing_raised(Exception) { @parser.convert(:numeric) }
-
- # and use
- assert_equal( [String, String, Fixnum, String, Float],
- @parser.shift.map { |field| field.class } )
+ def test_builtin_date_time_converter_iso8601_under_second_offset
+ iso8601_string = "2018-01-14T22:25:19.1+09:00"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19.1, "+9")
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_builtin_all_nested_combo_converter
- # setup parser...
- @data << ",#{@win_safe_time_str}" # add a DateTime field
- @parser = CSV.new(@data) # reset parser
- assert_nothing_raised(Exception) { @parser.convert(:all) }
-
- # and use
- assert_equal( [String, String, Fixnum, String, Float, DateTime],
- @parser.shift.map { |field| field.class } )
+ def test_builtin_date_time_converter_iso8601_offset
+ iso8601_string = "2018-01-14T22:25:19+09:00"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19, "+9")
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_convert_with_custom_code
- # define custom converter...
- assert_nothing_raised(Exception) do
- @parser.convert { |field| field =~ /\A:(\S.*?)\s*\Z/ ? $1.to_sym : field }
- end
-
- # and use
- assert_equal(["Numbers", :integer, "1", :float, "3.015"], @parser.shift)
+ def test_builtin_date_time_converter_iso8601_utc
+ iso8601_string = "2018-01-14T22:25:19Z"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][iso8601_string])
end
- def test_convert_with_custom_code_mix
- # mix built-in and custom...
- assert_nothing_raised(Exception) { @parser.convert(:numeric) }
- assert_nothing_raised(Exception) { @parser.convert(&@custom) }
-
- # and use
- assert_equal(["Numbers", :integer, 1, :float, 3.015], @parser.shift)
+ def test_builtin_date_time_converter_rfc3339_minute
+ rfc3339_string = "2018-01-14 22:25"
+ datetime = DateTime.new(2018, 1, 14, 22, 25)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
end
- def test_convert_with_custom_code_using_field_info
- # define custom converter that uses field information...
- assert_nothing_raised(Exception) do
- @parser.convert do |field, info|
- assert_equal(1, info.line)
- info.index == 4 ? Float(field).floor : field
- end
- end
-
- # and use
- assert_equal(["Numbers", ":integer", "1", ":float", 3], @parser.shift)
+ def test_builtin_date_time_converter_rfc3339_second
+ rfc3339_string = "2018-01-14 22:25:19"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
end
- def test_convert_with_custom_code_using_field_info_header
- @parser = CSV.new(@data, headers: %w{one two three four five})
-
- # define custom converter that uses field header information...
- assert_nothing_raised(Exception) do
- @parser.convert do |field, info|
- info.header == "three" ? Integer(field) * 100 : field
- end
- end
+ def test_builtin_date_time_converter_rfc3339_under_second
+ rfc3339_string = "2018-01-14 22:25:19.1"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19.1)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- # and use
- assert_equal( ["Numbers", ":integer", 100, ":float", "3.015"],
- @parser.shift.fields )
+ def test_builtin_date_time_converter_rfc3339_under_second_offset
+ rfc3339_string = "2018-01-14 22:25:19.1+09:00"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19.1, "+9")
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
end
- def test_shortcut_interface
- assert_equal( ["Numbers", ":integer", 1, ":float", 3.015],
- CSV.parse_line(@data, converters: :numeric) )
+ def test_builtin_date_time_converter_rfc3339_offset
+ rfc3339_string = "2018-01-14 22:25:19+09:00"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19, "+9")
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- assert_equal( ["Numbers", ":integer", 1, ":float", 3.015],
- CSV.parse_line(@data, converters: [:integer, :float]) )
+ def test_builtin_date_time_converter_rfc3339_utc
+ rfc3339_string = "2018-01-14 22:25:19Z"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- assert_equal( ["Numbers", :integer, 1, :float, 3.015],
- CSV.parse_line(@data, converters: [:numeric, @custom]) )
+ def test_builtin_date_time_converter_rfc3339_tab_minute
+ rfc3339_string = "2018-01-14\t22:25"
+ datetime = DateTime.new(2018, 1, 14, 22, 25)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
end
- def test_unconverted_fields
- [ [ @data,
- ["Numbers", :integer, 1, :float, 3.015],
- %w{Numbers :integer 1 :float 3.015} ],
- ["\n", Array.new, Array.new] ].each do |test, fields, unconverted|
- row = nil
- assert_nothing_raised(Exception) do
- row = CSV.parse_line( test,
- converters: [:numeric, @custom],
- unconverted_fields: true )
- end
- assert_not_nil(row)
- assert_equal(fields, row)
- assert_respond_to(row, :unconverted_fields)
- assert_equal(unconverted, row.unconverted_fields)
- end
+ def test_builtin_date_time_converter_rfc3339_tab_second
+ rfc3339_string = "2018-01-14\t22:25:19"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- data = <<-END_CSV.gsub(/^\s+/, "")
- first,second,third
- 1,2,3
- END_CSV
- row = nil
- assert_nothing_raised(Exception) do
- row = CSV.parse_line( data,
- converters: :numeric,
- unconverted_fields: true,
- headers: :first_row )
- end
- assert_not_nil(row)
- assert_equal([["first", 1], ["second", 2], ["third", 3]], row.to_a)
- assert_respond_to(row, :unconverted_fields)
- assert_equal(%w{1 2 3}, row.unconverted_fields)
+ def test_builtin_date_time_converter_rfc3339_tab_under_second
+ rfc3339_string = "2018-01-14\t22:25:19.1"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19.1)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- assert_nothing_raised(Exception) do
- row = CSV.parse_line( data,
- converters: :numeric,
- unconverted_fields: true,
- headers: :first_row,
- return_headers: true )
- end
- assert_not_nil(row)
- assert_equal( [%w{first first}, %w{second second}, %w{third third}],
- row.to_a )
- assert_respond_to(row, :unconverted_fields)
- assert_equal(%w{first second third}, row.unconverted_fields)
+ def test_builtin_date_time_converter_rfc3339_tab_under_second_offset
+ rfc3339_string = "2018-01-14\t22:25:19.1+09:00"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19.1, "+9")
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- assert_nothing_raised(Exception) do
- row = CSV.parse_line( data,
- converters: :numeric,
- unconverted_fields: true,
- headers: :first_row,
- return_headers: true,
- header_converters: :symbol )
- end
- assert_not_nil(row)
- assert_equal( [[:first, "first"], [:second, "second"], [:third, "third"]],
- row.to_a )
- assert_respond_to(row, :unconverted_fields)
- assert_equal(%w{first second third}, row.unconverted_fields)
+ def test_builtin_date_time_converter_rfc3339_tab_offset
+ rfc3339_string = "2018-01-14\t22:25:19+09:00"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19, "+9")
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
+ end
- assert_nothing_raised(Exception) do
- row = CSV.parse_line( data,
- converters: :numeric,
- unconverted_fields: true,
- headers: %w{my new headers},
- return_headers: true,
- header_converters: :symbol )
- end
- assert_not_nil(row)
- assert_equal( [[:my, "my"], [:new, "new"], [:headers, "headers"]],
- row.to_a )
- assert_respond_to(row, :unconverted_fields)
- assert_equal(Array.new, row.unconverted_fields)
+ def test_builtin_date_time_converter_rfc3339_tab_utc
+ rfc3339_string = "2018-01-14\t22:25:19Z"
+ datetime = DateTime.new(2018, 1, 14, 22, 25, 19)
+ assert_equal(datetime,
+ CSV::Converters[:date_time][rfc3339_string])
end
end
diff --git a/test/csv/test_encodings.rb b/test/csv/test_encodings.rb
index dc45692ce4..f08d551f69 100755..100644
--- a/test/csv/test_encodings.rb
+++ b/test/csv/test_encodings.rb
@@ -1,16 +1,11 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
-# tc_encodings.rb
-#
-# Created by James Edward Gray II on 2008-09-13.
-# Copyright 2008 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+require_relative "helper"
-require_relative "base"
-
-class TestCSV::Encodings < TestCSV
+class TestCSVEncodings < Test::Unit::TestCase
extend DifferentOFS
+ include Helper
def setup
super
@@ -247,6 +242,18 @@ class TestCSV::Encodings < TestCSV
assert_equal("UTF-8", data.to_csv.encoding.name)
end
+ def test_encoding_is_not_upgraded_for_non_ascii_content_during_writing_as_needed
+ data = ["\u00c0".encode("ISO-8859-1"), "\u3042"]
+ assert_equal([
+ "ISO-8859-1",
+ "UTF-8",
+ ],
+ data.collect {|field| field.encoding.name})
+ assert_raise(Encoding::CompatibilityError) do
+ data.to_csv
+ end
+ end
+
def test_explicit_encoding
bug9766 = '[ruby-core:62113] [Bug #9766]'
s = CSV.generate(encoding: "Windows-31J") do |csv|
@@ -255,26 +262,85 @@ class TestCSV::Encodings < TestCSV
assert_equal(["foo,\u3042\n".encode(Encoding::Windows_31J), Encoding::Windows_31J], [s, s.encoding], bug9766)
end
+ def test_encoding_with_default_internal
+ with_default_internal(Encoding::UTF_8) do
+ s = CSV.generate(String.new(encoding: Encoding::Big5), encoding: Encoding::Big5) do |csv|
+ csv << ["漢字"]
+ end
+ assert_equal(["漢字\n".encode(Encoding::Big5), Encoding::Big5], [s, s.encoding])
+ end
+ end
+
+ def test_row_separator_detection_with_invalid_encoding
+ csv = CSV.new("invalid,\xF8\r\nvalid,x\r\n".force_encoding("UTF-8"),
+ encoding: "UTF-8")
+ assert_equal("\r\n", csv.row_sep)
+ end
+
+ def test_invalid_encoding_row_error
+ csv = CSV.new("valid,x\rinvalid,\xF8\r".force_encoding("UTF-8"),
+ encoding: "UTF-8", row_sep: "\r")
+ error = assert_raise(CSV::MalformedCSVError) do
+ csv.shift
+ csv.shift
+ end
+ assert_equal("Invalid byte sequence in UTF-8 in line 2.",
+ error.message)
+ end
+
+ def test_string_input_transcode
+ # U+3042 HIRAGANA LETTER A
+ # U+3044 HIRAGANA LETTER I
+ # U+3046 HIRAGANA LETTER U
+ value = "\u3042\u3044\u3046"
+ csv = CSV.new(value, encoding: "UTF-8:EUC-JP")
+ assert_equal([[value.encode("EUC-JP")]],
+ csv.read)
+ end
+
+ def test_string_input_set_encoding_string
+ # U+3042 HIRAGANA LETTER A
+ # U+3044 HIRAGANA LETTER I
+ # U+3046 HIRAGANA LETTER U
+ value = "\u3042\u3044\u3046".encode("EUC-JP")
+ csv = CSV.new(value.dup.force_encoding("UTF-8"), encoding: "EUC-JP")
+ assert_equal([[value.encode("EUC-JP")]],
+ csv.read)
+ end
+
+ def test_string_input_set_encoding_encoding
+ # U+3042 HIRAGANA LETTER A
+ # U+3044 HIRAGANA LETTER I
+ # U+3046 HIRAGANA LETTER U
+ value = "\u3042\u3044\u3046".encode("EUC-JP")
+ csv = CSV.new(value.dup.force_encoding("UTF-8"),
+ encoding: Encoding.find("EUC-JP"))
+ assert_equal([[value.encode("EUC-JP")]],
+ csv.read)
+ end
+
private
- def assert_parses(fields, encoding, options = { })
+ def assert_parses(fields, encoding, **options)
encoding = Encoding.find(encoding) unless encoding.is_a? Encoding
orig_fields = fields
- fields = encode_ary(fields, encoding)
- data = ary_to_data(fields, options)
- parsed = CSV.parse(data, options)
+ fields = encode_ary(fields, encoding)
+ data = ary_to_data(fields, **options)
+ parsed = CSV.parse(data, **options)
assert_equal(fields, parsed)
parsed.flatten.each_with_index do |field, i|
assert_equal(encoding, field.encoding, "Field[#{i + 1}] was transcoded.")
end
File.open(@temp_csv_path, "wb") {|f| f.print(data)}
- CSV.open(@temp_csv_path, "rb:#{encoding}", options) do |csv|
+ CSV.open(@temp_csv_path, "rb:#{encoding}", **options) do |csv|
csv.each_with_index do |row, i|
assert_equal(fields[i], row)
end
end
begin
- CSV.open(@temp_csv_path, "rb:#{encoding}:#{__ENCODING__}", options) do |csv|
+ CSV.open(@temp_csv_path,
+ "rb:#{encoding}:#{__ENCODING__}",
+ **options) do |csv|
csv.each_with_index do |row, i|
assert_equal(orig_fields[i], row)
end
@@ -282,7 +348,7 @@ class TestCSV::Encodings < TestCSV
rescue Encoding::ConverterNotFoundError
end
options[:encoding] = encoding.name
- CSV.open(@temp_csv_path, options) do |csv|
+ CSV.open(@temp_csv_path, **options) do |csv|
csv.each_with_index do |row, i|
assert_equal(fields[i], row)
end
@@ -291,7 +357,7 @@ class TestCSV::Encodings < TestCSV
options[:external_encoding] = encoding.name
options[:internal_encoding] = __ENCODING__.name
begin
- CSV.open(@temp_csv_path, options) do |csv|
+ CSV.open(@temp_csv_path, **options) do |csv|
csv.each_with_index do |row, i|
assert_equal(orig_fields[i], row)
end
@@ -304,7 +370,7 @@ class TestCSV::Encodings < TestCSV
ary.map { |row| row.map { |field| field.encode(encoding) } }
end
- def ary_to_data(ary, options = { })
+ def ary_to_data(ary, **options)
encoding = ary.flatten.first.encoding
quote_char = (options[:quote_char] || '"').encode(encoding)
col_sep = (options[:col_sep] || ",").encode(encoding)
@@ -316,9 +382,9 @@ class TestCSV::Encodings < TestCSV
}.join('').encode(encoding)
end
- def encode_for_tests(data, options = { })
- yield ary_to_data(encode_ary(data, "UTF-8"), options)
- yield ary_to_data(encode_ary(data, "UTF-16BE"), options)
+ def encode_for_tests(data, **options)
+ yield ary_to_data(encode_ary(data, "UTF-8"), **options)
+ yield ary_to_data(encode_ary(data, "UTF-16BE"), **options)
end
def each_encoding
diff --git a/test/csv/test_features.rb b/test/csv/test_features.rb
index e31465710b..d6eb2dc13b 100755..100644
--- a/test/csv/test_features.rb
+++ b/test/csv/test_features.rb
@@ -1,21 +1,15 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
-
-# tc_features.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
begin
require "zlib"
rescue LoadError
end
-require_relative "base"
+require_relative "helper"
require "tempfile"
-class TestCSV::Features < TestCSV
+class TestCSVFeatures < Test::Unit::TestCase
extend DifferentOFS
TEST_CASES = [ [%Q{a,b}, ["a", "b"]],
@@ -38,12 +32,12 @@ class TestCSV::Features < TestCSV
def setup
super
- @sample_data = <<-END_DATA.gsub(/^ +/, "")
- line,1,abc
- line,2,"def\nghi"
+ @sample_data = <<-CSV
+line,1,abc
+line,2,"def\nghi"
- line,4,jkl
- END_DATA
+line,4,jkl
+ CSV
@csv = CSV.new(@sample_data)
end
@@ -58,27 +52,52 @@ class TestCSV::Features < TestCSV
assert_equal([",,,", nil], CSV.parse_line(",,,;", col_sep: ";"))
end
+ def test_col_sep_nil
+ assert_raise_with_message(ArgumentError,
+ ":col_sep must be 1 or more characters: nil") do
+ CSV.parse(@sample_data, col_sep: nil)
+ end
+ end
+
+ def test_col_sep_empty
+ assert_raise_with_message(ArgumentError,
+ ":col_sep must be 1 or more characters: \"\"") do
+ CSV.parse(@sample_data, col_sep: "")
+ end
+ end
+
def test_row_sep
- assert_raise(CSV::MalformedCSVError) do
- CSV.parse_line("1,2,3\n,4,5\r\n", row_sep: "\r\n")
+ error = assert_raise(CSV::MalformedCSVError) do
+ CSV.parse_line("1,2,3\n,4,5\r\n", row_sep: "\r\n")
end
+ assert_equal("Unquoted fields do not allow new line <\"\\n\"> in line 1.",
+ error.message)
assert_equal( ["1", "2", "3\n", "4", "5"],
CSV.parse_line(%Q{1,2,"3\n",4,5\r\n}, row_sep: "\r\n"))
end
def test_quote_char
TEST_CASES.each do |test_case|
- assert_equal( test_case.last.map { |t| t.tr('"', "'") unless t.nil? },
- CSV.parse_line( test_case.first.tr('"', "'"),
- quote_char: "'" ) )
+ assert_equal(test_case.last.map {|t| t.tr('"', "'") unless t.nil?},
+ CSV.parse_line(test_case.first.tr('"', "'"),
+ quote_char: "'" ))
+ end
+ end
+
+ def test_quote_char_special_regexp_char
+ TEST_CASES.each do |test_case|
+ assert_equal(test_case.last.map {|t| t.tr('"', "|") unless t.nil?},
+ CSV.parse_line(test_case.first.tr('"', "|"),
+ quote_char: "|"))
end
end
- def test_bug_8405
+ def test_quote_char_special_regexp_char_liberal_parsing
TEST_CASES.each do |test_case|
- assert_equal( test_case.last.map { |t| t.tr('"', "|") unless t.nil? },
- CSV.parse_line( test_case.first.tr('"', "|"),
- quote_char: "|" ) )
+ assert_equal(test_case.last.map {|t| t.tr('"', "|") unless t.nil?},
+ CSV.parse_line(test_case.first.tr('"', "|"),
+ quote_char: "|",
+ liberal_parsing: true))
end
end
@@ -103,6 +122,20 @@ class TestCSV::Features < TestCSV
assert_equal($/, CSV.new(STDERR).row_sep)
end
+ def test_line
+ lines = [
+ %Q(\u{3000}abc,def\n),
+ %Q(\u{3000}abc,"d\nef"\n),
+ %Q(\u{3000}abc,"d\r\nef"\n),
+ %Q(\u{3000}abc,"d\ref")
+ ]
+ csv = CSV.new(lines.join(''))
+ lines.each do |line|
+ csv.shift
+ assert_equal(line, csv.line)
+ end
+ end
+
def test_lineno
assert_equal(5, @sample_data.lines.to_a.size)
@@ -123,9 +156,12 @@ class TestCSV::Features < TestCSV
end
def test_unknown_options
- assert_raise_with_message(ArgumentError, /unknown/) {
+ assert_raise_with_message(ArgumentError, /unknown keyword/) {
CSV.new(@sample_data, unknown: :error)
}
+ assert_raise_with_message(ArgumentError, /unknown keyword/) {
+ CSV.new(@sample_data, universal_newline: true)
+ }
end
def test_skip_blanks
@@ -186,29 +222,19 @@ class TestCSV::Features < TestCSV
end
# reported by Kev Jackson
- def test_failing_to_escape_col_sep_bug_fix
+ def test_failing_to_escape_col_sep
assert_nothing_raised(Exception) { CSV.new(String.new, col_sep: "|") }
end
# reported by Chris Roos
- def test_failing_to_reset_headers_in_rewind_bug_fix
+ def test_failing_to_reset_headers_in_rewind
csv = CSV.new("forename,surname", headers: true, return_headers: true)
csv.each {|row| assert_predicate row, :header_row?}
csv.rewind
csv.each {|row| assert_predicate row, :header_row?}
end
- # reported by Dave Burt
- def test_leading_empty_fields_with_multibyte_col_sep_bug_fix
- data = <<-END_DATA.gsub(/^\s+/, "")
- <=><=>A<=>B<=>C
- 1<=>2<=>3
- END_DATA
- parsed = CSV.parse(data, col_sep: "<=>")
- assert_equal([[nil, nil, "A", "B", "C"], ["1", "2", "3"]], parsed)
- end
-
- def test_gzip_reader_bug_fix
+ def test_gzip_reader
zipped = nil
assert_nothing_raised(NoMethodError) do
zipped = CSV.new(
@@ -222,7 +248,7 @@ class TestCSV::Features < TestCSV
zipped.close
end if defined?(Zlib::GzipReader)
- def test_gzip_writer_bug_fix
+ def test_gzip_writer
Tempfile.create(%w"temp .gz") {|tempfile|
tempfile.close
file = tempfile.path
@@ -263,65 +289,71 @@ class TestCSV::Features < TestCSV
end
def test_inspect_shows_headers_when_available
- CSV.new("one,two,three\n1,2,3\n", headers: true) do |csv|
- assert_include(csv.inspect, "headers:true", "Header hint not shown.")
- csv.shift # load headers
- assert_match(/headers:\[[^\]]+\]/, csv.inspect)
- end
+ csv = CSV.new("one,two,three\n1,2,3\n", headers: true)
+ assert_include(csv.inspect, "headers:true", "Header hint not shown.")
+ csv.shift # load headers
+ assert_match(/headers:\[[^\]]+\]/, csv.inspect)
end
def test_inspect_encoding_is_ascii_compatible
- CSV.new("one,two,three\n1,2,3\n".encode("UTF-16BE")) do |csv|
- assert_send([Encoding, :compatible?,
- Encoding.find("US-ASCII"), csv.inspect.encoding],
- "inspect() was not ASCII compatible.")
- end
+ csv = CSV.new("one,two,three\n1,2,3\n".encode("UTF-16BE"))
+ assert_send([Encoding, :compatible?,
+ Encoding.find("US-ASCII"), csv.inspect.encoding],
+ "inspect() was not ASCII compatible.")
end
def test_version
assert_not_nil(CSV::VERSION)
assert_instance_of(String, CSV::VERSION)
assert_predicate(CSV::VERSION, :frozen?)
- assert_match(/\A\d\.\d\.\d\Z/, CSV::VERSION)
+ assert_match(/\A\d\.\d\.\d\z/, CSV::VERSION)
end
- def test_accepts_comment_skip_lines_option
- assert_nothing_raised(ArgumentError) do
- CSV.new(@sample_data, :skip_lines => /\A\s*#/)
- end
+ def test_table_nil_equality
+ assert_nothing_raised(NoMethodError) { CSV.parse("test", headers: true) == nil }
end
- def test_accepts_comment_defaults_to_nil
- c = CSV.new(@sample_data)
- assert_nil(c.skip_lines)
+ # non-seekable input stream for testing https://github.com/ruby/csv/issues/44
+ class DummyIO
+ extend Forwardable
+ def_delegators :@io, :gets, :read, :pos, :eof? # no seek or rewind!
+ def initialize(data)
+ @io = StringIO.new(data)
+ end
end
- class RegexStub
+ def test_line_separator_autodetection_for_non_seekable_input_lf
+ c = CSV.new(DummyIO.new("one,two,three\nfoo,bar,baz\n"))
+ assert_equal [["one", "two", "three"], ["foo", "bar", "baz"]], c.each.to_a
end
- def test_requires_skip_lines_to_call_match
- regex_stub = RegexStub.new
- assert_raise_with_message(ArgumentError, /skip_lines/) do
- CSV.new(@sample_data, :skip_lines => regex_stub)
- end
+ def test_line_separator_autodetection_for_non_seekable_input_cr
+ c = CSV.new(DummyIO.new("one,two,three\rfoo,bar,baz\r"))
+ assert_equal [["one", "two", "three"], ["foo", "bar", "baz"]], c.each.to_a
end
- def test_comment_rows_are_ignored
- sample_data = "line,1,a\n#not,a,line\nline,2,b\n #also,no,line"
- c = CSV.new sample_data, :skip_lines => /\A\s*#/
- assert_equal [["line", "1", "a"], ["line", "2", "b"]], c.each.to_a
+ def test_line_separator_autodetection_for_non_seekable_input_cr_lf
+ c = CSV.new(DummyIO.new("one,two,three\r\nfoo,bar,baz\r\n"))
+ assert_equal [["one", "two", "three"], ["foo", "bar", "baz"]], c.each.to_a
end
- def test_quoted_skip_line_markers_are_ignored
- sample_data = "line,1,a\n\"#not\",a,line\nline,2,b"
- c = CSV.new sample_data, :skip_lines => /\A\s*#/
- assert_equal [["line", "1", "a"], ["#not", "a", "line"], ["line", "2", "b"]], c.each.to_a
+ def test_line_separator_autodetection_for_non_seekable_input_1024_over_lf
+ table = (1..10).map { |row| (1..200).map { |col| "row#{row}col#{col}" }.to_a }.to_a
+ input = table.map { |line| line.join(",") }.join("\n")
+ c = CSV.new(DummyIO.new(input))
+ assert_equal table, c.each.to_a
end
- def test_string_works_like_a_regexp
- sample_data = "line,1,a\n#(not,a,line\nline,2,b\n also,#no,line"
- c = CSV.new sample_data, :skip_lines => "#"
- assert_equal [["line", "1", "a"], ["line", "2", "b"]], c.each.to_a
+ def test_line_separator_autodetection_for_non_seekable_input_1024_over_cr_lf
+ table = (1..10).map { |row| (1..200).map { |col| "row#{row}col#{col}" }.to_a }.to_a
+ input = table.map { |line| line.join(",") }.join("\r\n")
+ c = CSV.new(DummyIO.new(input))
+ assert_equal table, c.each.to_a
end
+ def test_line_separator_autodetection_for_non_seekable_input_many_cr_only
+ # input with lots of CRs (to make sure no bytes are lost due to look-ahead)
+ c = CSV.new(DummyIO.new("foo\r" + "\r" * 9999 + "bar\r"))
+ assert_equal [["foo"]] + [[]] * 9999 + [["bar"]], c.each.to_a
+ end
end
diff --git a/test/csv/test_interface.rb b/test/csv/test_interface.rb
deleted file mode 100755
index d6bf470f6b..0000000000
--- a/test/csv/test_interface.rb
+++ /dev/null
@@ -1,368 +0,0 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
-
-# tc_interface.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
-
-require_relative "base"
-require "tempfile"
-
-class TestCSV::Interface < TestCSV
- extend DifferentOFS
-
- def setup
- super
- @tempfile = Tempfile.new(%w"temp .csv")
- @tempfile.close
- @path = @tempfile.path
-
- File.open(@path, "wb") do |file|
- file << "1\t2\t3\r\n"
- file << "4\t5\r\n"
- end
-
- @expected = [%w{1 2 3}, %w{4 5}]
- end
-
- def teardown
- @tempfile.close(true)
- super
- end
-
- ### Test Read Interface ###
-
- def test_foreach
- CSV.foreach(@path, col_sep: "\t", row_sep: "\r\n") do |row|
- assert_equal(@expected.shift, row)
- end
- end
-
- def test_foreach_enum
- CSV.foreach(@path, col_sep: "\t", row_sep: "\r\n").zip(@expected) do |row, exp|
- assert_equal(exp, row)
- end
- end
-
- def test_open_and_close
- csv = CSV.open(@path, "r+", col_sep: "\t", row_sep: "\r\n")
- assert_not_nil(csv)
- assert_instance_of(CSV, csv)
- assert_not_predicate(csv, :closed?)
- csv.close
- assert_predicate(csv, :closed?)
-
- ret = CSV.open(@path) do |new_csv|
- csv = new_csv
- assert_instance_of(CSV, new_csv)
- "Return value."
- end
- assert_predicate(csv, :closed?)
- assert_equal("Return value.", ret)
- end
-
- def test_parse
- data = File.binread(@path)
- assert_equal( @expected,
- CSV.parse(data, col_sep: "\t", row_sep: "\r\n") )
-
- CSV.parse(data, col_sep: "\t", row_sep: "\r\n") do |row|
- assert_equal(@expected.shift, row)
- end
- end
-
- def test_parse_line
- row = CSV.parse_line("1;2;3", col_sep: ";")
- assert_not_nil(row)
- assert_instance_of(Array, row)
- assert_equal(%w{1 2 3}, row)
-
- # shortcut interface
- row = "1;2;3".parse_csv(col_sep: ";")
- assert_not_nil(row)
- assert_instance_of(Array, row)
- assert_equal(%w{1 2 3}, row)
- end
-
- def test_parse_line_with_empty_lines
- assert_equal(nil, CSV.parse_line("")) # to signal eof
- assert_equal(Array.new, CSV.parse_line("\n1,2,3"))
- end
-
- def test_read_and_readlines
- assert_equal( @expected,
- CSV.read(@path, col_sep: "\t", row_sep: "\r\n") )
- assert_equal( @expected,
- CSV.readlines(@path, col_sep: "\t", row_sep: "\r\n") )
-
-
- data = CSV.open(@path, col_sep: "\t", row_sep: "\r\n") do |csv|
- csv.read
- end
- assert_equal(@expected, data)
- data = CSV.open(@path, col_sep: "\t", row_sep: "\r\n") do |csv|
- csv.readlines
- end
- assert_equal(@expected, data)
- end
-
- def test_table
- table = CSV.table(@path, col_sep: "\t", row_sep: "\r\n")
- assert_instance_of(CSV::Table, table)
- assert_equal([[:"1", :"2", :"3"], [4, 5, nil]], table.to_a)
- end
-
- def test_shift # aliased as gets() and readline()
- CSV.open(@path, "rb+", col_sep: "\t", row_sep: "\r\n") do |csv|
- assert_equal(@expected.shift, csv.shift)
- assert_equal(@expected.shift, csv.shift)
- assert_equal(nil, csv.shift)
- end
- end
-
- def test_enumerators_are_supported
- CSV.open(@path, col_sep: "\t", row_sep: "\r\n") do |csv|
- enum = csv.each
- assert_instance_of(Enumerator, enum)
- assert_equal(@expected.shift, enum.next)
- end
- end
-
- def test_nil_is_not_acceptable
- assert_raise_with_message ArgumentError, "Cannot parse nil as CSV" do
- CSV.new(nil)
- end
- end
-
- ### Test Write Interface ###
-
- def test_generate
- str = CSV.generate do |csv| # default empty String
- assert_instance_of(CSV, csv)
- assert_equal(csv, csv << [1, 2, 3])
- assert_equal(csv, csv << [4, nil, 5])
- end
- assert_not_nil(str)
- assert_instance_of(String, str)
- assert_equal("1,2,3\n4,,5\n", str)
-
- CSV.generate(str) do |csv| # appending to a String
- assert_equal(csv, csv << ["last", %Q{"row"}])
- end
- assert_equal(%Q{1,2,3\n4,,5\nlast,"""row"""\n}, str)
- end
-
- def test_generate_line
- line = CSV.generate_line(%w{1 2 3}, col_sep: ";")
- assert_not_nil(line)
- assert_instance_of(String, line)
- assert_equal("1;2;3\n", line)
-
- # shortcut interface
- line = %w{1 2 3}.to_csv(col_sep: ";")
- assert_not_nil(line)
- assert_instance_of(String, line)
- assert_equal("1;2;3\n", line)
- end
-
- def test_write_header_detection
- File.unlink(@path)
-
- headers = %w{a b c}
- CSV.open(@path, "w", headers: true) do |csv|
- csv << headers
- csv << %w{1 2 3}
- assert_equal(headers, csv.instance_variable_get(:@headers))
- end
- end
-
- def test_write_lineno
- File.unlink(@path)
-
- CSV.open(@path, "w") do |csv|
- lines = 20
- lines.times { csv << %w{a b c} }
- assert_equal(lines, csv.lineno)
- end
- end
-
- def test_write_hash
- File.unlink(@path)
-
- lines = [{a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}]
- CSV.open( @path, "wb", headers: true,
- header_converters: :symbol ) do |csv|
- csv << lines.first.keys
- lines.each { |line| csv << line }
- end
- CSV.open( @path, "rb", headers: true,
- converters: :all,
- header_converters: :symbol ) do |csv|
- csv.each { |line| assert_equal(lines.shift, line.to_hash) }
- end
- end
-
- def test_write_hash_with_string_keys
- File.unlink(@path)
-
- lines = [{a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}]
- CSV.open( @path, "wb", headers: true ) do |csv|
- csv << lines.first.keys
- lines.each { |line| csv << line }
- end
- CSV.open( @path, "rb", headers: true ) do |csv|
- csv.each do |line|
- csv.headers.each_with_index do |header, h|
- keys = line.to_hash.keys
- assert_instance_of(String, keys[h])
- assert_same(header, keys[h])
- end
- end
- end
- end
-
- def test_write_hash_with_headers_array
- File.unlink(@path)
-
- lines = [{a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}]
- CSV.open(@path, "wb", headers: [:b, :a, :c]) do |csv|
- lines.each { |line| csv << line }
- end
-
- # test writing fields in the correct order
- File.open(@path, "rb") do |f|
- assert_equal("2,1,3", f.gets.strip)
- assert_equal("5,4,6", f.gets.strip)
- end
-
- # test reading CSV with headers
- CSV.open( @path, "rb", headers: [:b, :a, :c],
- converters: :all ) do |csv|
- csv.each { |line| assert_equal(lines.shift, line.to_hash) }
- end
- end
-
- def test_write_hash_with_headers_string
- File.unlink(@path)
-
- lines = [{"a" => 1, "b" => 2, "c" => 3}, {"a" => 4, "b" => 5, "c" => 6}]
- CSV.open(@path, "wb", headers: "b|a|c", col_sep: "|") do |csv|
- lines.each { |line| csv << line }
- end
-
- # test writing fields in the correct order
- File.open(@path, "rb") do |f|
- assert_equal("2|1|3", f.gets.strip)
- assert_equal("5|4|6", f.gets.strip)
- end
-
- # test reading CSV with headers
- CSV.open( @path, "rb", headers: "b|a|c",
- col_sep: "|",
- converters: :all ) do |csv|
- csv.each { |line| assert_equal(lines.shift, line.to_hash) }
- end
- end
-
- def test_write_headers
- File.unlink(@path)
-
- lines = [{"a" => 1, "b" => 2, "c" => 3}, {"a" => 4, "b" => 5, "c" => 6}]
- CSV.open( @path, "wb", headers: "b|a|c",
- write_headers: true,
- col_sep: "|" ) do |csv|
- lines.each { |line| csv << line }
- end
-
- # test writing fields in the correct order
- File.open(@path, "rb") do |f|
- assert_equal("b|a|c", f.gets.strip)
- assert_equal("2|1|3", f.gets.strip)
- assert_equal("5|4|6", f.gets.strip)
- end
-
- # test reading CSV with headers
- CSV.open( @path, "rb", headers: true,
- col_sep: "|",
- converters: :all ) do |csv|
- csv.each { |line| assert_equal(lines.shift, line.to_hash) }
- end
- end
-
- def test_append # aliased add_row() and puts()
- File.unlink(@path)
-
- CSV.open(@path, "wb", col_sep: "\t", row_sep: "\r\n") do |csv|
- @expected.each { |row| csv << row }
- end
-
- test_shift
-
- # same thing using CSV::Row objects
- File.unlink(@path)
-
- CSV.open(@path, "wb", col_sep: "\t", row_sep: "\r\n") do |csv|
- @expected.each { |row| csv << CSV::Row.new(Array.new, row) }
- end
-
- test_shift
- end
-
- ### Test Read and Write Interface ###
-
- def test_filter
- assert_respond_to(CSV, :filter)
-
- expected = [[1, 2, 3], [4, 5]]
- CSV.filter( "1;2;3\n4;5\n", (result = String.new),
- in_col_sep: ";", out_col_sep: ",",
- converters: :all ) do |row|
- assert_equal(row, expected.shift)
- row.map! { |n| n * 2 }
- row << "Added\r"
- end
- assert_equal("2,4,6,\"Added\r\"\n8,10,\"Added\r\"\n", result)
- end
-
- def test_instance
- csv = String.new
-
- first = nil
- assert_nothing_raised(Exception) do
- first = CSV.instance(csv, col_sep: ";")
- first << %w{a b c}
- end
-
- assert_equal("a;b;c\n", csv)
-
- second = nil
- assert_nothing_raised(Exception) do
- second = CSV.instance(csv, col_sep: ";")
- second << [1, 2, 3]
- end
-
- assert_equal(first.object_id, second.object_id)
- assert_equal("a;b;c\n1;2;3\n", csv)
-
- # shortcuts
- assert_equal(STDOUT, CSV.instance.instance_eval { @io })
- assert_equal(STDOUT, CSV { |new_csv| new_csv.instance_eval { @io } })
- end
-
- def test_options_are_not_modified
- opt = {}.freeze
- assert_nothing_raised { CSV.foreach(@path, opt) }
- assert_nothing_raised { CSV.open(@path, opt){} }
- assert_nothing_raised { CSV.parse("", opt) }
- assert_nothing_raised { CSV.parse_line("", opt) }
- assert_nothing_raised { CSV.read(@path, opt) }
- assert_nothing_raised { CSV.readlines(@path, opt) }
- assert_nothing_raised { CSV.table(@path, opt) }
- assert_nothing_raised { CSV.generate(opt){} }
- assert_nothing_raised { CSV.generate_line([], opt) }
- assert_nothing_raised { CSV.filter("", "", opt){} }
- assert_nothing_raised { CSV.instance("", opt) }
- end
-end
diff --git a/test/csv/test_patterns.rb b/test/csv/test_patterns.rb
new file mode 100644
index 0000000000..881f03a3a4
--- /dev/null
+++ b/test/csv/test_patterns.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+class TestCSVPatternMatching < Test::Unit::TestCase
+
+ def test_hash
+ case CSV::Row.new(%i{A B C}, [1, 2, 3])
+ in B: b, C: c
+ assert_equal([2, 3], [b, c])
+ end
+ end
+
+ def test_hash_rest
+ case CSV::Row.new(%i{A B C}, [1, 2, 3])
+ in B: b, **rest
+ assert_equal([2, { A: 1, C: 3 }], [b, rest])
+ end
+ end
+
+ def test_array
+ case CSV::Row.new(%i{A B C}, [1, 2, 3])
+ in *, matched
+ assert_equal(3, matched)
+ end
+ end
+end
diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb
index 3acceea234..b717945041 100755..100644
--- a/test/csv/test_row.rb
+++ b/test/csv/test_row.rb
@@ -1,15 +1,9 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
-# tc_row.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+require_relative "helper"
-require_relative "base"
-
-class TestCSV::Row < TestCSV
+class TestCSVRow < Test::Unit::TestCase
extend DifferentOFS
def setup
@@ -64,6 +58,9 @@ class TestCSV::Row < TestCSV
# by index
assert_equal(3, @row.field(2))
+ # by range
+ assert_equal([2,3], @row.field(1..2))
+
# missing
assert_nil(@row.field("Missing"))
assert_nil(@row.field(10))
@@ -103,6 +100,19 @@ class TestCSV::Row < TestCSV
def test_has_key?
assert_equal(true, @row.has_key?('B'))
assert_equal(false, @row.has_key?('foo'))
+
+ # aliases
+ assert_equal(true, @row.header?('B'))
+ assert_equal(false, @row.header?('foo'))
+
+ assert_equal(true, @row.include?('B'))
+ assert_equal(false, @row.include?('foo'))
+
+ assert_equal(true, @row.member?('B'))
+ assert_equal(false, @row.member?('foo'))
+
+ assert_equal(true, @row.key?('B'))
+ assert_equal(false, @row.key?('foo'))
end
def test_set_field
@@ -205,9 +215,20 @@ class TestCSV::Row < TestCSV
# by header
assert_equal(["C", 3], @row.delete("C"))
- # using a block
+ end
+
+ def test_delete_if
assert_equal(@row, @row.delete_if { |h, f| h == "A" and not f.nil? })
- assert_equal([["A", nil]], @row.to_a)
+ assert_equal([["B", 2], ["C", 3], ["A", nil]], @row.to_a)
+ end
+
+ def test_delete_if_without_block
+ enum = @row.delete_if
+ assert_instance_of(Enumerator, enum)
+ assert_equal(@row.size, enum.size)
+
+ assert_equal(@row, enum.each { |h, f| h == "A" and not f.nil? })
+ assert_equal([["B", 2], ["C", 3], ["A", nil]], @row.to_a)
end
def test_fields
@@ -248,12 +269,6 @@ class TestCSV::Row < TestCSV
end
def test_queries
- # headers
- assert_send([@row, :header?, "A"])
- assert_send([@row, :header?, "C"])
- assert_not_send([@row, :header?, "Z"])
- assert_send([@row, :include?, "A"]) # alias
-
# fields
assert(@row.field?(4))
assert(@row.field?(nil))
@@ -277,6 +292,27 @@ class TestCSV::Row < TestCSV
# verify that we can chain the call
assert_equal(@row, @row.each { })
+
+ # without block
+ ary = @row.to_a
+ enum = @row.each
+ assert_instance_of(Enumerator, enum)
+ assert_equal(@row.size, enum.size)
+ enum.each do |pair|
+ assert_equal(ary.first.first, pair.first)
+ assert_equal(ary.shift.last, pair.last)
+ end
+ end
+
+ def test_each_pair
+ assert_equal([
+ ["A", 1],
+ ["B", 2],
+ ["C", 3],
+ ["A", 4],
+ ["A", nil],
+ ],
+ @row.each_pair.to_a)
end
def test_enumerable
@@ -298,7 +334,7 @@ class TestCSV::Row < TestCSV
def test_to_hash
hash = @row.to_hash
- assert_equal({"A" => nil, "B" => 2, "C" => 3}, hash)
+ assert_equal({"A" => @row["A"], "B" => @row["B"], "C" => @row["C"]}, hash)
hash.keys.each_with_index do |string_key, h|
assert_predicate(string_key, :frozen?)
assert_same(string_key, @row.headers[h])
@@ -352,4 +388,48 @@ class TestCSV::Row < TestCSV
r = @row == []
assert_equal false, r
end
+
+ def test_dig_by_index
+ assert_equal(2, @row.dig(1))
+
+ assert_nil(@row.dig(100))
+ end
+
+ def test_dig_by_header
+ assert_equal(2, @row.dig("B"))
+
+ assert_nil(@row.dig("Missing"))
+ end
+
+ def test_dig_cell
+ row = CSV::Row.new(%w{A}, [["foo", ["bar", ["baz"]]]])
+
+ assert_equal("foo", row.dig(0, 0))
+ assert_equal("bar", row.dig(0, 1, 0))
+
+ assert_equal("foo", row.dig("A", 0))
+ assert_equal("bar", row.dig("A", 1, 0))
+ end
+
+ def test_dig_cell_no_dig
+ row = CSV::Row.new(%w{A}, ["foo"])
+
+ assert_raise(TypeError) do
+ row.dig(0, 0)
+ end
+ assert_raise(TypeError) do
+ row.dig("A", 0)
+ end
+ end
+
+ def test_dup
+ row = CSV::Row.new(["A"], ["foo"])
+ dupped_row = row.dup
+ dupped_row["A"] = "bar"
+ assert_equal(["foo", "bar"],
+ [row["A"], dupped_row["A"]])
+ dupped_row.delete("A")
+ assert_equal(["foo", nil],
+ [row["A"], dupped_row["A"]])
+ end
end
diff --git a/test/csv/test_table.rb b/test/csv/test_table.rb
index 44e9d4a8be..e8ab74044e 100755..100644
--- a/test/csv/test_table.rb
+++ b/test/csv/test_table.rb
@@ -1,15 +1,9 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
-# tc_table.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
+require_relative "helper"
-require_relative "base"
-
-class TestCSV::Table < TestCSV
+class TestCSVTable < Test::Unit::TestCase
extend DifferentOFS
def setup
@@ -22,9 +16,11 @@ class TestCSV::Table < TestCSV
@header_table = CSV::Table.new(
[CSV::Row.new(%w{A B C}, %w{A B C}, true)] + @rows
)
+
+ @header_only_table = CSV::Table.new([], headers: %w{A B C})
end
- def test_initialze
+ def test_initialize
assert_not_nil(@table)
assert_instance_of(CSV::Table, @table)
end
@@ -43,6 +39,11 @@ class TestCSV::Table < TestCSV
assert_equal(:row, rows.mode)
assert_equal(@table, rows)
+ col_or_row = rows.by_col_or_row
+ assert_equal(:row, rows.mode)
+ assert_equal(:col_or_row, col_or_row.mode)
+ assert_equal(@table, col_or_row)
+
# destructive mode changing calls
assert_equal(@table, @table.by_row!)
assert_equal(:row, @table.mode)
@@ -59,6 +60,17 @@ class TestCSV::Table < TestCSV
assert_equal Array.new, t.headers
end
+ def test_headers_only
+ assert_equal(%w[A B C], @header_only_table.headers)
+ end
+
+ def test_headers_modified_by_row
+ table = CSV::Table.new([], headers: ["A", "B"])
+ table << ["a", "b"]
+ table.first << {"C" => "c"}
+ assert_equal(["A", "B", "C"], table.headers)
+ end
+
def test_index
##################
### Mixed Mode ###
@@ -67,6 +79,9 @@ class TestCSV::Table < TestCSV
@rows.each_index { |i| assert_equal(@rows[i], @table[i]) }
assert_equal(nil, @table[100]) # empty row
+ # by row with Range
+ assert_equal([@table[1], @table[2]], @table[1..2])
+
# by col
@rows.first.headers.each do |header|
assert_equal(@rows.map { |row| row[header] }, @table[header])
@@ -146,13 +161,13 @@ class TestCSV::Table < TestCSV
@table.to_a )
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C,Type,Index
- 1,100,3,data,1
- 4,200,6,data,2
- 10,,12,data,3
- 13,,15,data,
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C,Type,Index
+1,100,3,data,1
+4,200,6,data,2
+10,,12,data,3
+13,,15,data,
+ CSV
# with headers
@header_table["Type"] = "data"
@@ -216,6 +231,17 @@ class TestCSV::Table < TestCSV
# verify that we can chain the call
assert_equal(@table, @table.each { })
+ # without block
+ enum = @table.each
+ assert_instance_of(Enumerator, enum)
+ assert_equal(@table.size, enum.size)
+
+ i = 0
+ enum.each do |row|
+ assert_equal(@rows[i], row)
+ i += 1
+ end
+
###################
### Column Mode ###
###################
@@ -227,6 +253,17 @@ class TestCSV::Table < TestCSV
assert_equal(@table[header], column)
end
+ # without block
+ enum = @table.each
+ assert_instance_of(Enumerator, enum)
+ assert_equal(@table.headers.size, enum.size)
+
+ headers = @table.headers
+ enum.each do |header, column|
+ assert_equal(headers.shift, header)
+ assert_equal(@table[header], column)
+ end
+
############################
### One Shot Mode Change ###
############################
@@ -237,6 +274,31 @@ class TestCSV::Table < TestCSV
@table.each { |row| assert_instance_of(CSV::Row, row) }
end
+ def test_each_by_col_duplicated_headers
+ table = CSV.parse(<<-CSV, headers: true)
+a,a,,,b
+1,2,3,4,5
+11,12,13,14,15
+ CSV
+ assert_equal([
+ ["a", ["1", "11"]],
+ ["a", ["2", "12"]],
+ [nil, ["3", "13"]],
+ [nil, ["4", "14"]],
+ ["b", ["5", "15"]],
+ ],
+ table.by_col.each.to_a)
+ end
+
+ def test_each_split
+ yielded_values = []
+ @table.each do |column1, column2, column3|
+ yielded_values << [column1, column2, column3]
+ end
+ assert_equal(@rows.collect(&:to_a),
+ yielded_values)
+ end
+
def test_enumerable
assert_equal( @rows.values_at(0, 2),
@table.select { |row| (row["B"] % 2).zero? } )
@@ -253,12 +315,12 @@ class TestCSV::Table < TestCSV
end
def test_to_csv
- csv = <<-END_CSV.gsub(/^\s+/, "")
- A,B,C
- 1,2,3
- 4,5,6
- 7,8,9
- END_CSV
+ csv = <<-CSV
+A,B,C
+1,2,3
+4,5,6
+7,8,9
+ CSV
# normal conversion
assert_equal(csv, @table.to_csv)
@@ -274,6 +336,43 @@ class TestCSV::Table < TestCSV
assert_equal(csv, @header_table.to_csv)
end
+ def test_to_csv_limit_positive
+ assert_equal(<<-CSV, @table.to_csv(limit: 2))
+A,B,C
+1,2,3
+4,5,6
+ CSV
+ end
+
+ def test_to_csv_limit_positive_over
+ assert_equal(<<-CSV, @table.to_csv(limit: 5))
+A,B,C
+1,2,3
+4,5,6
+7,8,9
+ CSV
+ end
+
+ def test_to_csv_limit_zero
+ assert_equal(<<-CSV, @table.to_csv(limit: 0))
+A,B,C
+ CSV
+ end
+
+ def test_to_csv_limit_negative
+ assert_equal(<<-CSV, @table.to_csv(limit: -2))
+A,B,C
+1,2,3
+4,5,6
+ CSV
+ end
+
+ def test_to_csv_limit_negative_over
+ assert_equal(<<-CSV, @table.to_csv(limit: -5))
+A,B,C
+ CSV
+ end
+
def test_append
# verify that we can chain the call
assert_equal(@table, @table << [10, 11, 12])
@@ -286,7 +385,7 @@ class TestCSV::Table < TestCSV
assert_equal(CSV::Row.new(%w[A B C], [13, 14, 15]), @table[-1])
end
- def test_delete_mixed
+ def test_delete_mixed_one
##################
### Mixed Mode ###
##################
@@ -297,11 +396,33 @@ class TestCSV::Table < TestCSV
assert_equal(@rows.map { |row| row["A"] }, @table.delete("A"))
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- B,C
- 2,3
- 8,9
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+B,C
+2,3
+8,9
+ CSV
+ end
+
+ def test_delete_mixed_multiple
+ ##################
+ ### Mixed Mode ###
+ ##################
+ # delete row and col
+ second_row = @rows[1]
+ a_col = @rows.map { |row| row["A"] }
+ a_col_without_second_row = a_col[0..0] + a_col[2..-1]
+ assert_equal([
+ second_row,
+ a_col_without_second_row,
+ ],
+ @table.delete(1, "A"))
+
+ # verify resulting table
+ assert_equal(<<-CSV, @table.to_csv)
+B,C
+2,3
+8,9
+ CSV
end
def test_delete_column
@@ -314,12 +435,12 @@ class TestCSV::Table < TestCSV
assert_equal(@rows.map { |row| row["C"] }, @table.delete("C"))
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- B
- 2
- 5
- 8
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+B
+2
+5
+8
+ CSV
end
def test_delete_row
@@ -332,11 +453,11 @@ class TestCSV::Table < TestCSV
assert_raise(TypeError) { @table.delete("C") }
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C
- 1,2,3
- 7,8,9
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C
+1,2,3
+7,8,9
+ CSV
end
def test_delete_with_blank_rows
@@ -353,10 +474,28 @@ class TestCSV::Table < TestCSV
assert_equal(@table, @table.delete_if { |row| (row["B"] % 2).zero? })
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C
- 4,5,6
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C
+4,5,6
+ CSV
+ end
+
+ def test_delete_if_row_without_block
+ ######################
+ ### Mixed/Row Mode ###
+ ######################
+ enum = @table.delete_if
+ assert_instance_of(Enumerator, enum)
+ assert_equal(@table.size, enum.size)
+
+ # verify that we can chain the call
+ assert_equal(@table, enum.each { |row| (row["B"] % 2).zero? })
+
+ # verify resulting table
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C
+4,5,6
+ CSV
end
def test_delete_if_column
@@ -366,12 +505,46 @@ class TestCSV::Table < TestCSV
@table.by_col!
assert_equal(@table, @table.delete_if { |h, v| h > "A" })
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A
- 1
- 4
- 7
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A
+1
+4
+7
+ CSV
+ end
+
+ def test_delete_if_column_without_block
+ ###################
+ ### Column Mode ###
+ ###################
+ @table.by_col!
+
+ enum = @table.delete_if
+ assert_instance_of(Enumerator, enum)
+ assert_equal(@table.headers.size, enum.size)
+
+ assert_equal(@table, enum.each { |h, v| h > "A" })
+ assert_equal(<<-CSV, @table.to_csv)
+A
+1
+4
+7
+ CSV
+ end
+
+ def test_delete_headers_only
+ ###################
+ ### Column Mode ###
+ ###################
+ @header_only_table.by_col!
+
+ # delete by index
+ assert_equal([], @header_only_table.delete(0))
+ assert_equal(%w[B C], @header_only_table.headers)
+
+ # delete by header
+ assert_equal([], @header_only_table.delete("C"))
+ assert_equal(%w[B], @header_only_table.headers)
end
def test_values_at
@@ -429,6 +602,90 @@ class TestCSV::Table < TestCSV
assert_send([Encoding, :compatible?,
Encoding.find("US-ASCII"),
@table.inspect.encoding],
- "inspect() was not ASCII compatible." )
+ "inspect() was not ASCII compatible." )
+ end
+
+ def test_inspect_with_rows
+ additional_rows = [ CSV::Row.new(%w{A B C}, [101, 102, 103]),
+ CSV::Row.new(%w{A B C}, [104, 105, 106]),
+ CSV::Row.new(%w{A B C}, [107, 108, 109]) ]
+ table = CSV::Table.new(@rows + additional_rows)
+ str_table = table.inspect
+
+ assert_equal(<<-CSV, str_table)
+#<CSV::Table mode:col_or_row row_count:7>
+A,B,C
+1,2,3
+4,5,6
+7,8,9
+101,102,103
+104,105,106
+ CSV
+ end
+
+ def test_dig_mixed
+ # by row
+ assert_equal(@rows[0], @table.dig(0))
+ assert_nil(@table.dig(100)) # empty row
+
+ # by col
+ assert_equal([2, 5, 8], @table.dig("B"))
+ assert_equal([nil] * @rows.size, @table.dig("Z")) # empty col
+
+ # by row then col
+ assert_equal(2, @table.dig(0, 1))
+ assert_equal(6, @table.dig(1, "C"))
+
+ # by col then row
+ assert_equal(5, @table.dig("B", 1))
+ assert_equal(9, @table.dig("C", 2))
+ end
+
+ def test_dig_by_column
+ @table.by_col!
+
+ assert_equal([2, 5, 8], @table.dig(1))
+ assert_equal([2, 5, 8], @table.dig("B"))
+
+ # by col then row
+ assert_equal(5, @table.dig("B", 1))
+ assert_equal(9, @table.dig("C", 2))
+ end
+
+ def test_dig_by_row
+ @table.by_row!
+
+ assert_equal(@rows[1], @table.dig(1))
+ assert_raise(TypeError) { @table.dig("B") }
+
+ # by row then col
+ assert_equal(2, @table.dig(0, 1))
+ assert_equal(6, @table.dig(1, "C"))
+ end
+
+ def test_dig_cell
+ table = CSV::Table.new([CSV::Row.new(["A"], [["foo", ["bar", ["baz"]]]])])
+
+ # by row, col then cell
+ assert_equal("foo", table.dig(0, "A", 0))
+ assert_equal(["baz"], table.dig(0, "A", 1, 1))
+
+ # by col, row then cell
+ assert_equal("foo", table.dig("A", 0, 0))
+ assert_equal(["baz"], table.dig("A", 0, 1, 1))
+ end
+
+ def test_dig_cell_no_dig
+ table = CSV::Table.new([CSV::Row.new(["A"], ["foo"])])
+
+ # by row, col then cell
+ assert_raise(TypeError) do
+ table.dig(0, "A", 0)
+ end
+
+ # by col, row then cell
+ assert_raise(TypeError) do
+ table.dig("A", 0, 0)
+ end
end
end
diff --git a/test/csv/ts_all.rb b/test/csv/ts_all.rb
deleted file mode 100644
index 389384168b..0000000000
--- a/test/csv/ts_all.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env ruby -w
-# encoding: UTF-8
-
-# ts_all.rb
-#
-# Created by James Edward Gray II on 2005-10-31.
-# Copyright 2005 James Edward Gray II. You can redistribute or modify this code
-# under the terms of Ruby's license.
-
-require "test/unit"
-
-require "test_csv_parsing"
-require "test_features"
-require "test_interface"
-require "test_csv_writing"
-require "test_data_converters"
-require "test_row"
-require "test_table"
-require "test_headers"
-require "test_encodings"
diff --git a/test/csv/write/test_converters.rb b/test/csv/write/test_converters.rb
new file mode 100644
index 0000000000..0e0080b4c5
--- /dev/null
+++ b/test/csv/write/test_converters.rb
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+module TestCSVWriteConverters
+ def test_one
+ assert_equal(%Q[=a,=b,=c\n],
+ generate_line(["a", "b", "c"],
+ write_converters: ->(value) {"=" + value}))
+ end
+
+ def test_multiple
+ assert_equal(%Q[=a_,=b_,=c_\n],
+ generate_line(["a", "b", "c"],
+ write_converters: [
+ ->(value) {"=" + value},
+ ->(value) {value + "_"},
+ ]))
+ end
+
+ def test_nil_value
+ assert_equal(%Q[a,NaN,29\n],
+ generate_line(["a", nil, 29],
+ write_nil_value: "NaN"))
+ end
+
+ def test_empty_value
+ assert_equal(%Q[a,,29\n],
+ generate_line(["a", "", 29],
+ write_empty_value: nil))
+ end
+end
+
+class TestCSVWriteConvertersGenerateLine < Test::Unit::TestCase
+ include TestCSVWriteConverters
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate_line(row, **kwargs)
+ end
+end
+
+class TestCSVWriteConvertersGenerate < Test::Unit::TestCase
+ include TestCSVWriteConverters
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate(**kwargs) do |csv|
+ csv << row
+ end
+ end
+end
diff --git a/test/csv/write/test_force_quotes.rb b/test/csv/write/test_force_quotes.rb
new file mode 100644
index 0000000000..622dcb021b
--- /dev/null
+++ b/test/csv/write/test_force_quotes.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+module TestCSVWriteForceQuotes
+ def test_default
+ assert_equal(%Q[1,2,3#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["1", "2", "3"]))
+ end
+
+ def test_true
+ assert_equal(%Q["1","2","3"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["1", "2", "3"],
+ force_quotes: true))
+ end
+
+ def test_false
+ assert_equal(%Q[1,2,3#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["1", "2", "3"],
+ force_quotes: false))
+ end
+
+ def test_field_name
+ assert_equal(%Q["1",2,"3"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["1", "2", "3"],
+ headers: ["a", "b", "c"],
+ force_quotes: ["a", :c]))
+ end
+
+ def test_field_name_without_headers
+ force_quotes = ["a", "c"]
+ error = assert_raise(ArgumentError) do
+ generate_line(["1", "2", "3"],
+ force_quotes: force_quotes)
+ end
+ assert_equal(":headers is required when you use field name " +
+ "in :force_quotes: " +
+ "#{force_quotes.first.inspect}: #{force_quotes.inspect}",
+ error.message)
+ end
+
+ def test_field_index
+ assert_equal(%Q["1",2,"3"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["1", "2", "3"],
+ force_quotes: [0, 2]))
+ end
+
+ def test_field_unknown
+ force_quotes = [1.1]
+ error = assert_raise(ArgumentError) do
+ generate_line(["1", "2", "3"],
+ force_quotes: force_quotes)
+ end
+ assert_equal(":force_quotes element must be field index or field name: " +
+ "#{force_quotes.first.inspect}: #{force_quotes.inspect}",
+ error.message)
+ end
+end
+
+class TestCSVWriteForceQuotesGenerateLine < Test::Unit::TestCase
+ include TestCSVWriteForceQuotes
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate_line(row, **kwargs)
+ end
+end
+
+class TestCSVWriteForceQuotesGenerate < Test::Unit::TestCase
+ include TestCSVWriteForceQuotes
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate(**kwargs) do |csv|
+ csv << row
+ end
+ end
+end
diff --git a/test/csv/write/test_general.rb b/test/csv/write/test_general.rb
new file mode 100644
index 0000000000..677119e1ae
--- /dev/null
+++ b/test/csv/write/test_general.rb
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+module TestCSVWriteGeneral
+ include Helper
+
+ def test_tab
+ assert_equal("\t#{$INPUT_RECORD_SEPARATOR}",
+ generate_line(["\t"]))
+ end
+
+ def test_quote_character
+ assert_equal(%Q[foo,"""",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q["], "baz"]))
+ end
+
+ def test_quote_character_double
+ assert_equal(%Q[foo,"""""",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q[""], "baz"]))
+ end
+
+ def test_quote
+ assert_equal(%Q[foo,"""bar""",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q["bar"], "baz"]))
+ end
+
+ def test_quote_lf
+ assert_equal(%Q["""\n","""\n"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([%Q["\n], %Q["\n]]))
+ end
+
+ def test_quote_cr
+ assert_equal(%Q["""\r","""\r"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([%Q["\r], %Q["\r]]))
+ end
+
+ def test_quote_last
+ assert_equal(%Q[foo,"bar"""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q[bar"]]))
+ end
+
+ def test_quote_lf_last
+ assert_equal(%Q[foo,"\nbar"""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q[\nbar"]]))
+ end
+
+ def test_quote_lf_value_lf
+ assert_equal(%Q[foo,"""\nbar\n"""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q["\nbar\n"]]))
+ end
+
+ def test_quote_lf_value_lf_nil
+ assert_equal(%Q[foo,"""\nbar\n""",#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", %Q["\nbar\n"], nil]))
+ end
+
+ def test_cr
+ assert_equal(%Q[foo,"\r",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "\r", "baz"]))
+ end
+
+ def test_lf
+ assert_equal(%Q[foo,"\n",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "\n", "baz"]))
+ end
+
+ def test_cr_lf
+ assert_equal(%Q[foo,"\r\n",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "\r\n", "baz"]))
+ end
+
+ def test_cr_dot_lf
+ assert_equal(%Q[foo,"\r.\n",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "\r.\n", "baz"]))
+ end
+
+ def test_cr_lf_cr
+ assert_equal(%Q[foo,"\r\n\r",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "\r\n\r", "baz"]))
+ end
+
+ def test_cr_lf_lf
+ assert_equal(%Q[foo,"\r\n\n",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "\r\n\n", "baz"]))
+ end
+
+ def test_cr_lf_comma
+ assert_equal(%Q["\r\n,"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["\r\n,"]))
+ end
+
+ def test_cr_lf_comma_nil
+ assert_equal(%Q["\r\n,",#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["\r\n,", nil]))
+ end
+
+ def test_comma
+ assert_equal(%Q[","#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([","]))
+ end
+
+ def test_comma_double
+ assert_equal(%Q[",",","#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([",", ","]))
+ end
+
+ def test_comma_and_value
+ assert_equal(%Q[foo,"foo,bar",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "foo,bar", "baz"]))
+ end
+
+ def test_one_element
+ assert_equal(%Q[foo#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo"]))
+ end
+
+ def test_nil_values_only
+ assert_equal(%Q[,,#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([nil, nil, nil]))
+ end
+
+ def test_nil_double_only
+ assert_equal(%Q[,#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([nil, nil]))
+ end
+
+ def test_nil_values
+ assert_equal(%Q[foo,,,#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", nil, nil, nil]))
+ end
+
+ def test_nil_value_first
+ assert_equal(%Q[,foo,baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([nil, "foo", "baz"]))
+ end
+
+ def test_nil_value_middle
+ assert_equal(%Q[foo,,baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", nil, "baz"]))
+ end
+
+ def test_nil_value_last
+ assert_equal(%Q[foo,baz,#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "baz", nil]))
+ end
+
+ def test_nil_empty
+ assert_equal(%Q[,""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([nil, ""]))
+ end
+
+ def test_nil_cr
+ assert_equal(%Q[,"\r"#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([nil, "\r"]))
+ end
+
+ def test_values
+ assert_equal(%Q[foo,bar#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "bar"]))
+ end
+
+ def test_semi_colon
+ assert_equal(%Q[;#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([";"]))
+ end
+
+ def test_semi_colon_values
+ assert_equal(%Q[;,;#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([";", ";"]))
+ end
+
+ def test_tab_values
+ assert_equal(%Q[\t,\t#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["\t", "\t"]))
+ end
+
+ def test_col_sep
+ assert_equal(%Q[a;b;;c#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["a", "b", nil, "c"],
+ col_sep: ";"))
+ assert_equal(%Q[a\tb\t\tc#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["a", "b", nil, "c"],
+ col_sep: "\t"))
+ end
+
+ def test_row_sep
+ assert_equal(%Q[a,b,,c\r\n],
+ generate_line(["a", "b", nil, "c"],
+ row_sep: "\r\n"))
+ end
+
+ def test_force_quotes
+ assert_equal(%Q["1","b","","already ""quoted"""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([1, "b", nil, %Q{already "quoted"}],
+ force_quotes: true))
+ end
+
+ def test_encoding_utf8
+ assert_equal(%Q[あ,い,う#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["あ" , "い", "う"]))
+ end
+
+ def test_encoding_euc_jp
+ row = ["あ", "い", "う"].collect {|field| field.encode("EUC-JP")}
+ assert_equal(%Q[あ,い,う#{$INPUT_RECORD_SEPARATOR}].encode("EUC-JP"),
+ generate_line(row))
+ end
+
+ def test_encoding_with_default_internal
+ with_default_internal(Encoding::UTF_8) do
+ row = ["あ", "い", "う"].collect {|field| field.encode("EUC-JP")}
+ assert_equal(%Q[あ,い,う#{$INPUT_RECORD_SEPARATOR}].encode("EUC-JP"),
+ generate_line(row, encoding: Encoding::EUC_JP))
+ end
+ end
+
+ def test_with_default_internal
+ with_default_internal(Encoding::UTF_8) do
+ row = ["あ", "い", "う"].collect {|field| field.encode("EUC-JP")}
+ assert_equal(%Q[あ,い,う#{$INPUT_RECORD_SEPARATOR}].encode("EUC-JP"),
+ generate_line(row))
+ end
+ end
+end
+
+class TestCSVWriteGeneralGenerateLine < Test::Unit::TestCase
+ include TestCSVWriteGeneral
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate_line(row, **kwargs)
+ end
+end
+
+class TestCSVWriteGeneralGenerate < Test::Unit::TestCase
+ include TestCSVWriteGeneral
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate(**kwargs) do |csv|
+ csv << row
+ end
+ end
+end
diff --git a/test/csv/write/test_quote_empty.rb b/test/csv/write/test_quote_empty.rb
new file mode 100644
index 0000000000..70f73dad4a
--- /dev/null
+++ b/test/csv/write/test_quote_empty.rb
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+
+require_relative "../helper"
+
+module TestCSVWriteQuoteEmpty
+ def test_quote_empty_default
+ assert_equal(%Q["""",""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([%Q["], ""]))
+ end
+
+ def test_quote_empty_false
+ assert_equal(%Q["""",#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([%Q["], ""],
+ quote_empty: false))
+ end
+
+ def test_empty_default
+ assert_equal(%Q[foo,"",baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "", "baz"]))
+ end
+
+ def test_empty_false
+ assert_equal(%Q[foo,,baz#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["foo", "", "baz"],
+ quote_empty: false))
+ end
+
+ def test_empty_only_default
+ assert_equal(%Q[""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([""]))
+ end
+
+ def test_empty_only_false
+ assert_equal(%Q[#{$INPUT_RECORD_SEPARATOR}],
+ generate_line([""],
+ quote_empty: false))
+ end
+
+ def test_empty_double_default
+ assert_equal(%Q["",""#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["", ""]))
+ end
+
+ def test_empty_double_false
+ assert_equal(%Q[,#{$INPUT_RECORD_SEPARATOR}],
+ generate_line(["", ""],
+ quote_empty: false))
+ end
+end
+
+class TestCSVWriteQuoteEmptyGenerateLine < Test::Unit::TestCase
+ include TestCSVWriteQuoteEmpty
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate_line(row, **kwargs)
+ end
+end
+
+class TestCSVWriteQuoteEmptyGenerate < Test::Unit::TestCase
+ include TestCSVWriteQuoteEmpty
+ extend DifferentOFS
+
+ def generate_line(row, **kwargs)
+ CSV.generate(**kwargs) do |csv|
+ csv << row
+ end
+ end
+end