diff options
Diffstat (limited to 'test/csv')
| -rw-r--r-- | test/csv/base.rb | 9 | ||||
| -rw-r--r-- | test/csv/line_endings.gz | bin | 0 -> 59 bytes | |||
| -rw-r--r-- | test/csv/test_csv.rb | 1753 | ||||
| -rwxr-xr-x | test/csv/test_csv_parsing.rb | 244 | ||||
| -rwxr-xr-x | test/csv/test_csv_writing.rb | 98 | ||||
| -rwxr-xr-x | test/csv/test_data_converters.rb | 273 | ||||
| -rwxr-xr-x | test/csv/test_encodings.rb | 338 | ||||
| -rwxr-xr-x | test/csv/test_features.rb | 380 | ||||
| -rwxr-xr-x | test/csv/test_headers.rb | 305 | ||||
| -rwxr-xr-x | test/csv/test_interface.rb | 393 | ||||
| -rwxr-xr-x | test/csv/test_row.rb | 380 | ||||
| -rwxr-xr-x | test/csv/test_table.rb | 497 | ||||
| -rw-r--r-- | test/csv/ts_all.rb | 21 |
13 files changed, 2938 insertions, 1753 deletions
diff --git a/test/csv/base.rb b/test/csv/base.rb new file mode 100644 index 0000000000..a282c7afed --- /dev/null +++ b/test/csv/base.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: false +require "test/unit" + +require "csv" + +require_relative "../lib/with_different_ofs.rb" + +class TestCSV < Test::Unit::TestCase +end diff --git a/test/csv/line_endings.gz b/test/csv/line_endings.gz Binary files differnew file mode 100644 index 0000000000..39e1729ee4 --- /dev/null +++ b/test/csv/line_endings.gz diff --git a/test/csv/test_csv.rb b/test/csv/test_csv.rb deleted file mode 100644 index 1db540921b..0000000000 --- a/test/csv/test_csv.rb +++ /dev/null @@ -1,1753 +0,0 @@ -require 'test/unit' -require 'tempfile' -require 'fileutils' - -require 'csv' - -class CSV - class StreamBuf - # Let buffer work hard. - remove_const("BufSize") - BufSize = 2 - end -end - - -module CSVTestSupport - def d(data) - data - end -end - - -class TestCSV < Test::Unit::TestCase - file = Tempfile.new("crlf") - file << "\n" - file.open - file.binmode - RSEP = file.read - file.close - - include CSVTestSupport - - class << self - include CSVTestSupport - end - - @@simpleCSVData = { - [nil] => '', - [''] => '""', - [nil, nil] => ',', - [nil, nil, nil] => ',,', - ['foo'] => 'foo', - [','] => '","', - [',', ','] => '",",","', - [';'] => ';', - [';', ';'] => ';,;', - ["\"\r", "\"\r"] => "\"\"\"\r\",\"\"\"\r\"", - ["\"\n", "\"\n"] => "\"\"\"\n\",\"\"\"\n\"", - ["\t"] => "\t", - ["\t", "\t"] => "\t,\t", - ['foo', 'bar'] => 'foo,bar', - ['foo', '"bar"', 'baz'] => 'foo,"""bar""",baz', - ['foo', 'foo,bar', 'baz'] => 'foo,"foo,bar",baz', - ['foo', '""', 'baz'] => 'foo,"""""",baz', - ['foo', '', 'baz'] => 'foo,"",baz', - ['foo', nil, 'baz'] => 'foo,,baz', - [nil, 'foo', 'bar'] => ',foo,bar', - ['foo', 'bar', nil] => 'foo,bar,', - ['foo', "\r", 'baz'] => "foo,\"\r\",baz", - ['foo', "\n", 'baz'] => "foo,\"\n\",baz", - ['foo', "\r\n\r", 'baz'] => "foo,\"\r\n\r\",baz", - ['foo', "\r\n", 'baz'] => "foo,\"\r\n\",baz", - ['foo', "\r.\n", 'baz'] => "foo,\"\r.\n\",baz", - ['foo', "\r\n\n", 'baz'] => "foo,\"\r\n\n\",baz", - ['foo', '"', 'baz'] => 'foo,"""",baz', - } - - @@fullCSVData = { - [d(nil)] => '', - [d('')] => '""', - [d(nil), d(nil)] => ',', - [d(nil), d(nil), d(nil)] => ',,', - [d('foo')] => 'foo', - [d('foo'), d('bar')] => 'foo,bar', - [d('foo'), d('"bar"'), d('baz')] => 'foo,"""bar""",baz', - [d('foo'), d('foo,bar'), d('baz')] => 'foo,"foo,bar",baz', - [d('foo'), d('""'), d('baz')] => 'foo,"""""",baz', - [d('foo'), d(''), d('baz')] => 'foo,"",baz', - [d('foo'), d(nil), d('baz')] => 'foo,,baz', - [d('foo'), d("\r"), d('baz')] => "foo,\"\r\",baz", - [d('foo'), d("\n"), d('baz')] => "foo,\"\n\",baz", - [d('foo'), d("\r\n"), d('baz')] => "foo,\"\r\n\",baz", - [d('foo'), d("\r.\n"), d('baz')] => "foo,\"\r.\n\",baz", - [d('foo'), d("\r\n\n"), d('baz')] => "foo,\"\r\n\n\",baz", - [d('foo'), d('"'), d('baz')] => 'foo,"""",baz', - } - - @@fullCSVDataArray = @@fullCSVData.collect { |key, value| key } - - def ssv2csv(ssvStr, row_sep = nil) - sepConv(ssvStr, ?;, ?,, row_sep) - end - - def csv2ssv(csvStr, row_sep = nil) - sepConv(csvStr, ?,, ?;, row_sep) - end - - def tsv2csv(tsvStr, row_sep = nil) - sepConv(tsvStr, ?\t, ?,, row_sep) - end - - def csv2tsv(csvStr, row_sep = nil) - sepConv(csvStr, ?,, ?\t, row_sep) - end - - def sepConv(srcStr, srcSep, destSep, row_sep = nil) - rows = [] - cols, idx = CSV.parse_row(srcStr, 0, rows, srcSep, row_sep) - destStr = '' - cols = CSV.generate_row(rows, rows.size, destStr, destSep, row_sep) - destStr - end - -public - - def setup - @tmpdir = File.join(Dir.tmpdir, "ruby_test_csv_tmp_#{$$}") - Dir.mkdir(@tmpdir) - @infile = File.join(@tmpdir, 'in.csv') - @infiletsv = File.join(@tmpdir, 'in.tsv') - @emptyfile = File.join(@tmpdir, 'empty.csv') - @outfile = File.join(@tmpdir, 'out.csv') - @bomfile = File.join(@tmpdir, "bom.csv") - @macfile = File.join(@tmpdir, "mac.csv") - - CSV.open(@infile, "wb") do |writer| - @@fullCSVDataArray.each do |row| - writer.add_row(row) - end - end - - CSV.open(@infiletsv, "wb", ?\t) do |writer| - @@fullCSVDataArray.each do |row| - writer.add_row(row) - end - end - - CSV.generate(@emptyfile) do |writer| - # Create empty file. - end - - File.open(@bomfile, "wb") do |f| - f.write("\357\273\277\"foo\"\r\n\"bar\"\r\n") - end - - File.open(@macfile, "wb") do |f| - f.write("\"Avenches\",\"aus Umgebung\"\r\"Bad Hersfeld\",\"Ausgrabung\"") - end - end - - def teardown - FileUtils.rm_rf(@tmpdir) - end - - #### CSV::Reader unit test - - def test_Reader_each - file = File.open(@infile, "rb") - begin - reader = CSV::Reader.create(file) - expectedArray = @@fullCSVDataArray.dup - first = true - ret = reader.each { |row| - if first - assert_instance_of(Array, row) - first = false - end - expected = expectedArray.shift - assert_equal(expected, row) - } - assert_nil(ret, "Return is nil") - assert(expectedArray.empty?) - ensure - file.close - end - - # Illegal format. - reader = CSV::Reader.create("a,b\r\na,b,\"c\"\ra") - assert_raises(CSV::IllegalFormatError) do - reader.each do |row| - end - end - - reader = CSV::Reader.create("a,b\r\n\"") - assert_raises(CSV::IllegalFormatError) do - reader.each do |row| - end - end - end - - def test_Reader_shift - file = File.open(@infile, "rb") - begin - reader = CSV::Reader.create(file) - first = true - checked = 0 - @@fullCSVDataArray.each do |expected| - actual = reader.shift - if first - assert_instance_of(Array, actual) - first = false - end - assert_equal(expected, actual) - checked += 1 - end - assert(checked == @@fullCSVDataArray.size) - ensure - file.close - end - - # Illegal format. - reader = CSV::Reader.create("a,b\r\na,b,\"c\"\ra") - assert_raises(CSV::IllegalFormatError) do - reader.shift - reader.shift - end - - reader = CSV::Reader.create("a,b\r\na,b,\"c\"\ra") - assert_raises(CSV::IllegalFormatError) do - reader.shift - reader.shift - end - end - - def test_Reader_getRow - if CSV::Reader.respond_to?(:allocate) - obj = CSV::Reader.allocate - assert_raises(NotImplementedError) do - row = [] - obj.shift - end - end - end - - def test_IOReader_close_on_terminate - f = File.open(@infile, "r") - reader = CSV::IOReader.create(f) - reader.close - assert(!f.closed?) - f.close - - f = File.open(@infile, "r") - writer = CSV::IOReader.create(f) - writer.close_on_terminate - writer.close - assert(f.closed?) - end - - def test_Reader_close - f = File.open(@infile, "r") - reader = CSV::IOReader.create(f) - reader.close_on_terminate - reader.close - assert(f.closed?) - end - - def test_Reader_s_new - assert_raises(RuntimeError) do - CSV::Reader.new(nil) - end - end - - def test_Reader_s_create - reader = CSV::Reader.create("abc") - assert_instance_of(CSV::StringReader, reader, "With a String") - - file = File.open(@infile, "rb") - reader = CSV::Reader.create(file) - assert_instance_of(CSV::IOReader, reader, 'With an IO') - - obj = Object.new - def obj.sysread(size) - "abc" - end - def obj.read(size) - "abc" - end - reader = CSV::Reader.create(obj) - assert_instance_of(CSV::IOReader, reader, "With not an IO or String") - - # No need to test Tempfile because it's a pseudo IO. I test this here - # fors other tests. - reader = CSV::Reader.create(Tempfile.new("in.csv")) - assert_instance_of(CSV::IOReader, reader, "With an pseudo IO.") - file.close - end - - def test_IOReader_s_create_binmode - file = File.open(@outfile, "wb") - file << "\"\r\n\",\"\r\",\"\n\"\r1,2,3" - file.close - - file = File.open(@outfile, "rb") - begin - reader = CSV::IOReader.new(file, ?,, ?\r) - assert_equal(["\r\n", "\r", "\n"], reader.shift.to_a) - assert_equal(["1", "2", "3"], reader.shift.to_a) - reader.close - ensure - file.close - end - - file = File.open(@outfile, "r") # not "rb" - begin - lfincell = (RSEP == "\n" ? "\r\n" : "\n") - reader = CSV::IOReader.new(file, ?,, ?\r) - assert_equal([lfincell, "\r", "\n"], reader.shift.to_a) - assert_equal(["1", "2", "3"], reader.shift.to_a) - reader.close - ensure - file.close - end - end - - def test_Reader_s_parse - ret = CSV::Reader.parse("a,b,c") { |row| - assert_instance_of(Array, row, "Block parameter") - } - assert_nil(ret, "Return is nil") - - ret = CSV::Reader.parse("a;b;c", ?;) { |row| - assert_instance_of(Array, row, "Block parameter") - } - - file = Tempfile.new("in.csv") - file << "a,b,c" - file.open - ret = CSV::Reader.parse(file) { |row| - assert_instance_of(Array, row, "Block parameter") - } - assert_nil(ret, "Return is nil") - - file = Tempfile.new("in.csv") - file << "a,b,c" - file.open - ret = CSV::Reader.parse(file, ?,) { |row| - assert_instance_of(Array, row, "Block parameter") - } - - # Illegal format. - assert_raises(CSV::IllegalFormatError) do - CSV::Reader.parse("a,b\r\na,b,\"c\"\ra") do |row| - end - end - - assert_raises(CSV::IllegalFormatError) do - CSV::Reader.parse("a,b\r\na,b\"") do |row| - end - end - end - - - #### CSV::Writer unit test - - def test_Writer_s_new - assert_raises(RuntimeError) do - CSV::Writer.new(nil) - end - end - - def test_Writer_s_generate - ret = CSV::Writer.generate(STDOUT) { |writer| - assert_instance_of(CSV::BasicWriter, writer, "Block parameter") - } - - ret = CSV::Writer.generate(STDOUT, ?;) { |writer| - assert_instance_of(CSV::BasicWriter, writer, "Block parameter") - } - - assert_nil(ret, "Return is nil") - end - - def test_Writer_s_create - writer = CSV::Writer.create(STDERR) - assert_instance_of(CSV::BasicWriter, writer, "String") - - writer = CSV::Writer.create(STDERR, ?;) - assert_instance_of(CSV::BasicWriter, writer, "String") - - writer = CSV::Writer.create(Tempfile.new("out.csv")) - assert_instance_of(CSV::BasicWriter, writer, "IO") - end - - def test_Writer_LSHIFT # '<<' - file = Tempfile.new("out.csv") - CSV::Writer.generate(file) do |writer| - ret = writer << ['a', 'b', 'c'] - assert_instance_of(CSV::BasicWriter, ret, 'Return is self') - - writer << [nil, 'e', 'f'] << [nil, nil, ''] - end - file.open - file.binmode - str = file.read - assert_equal("a,b,c#{RSEP},e,f#{RSEP},,\"\"#{RSEP}", str, 'Normal') - - file = Tempfile.new("out2.csv") - CSV::Writer.generate(file) do |writer| - ret = writer << [d('a'), d('b'), d('c')] - assert_instance_of(CSV::BasicWriter, ret, 'Return is self') - - writer << [d(nil), d('e'), d('f')] << [d(nil), d(nil), d('')] - end - file.open - file.binmode - str = file.read - assert_equal("a,b,c#{RSEP},e,f#{RSEP},,\"\"#{RSEP}", str, 'Normal') - end - - def test_Writer_add_row - file = Tempfile.new("out.csv") - CSV::Writer.generate(file) do |writer| - ret = writer.add_row( - [d('a'), d('b'), d('c')]) - assert_instance_of(CSV::BasicWriter, ret, 'Return is self') - - writer.add_row( - [d(nil), d('e'), d('f')] - ).add_row( - [d(nil), d(nil), d('')] - ) - end - file.open - file.binmode - str = file.read - assert_equal("a,b,c#{RSEP},e,f#{RSEP},,\"\"#{RSEP}", str, 'Normal') - end - - def test_Writer_close - f = File.open(@outfile, "w") - writer = CSV::BasicWriter.create(f) - writer.close_on_terminate - writer.close - assert(f.closed?) - end - - def test_BasicWriter_close_on_terminate - f = File.open(@outfile, "w") - writer = CSV::BasicWriter.create(f) - writer.close - assert(!f.closed?) - f.close - - f = File.open(@outfile, "w") - writer = CSV::BasicWriter.new(f) - writer.close_on_terminate - writer.close - assert(f.closed?) - end - - def test_BasicWriter_s_create_binmode - file = File.open(@outfile, "w") # not "wb" - begin - writer = CSV::BasicWriter.new(file, ?,, ?\r) - writer << ["\r\n", "\r", "\n"] - writer << ["1", "2", "3"] - writer.close - ensure - file.close - end - - file = File.open(@outfile, "rb") - str = file.read - file.close - assert_equal("\"\r#{RSEP}\",\"\r\",\"#{RSEP}\"\r1,2,3\r", str) - end - - #### CSV unit test - - def test_s_open_reader - assert_raises(ArgumentError, 'Illegal mode') do - CSV.open("temp", "a") - end - - assert_raises(ArgumentError, 'Illegal mode') do - CSV.open("temp", "a", ?;) - end - - reader = CSV.open(@infile, "r") - assert_instance_of(CSV::IOReader, reader) - reader.close - - reader = CSV.open(@infile, "rb") - assert_instance_of(CSV::IOReader, reader) - reader.close - - reader = CSV.open(@infile, "r", ?;) - assert_instance_of(CSV::IOReader, reader) - reader.close - - CSV.open(@infile, "r") do |row| - assert_instance_of(Array, row) - break - end - - CSV.open(@infiletsv, "r", ?\t) do |row| - assert_instance_of(Array, row) - break - end - - assert_raises(Errno::ENOENT) do - CSV.open("NoSuchFileOrDirectory", "r") - end - - assert_raises(Errno::ENOENT) do - CSV.open("NoSuchFileOrDirectory", "r", ?;) - end - - # Illegal format. - File.open(@outfile, "wb") do |f| - f << "a,b\r\na,b,\"c\"\ra" - end - assert_raises(CSV::IllegalFormatError) do - CSV.open(@outfile, "r") do |row| - end - end - - File.open(@outfile, "wb") do |f| - f << "a,b\r\na,b\"" - end - assert_raises(CSV::IllegalFormatError) do - CSV.open(@outfile, "r") do |row| - end - end - - CSV.open(@emptyfile, "r") do |row| - assert_fail("Must not reach here") - end - end - - def test_s_parse - result = CSV.parse(File.read(@infile)) - assert_instance_of(Array, result) - assert_instance_of(Array, result[0]) - - result = CSV.parse(File.read(@infile)) - assert_instance_of(Array, result) - assert_instance_of(Array, result[0]) - - assert_equal([], CSV.parse("")) - assert_equal([[nil]], CSV.parse("\n")) - - CSV.parse(File.read(@infile)) do |row| - assert_instance_of(Array, row) - break - end - - CSV.parse(File.read(@infiletsv), ?\t) do |row| - assert_instance_of(Array, row) - break - end - - CSV.parse("") do |row| - assert(false) - end - - count = 0 - CSV.parse("\n") do |row| - assert_equal([nil], row) - count += 1 - end - assert_equal(1, count) - - assert_equal([["a|b-c|d"]], CSV.parse("a|b-c|d")) - assert_equal([["a", "b"], ["c", "d"]], CSV.parse("a|b-c|d", "|", "-")) - end - - def test_s_open_writer - writer = CSV.open(@outfile, "w") - assert_instance_of(CSV::BasicWriter, writer) - writer.close - - writer = CSV.open(@outfile, "wb") - assert_instance_of(CSV::BasicWriter, writer) - writer.close - - writer = CSV.open(@outfile, "wb", ?;) - assert_instance_of(CSV::BasicWriter, writer) - writer.close - - CSV.open(@outfile, "w") do |writer| - assert_instance_of(CSV::BasicWriter, writer) - end - - CSV.open(@outfile, "w", ?;) do |writer| - assert_instance_of(CSV::BasicWriter, writer) - end - - begin - CSV.open(@tmpdir, "w") - assert(false) - rescue Exception => ex - assert(ex.is_a?(Errno::EEXIST) || ex.is_a?(Errno::EISDIR) || ex.is_a?(Errno::EACCES)) - end - end - - def test_s_generate - writer = CSV.generate(@outfile) - assert_instance_of(CSV::BasicWriter, writer) - writer.close - - writer = CSV.generate(@outfile, ?;) - assert_instance_of(CSV::BasicWriter, writer) - writer.close - - CSV.generate(@outfile) do |writer| - assert_instance_of(CSV::BasicWriter, writer) - end - - CSV.generate(@outfile, ?;) do |writer| - assert_instance_of(CSV::BasicWriter, writer) - end - - begin - CSV.generate(@tmpdir) - assert(false) - rescue Exception => ex - assert(ex.is_a?(Errno::EEXIST) || ex.is_a?(Errno::EISDIR) || ex.is_a?(Errno::EACCES)) - end - end - - def test_s_generate_line - str = CSV.generate_line([]) - assert_equal('', str, "Extra boundary check.") - - str = CSV.generate_line([], ?;) - assert_equal('', str, "Extra boundary check.") - - @@simpleCSVData.each do |col, str| - buf = CSV.generate_line(col) - assert_equal(str, buf) - end - - @@simpleCSVData.each do |col, str| - buf = CSV.generate_line(col, ?;) - assert_equal(str + "\n", ssv2csv(buf)) - end - - @@simpleCSVData.each do |col, str| - buf = CSV.generate_line(col, ?\t) - assert_equal(str + "\n", tsv2csv(buf)) - end - - str = CSV.generate_line(['a', 'b'], nil, ?|) - assert_equal('a,b', str) - - str = CSV.generate_line(['a', 'b'], nil, "a") - assert_equal('"a",b', str) - end - - def test_s_generate_row - buf = '' - cols = CSV.generate_row([], 0, buf) - assert_equal(0, cols) - assert_equal("\n", buf, "Extra boundary check.") - - buf = '' - cols = CSV.generate_row([], 0, buf, ?;) - assert_equal(0, cols) - assert_equal("\n", buf, "Extra boundary check.") - - buf = '' - cols = CSV.generate_row([], 0, buf, ?\t) - assert_equal(0, cols) - assert_equal("\n", buf, "Extra boundary check.") - - buf = '' - cols = CSV.generate_row([], 0, buf, ?\t, ?|) - assert_equal(0, cols) - assert_equal("|", buf, "Extra boundary check.") - - buf = '' - cols = CSV.generate_row([d('1')], 2, buf) - assert_equal('1,', buf) - - buf = '' - cols = CSV.generate_row([d('1')], 2, buf, ?;) - assert_equal('1;', buf) - - buf = '' - cols = CSV.generate_row([d('1')], 2, buf, ?\t) - assert_equal("1\t", buf) - - buf = '' - cols = CSV.generate_row([d('1')], 2, buf, ?\t, ?|) - assert_equal("1\t", buf) - - buf = '' - cols = CSV.generate_row([d('1'), d('2')], 1, buf) - assert_equal("1\n", buf) - - buf = '' - cols = CSV.generate_row([d('1'), d('2')], 1, buf, ?;) - assert_equal("1\n", buf) - - buf = '' - cols = CSV.generate_row([d('1'), d('2')], 1, buf, ?\t) - assert_equal("1\n", buf) - - buf = '' - cols = CSV.generate_row([d('1'), d('2')], 1, buf, ?\t, ?\n) - assert_equal("1\n", buf) - - buf = '' - cols = CSV.generate_row([d('1'), d('2')], 1, buf, ?\t, ?\r) - assert_equal("1\r", buf) - - buf = '' - cols = CSV.generate_row([d('1'), d('2')], 1, buf, ?\t, ?|) - assert_equal("1|", buf) - - @@fullCSVData.each do |col, str| - buf = '' - cols = CSV.generate_row(col, col.size, buf) - assert_equal(col.size, cols) - assert_equal(str + "\n", buf) - end - - @@fullCSVData.each do |col, str| - buf = '' - cols = CSV.generate_row(col, col.size, buf, ?;) - assert_equal(col.size, cols) - assert_equal(str + "\n", ssv2csv(buf)) - end - - @@fullCSVData.each do |col, str| - buf = '' - cols = CSV.generate_row(col, col.size, buf, ?\t) - assert_equal(col.size, cols) - assert_equal(str + "\n", tsv2csv(buf)) - end - - # row separator - @@fullCSVData.each do |col, str| - buf = '' - cols = CSV.generate_row(col, col.size, buf, ?,, ?|) - assert_equal(col.size, cols) - assert_equal(str + "|", buf) - end - - # col and row separator - @@fullCSVData.each do |col, str| - buf = '' - cols = CSV.generate_row(col, col.size, buf, ?\t, ?|) - assert_equal(col.size, cols) - assert_equal(str + "|", tsv2csv(buf, ?|)) - end - - buf = '' - toBe = '' - cols = 0 - colsToBe = 0 - @@fullCSVData.each do |col, str| - cols += CSV.generate_row(col, col.size, buf) - toBe << str << "\n" - colsToBe += col.size - end - assert_equal(colsToBe, cols) - assert_equal(toBe, buf) - - buf = '' - toBe = '' - cols = 0 - colsToBe = 0 - @@fullCSVData.each do |col, str| - lineBuf = '' - cols += CSV.generate_row(col, col.size, lineBuf, ?;) - buf << ssv2csv(lineBuf) << "\n" - toBe << ssv2csv(lineBuf) << "\n" - colsToBe += col.size - end - assert_equal(colsToBe, cols) - assert_equal(toBe, buf) - - buf = '' - toBe = '' - cols = 0 - colsToBe = 0 - @@fullCSVData.each do |col, str| - lineBuf = '' - cols += CSV.generate_row(col, col.size, lineBuf, ?\t) - buf << tsv2csv(lineBuf) << "\n" - toBe << tsv2csv(lineBuf) << "\n" - colsToBe += col.size - end - assert_equal(colsToBe, cols) - assert_equal(toBe, buf) - - buf = '' - toBe = '' - cols = 0 - colsToBe = 0 - @@fullCSVData.each do |col, str| - lineBuf = '' - cols += CSV.generate_row(col, col.size, lineBuf, ?|) - buf << tsv2csv(lineBuf, ?|) - toBe << tsv2csv(lineBuf, ?|) - colsToBe += col.size - end - assert_equal(colsToBe, cols) - assert_equal(toBe, buf) - end - - def test_s_parse_line - @@simpleCSVData.each do |col, str| - row = CSV.parse_line(str) - assert_instance_of(Array, row) - assert_equal(col.size, row.size) - assert_equal(col, row) - end - - @@simpleCSVData.each do |col, str| - str = csv2ssv(str) - row = CSV.parse_line(str, ?;) - assert_instance_of(Array, row) - assert_equal(col.size, row.size, str.inspect) - assert_equal(col, row, str.inspect) - end - - @@simpleCSVData.each do |col, str| - str = csv2tsv(str) - row = CSV.parse_line(str, ?\t) - assert_instance_of(Array, row) - assert_equal(col.size, row.size) - assert_equal(col, row) - end - - assert_equal(['a', 'b', 'c'], CSV.parse_line("a,b,c", nil, nil)) - assert_equal(['a', nil], CSV.parse_line("a,b,c", nil, ?b)) - assert_equal(['a', 'b', nil], CSV.parse_line("a,b,c", nil, "c")) - assert_equal([nil], CSV.parse_line("")) - assert_equal([nil], CSV.parse_line("\n")) - assert_equal([""], CSV.parse_line("\"\"\n")) - - # Illegal format. - buf = [] - row = CSV.parse_line("a,b,\"c\"\ra") - assert_instance_of(Array, row) - assert_equal(0, row.size) - - buf = Array.new - row = CSV.parse_line("a;b;\"c\"\ra", ?;) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - buf = Array.new - row = CSV.parse_line("a\tb\t\"c\"\ra", ?\t) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("a,b\"") - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("a;b\"", ?;) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("a\tb\"", ?\t) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("\"a,b\"\r,") - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("\"a;b\"\r;", ?;) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("\"a\tb\"\r\t", ?\t) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("\"a,b\"\r\"") - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("\"a;b\"\r\"", ?;) - assert_instance_of(Array, row) - assert_equal(0, row.size) - - row = CSV.parse_line("\"a\tb\"\r\"", ?\t) - assert_instance_of(Array, row) - assert_equal(0, row.size) - end - - def test_s_parse_row - @@fullCSVData.each do |col, str| - buf = Array.new - cols, idx = CSV.parse_row(str + "\r\n", 0, buf) - assert_equal(cols, buf.size, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf, str.inspect) - - buf = Array.new - cols, idx = CSV.parse_row(str + "\n", 0, buf, ?,, ?\n) - assert_equal(cols, buf.size, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf, str.inspect) - - # separator: | - buf = Array.new - cols, idx = CSV.parse_row(str + "|", 0, buf, ?,) - assert_not_equal(col, buf) - buf = Array.new - cols, idx = CSV.parse_row(str + "|", 0, buf, ?,, ?|) - assert_equal(cols, buf.size, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf, str.inspect) - end - - @@fullCSVData.each do |col, str| - str = csv2ssv(str) - buf = Array.new - cols, idx = CSV.parse_row(str + "\r\n", 0, buf, ?;) - assert_equal(cols, buf.size, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf, str) - end - - @@fullCSVData.each do |col, str| - str = csv2tsv(str) - buf = Array.new - cols, idx = CSV.parse_row(str + "\r\n", 0, buf, ?\t) - assert_equal(cols, buf.size, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf, str) - end - - @@fullCSVData.each do |col, str| - str = csv2tsv(str, ?|) - buf = Array.new - cols, idx = CSV.parse_row(str + "|", 0, buf, ?\t, ?|) - assert_equal(cols, buf.size, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf, str) - end - - buf = [] - CSV.parse_row("a,b,c", 0, buf, nil, nil) - assert_equal(['a', 'b', 'c'], buf) - - buf = [] - CSV.parse_row("a,b,c", 0, buf, nil, ?b) - assert_equal(['a', nil], buf) - - buf = [] - CSV.parse_row("a,b,c", 0, buf, nil, "c") - assert_equal(['a', 'b', nil], buf) - - buf = Array.new - cols, idx = CSV.parse_row("a,b,\"c\r\"", 0, buf) - assert_equal(["a", "b", "c\r"], buf.to_a) - - buf = Array.new - cols, idx = CSV.parse_row("a;b;\"c\r\"", 0, buf, ?;) - assert_equal(["a", "b", "c\r"], buf.to_a) - - buf = Array.new - cols, idx = CSV.parse_row("a\tb\t\"c\r\"", 0, buf, ?\t) - assert_equal(["a", "b", "c\r"], buf.to_a) - - buf = Array.new - cols, idx = CSV.parse_row("a,b,c\n", 0, buf, ?,, ?\n) - assert_equal(["a", "b", "c"], buf.to_a) - - buf = Array.new - cols, idx = CSV.parse_row("a\tb\tc\n", 0, buf, ?\t, ?\n) - assert_equal(["a", "b", "c"], buf.to_a) - - # Illegal format. - buf = Array.new - cols, idx = CSV.parse_row("a,b,c\"", 0, buf) - assert_equal(0, cols, "Illegal format; unbalanced double-quote.") - - buf = Array.new - cols, idx = CSV.parse_row("a;b;c\"", 0, buf, ?;) - assert_equal(0, cols, "Illegal format; unbalanced double-quote.") - - buf = Array.new - cols, idx = CSV.parse_row("a,b,\"c\"\ra", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a,b,\"c\"\ra", 0, buf, ?;) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a,b\"", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a;b\"", 0, buf, ?;) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("\"a,b\"\r,", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a\r,", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a\r", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a\rbc", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a\r\"\"", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("a\r\rabc,", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("\"a;b\"\r;", 0, buf, ?;) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("\"a,b\"\r\"", 0, buf) - assert_equal(0, cols) - assert_equal(0, idx) - - buf = Array.new - cols, idx = CSV.parse_row("\"a;b\"\r\"", 0, buf, ?;) - assert_equal(0, cols) - assert_equal(0, idx) - end - - def test_s_parse_rowEOF - @@fullCSVData.each do |col, str| - if str == '' - # String "" is not allowed. - next - end - buf = Array.new - cols, idx = CSV.parse_row(str, 0, buf) - assert_equal(col.size, cols, "Reported size.") - assert_equal(col.size, buf.size, "Size.") - assert_equal(col, buf) - end - end - - def test_s_parse_rowConcat - buf = '' - toBe = [] - @@fullCSVData.each do |col, str| - buf << str << "\r\n" - toBe.concat(col) - end - idx = 0 - cols = 0 - parsed = Array.new - parsedCols = 0 - begin - cols, idx = CSV.parse_row(buf, idx, parsed) - parsedCols += cols - end while cols > 0 - assert_equal(toBe.size, parsedCols) - assert_equal(toBe.size, parsed.size) - assert_equal(toBe, parsed) - - buf = '' - toBe = [] - @@fullCSVData.each do |col, str| - buf << str << "\n" - toBe.concat(col) - end - idx = 0 - cols = 0 - parsed = Array.new - parsedCols = 0 - begin - cols, idx = CSV.parse_row(buf, idx, parsed, ?,, ?\n) - parsedCols += cols - end while cols > 0 - assert_equal(toBe.size, parsedCols) - assert_equal(toBe.size, parsed.size) - assert_equal(toBe, parsed) - - buf = '' - toBe = [] - @@fullCSVData.sort { |a, b| - a[0].length <=> b[0].length - }.each do |col, str| - buf << str << "\n" - toBe.concat(col) - end - idx = 0 - cols = 0 - parsed = Array.new - parsedCols = 0 - begin - cols, idx = CSV.parse_row(buf, idx, parsed, ?,, ?\n) - parsedCols += cols - end while cols > 0 - assert_equal(toBe.size, parsedCols) - assert_equal(toBe.size, parsed.size) - assert_equal(toBe, parsed) - - buf = '' - toBe = [] - @@fullCSVData.each do |col, str| - buf << str << "|" - toBe.concat(col) - end - idx = 0 - cols = 0 - parsed = [] - parsedCols = 0 - begin - cols, idx = CSV.parse_row(buf, idx, parsed, ?,, ?|) - parsedCols += cols - end while cols > 0 - assert_equal(toBe.size, parsedCols) - assert_equal(toBe.size, parsed.size) - assert_equal(toBe, parsed) - end - - def test_utf8 - rows = [] - CSV.open(@bomfile, "r") do |row| - rows << row.to_a - end - assert_equal([["foo"], ["bar"]], rows) - - rows = [] - file = File.open(@bomfile) - CSV::Reader.parse(file) do |row| - rows << row.to_a - end - assert_equal([["foo"], ["bar"]], rows) - file.close - end - - def test_macCR - rows = [] - CSV.open(@macfile, "r", ?,, ?\r) do |row| - rows << row.to_a - end - assert_equal([["Avenches", "aus Umgebung"], ["Bad Hersfeld", "Ausgrabung"]], rows) - - rows = [] - assert_raises(CSV::IllegalFormatError) do - CSV.open(@macfile, "r") do |row| - rows << row.to_a - end - assert_equal([["Avenches", "aus Umgebung\r\"Bad Hersfeld", "Ausgrabung"]], rows) - end - - rows = [] - file = File.open(@macfile) - begin - CSV::Reader.parse(file, ?,, ?\r) do |row| - rows << row.to_a - end - assert_equal([["Avenches", "aus Umgebung"], ["Bad Hersfeld", "Ausgrabung"]], rows) - ensure - file.close - end - - rows = [] - file = File.open(@macfile) - begin - assert_raises(CSV::IllegalFormatError) do - CSV::Reader.parse(file, ?,) do |row| - rows << row.to_a - end - assert_equal([["Avenches", "aus Umgebung\r\"Bad Hersfeld", "Ausgrabung"]], rows) - end - ensure - file.close - end - end - - - #### CSV unit test - - InputStreamPattern = '0123456789' - InputStreamPatternSize = InputStreamPattern.size - def expChar(idx) - InputStreamPattern[idx % InputStreamPatternSize] - end - - def expStr(idx, n) - if n > InputStreamPatternSize - InputStreamPattern + expStr(0, n - InputStreamPatternSize) - else - InputStreamPattern[idx % InputStreamPatternSize, n] - end - end - - def setupInputStream(size, bufSize = nil) - setBufSize(bufSize) if bufSize - m = ((size / InputStreamPatternSize) + 1).to_i - File.open(@outfile, "wb") do |f| - f << (InputStreamPattern * m)[0, size] - end - file = File.open(@outfile, "rb") - buf = CSV::IOBuf.new(file) - if block_given? - yield(buf) - file.close - nil - else - buf - end - end - - def setBufSize(size) - CSV::StreamBuf.module_eval('remove_const("BufSize")') - CSV::StreamBuf.module_eval("BufSize = #{ size }") - end - - class StrBuf < CSV::StreamBuf - private - def initialize(string) - @str = string - @idx = 0 - super() - end - - def read(size) - str = @str[@idx, size] - if str.empty? - nil - else - @idx += str.size - str - end - end - end - - class ErrBuf < CSV::StreamBuf - class Error < RuntimeError; end - private - def initialize - @first = true - super() - end - - def read(size) - if @first - @first = false - "a" * size - else - raise ErrBuf::Error.new - end - end - end - - def test_StreamBuf_MyBuf - # At first, check ruby's behaviour. - s = "abc" - assert_equal(?a, s[0]) - assert_equal(?b, s[1]) - assert_equal(?c, s[2]) - assert_equal(nil, s[3]) - assert_equal("a", s[0, 1]) - assert_equal("b", s[1, 1]) - assert_equal("c", s[2, 1]) - assert_equal("", s[3, 1]) - assert_equal(nil, s[4, 1]) - - s = StrBuf.new("abc") - assert_equal(?a, s[0]) - assert_equal(?b, s.get(1)) - assert_equal(?c, s[2]) - assert_equal(nil, s.get(3)) - assert_equal("a", s[0, 1]) - assert_equal("b", s.get(1, 1)) - assert_equal("c", s[2, 1]) - assert_equal("", s.get(3, 1)) - assert_equal(nil, s[4, 1]) - - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(?b, s[0]) - assert(!s.is_eos?) - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(?c, s[0]) - assert(!s.is_eos?) - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(nil, s[0]) - assert(s.is_eos?) - dropped = s.drop(1) - assert_equal(0, dropped) - assert_equal(nil, s[0]) - assert(s.is_eos?) - - s = StrBuf.new("") - assert_equal(nil, s[0]) - - s = StrBuf.new("") - dropped = s.drop(1) - assert_equal(0, dropped) - - assert_raises(TestCSV::ErrBuf::Error) do - s = ErrBuf.new - s[1024] - end - - assert_raises(TestCSV::ErrBuf::Error) do - s = ErrBuf.new - s.drop(1024) - end - end - - def test_StreamBuf_AREF # '[idx]' - setupInputStream(22, 1024) do |s| - [0, 1, 9, 10, 19, 20, 21].each do |idx| - assert_equal(expChar(idx), s[idx], idx.to_s) - end - [22, 23].each do |idx| - assert_equal(nil, s[idx], idx.to_s) - end - assert_equal(nil, s[-1]) - end - - setupInputStream(22, 1) do |s| - [0, 1, 9, 10, 19, 20, 21].each do |idx| - assert_equal(expChar(idx), s[idx], idx.to_s) - end - [22, 23].each do |idx| - assert_equal(nil, s[idx], idx.to_s) - end - end - - setupInputStream(1024, 1) do |s| - [1023, 0].each do |idx| - assert_equal(expChar(idx), s[idx], idx.to_s) - end - [1024, 1025].each do |idx| - assert_equal(nil, s[idx], idx.to_s) - end - end - - setupInputStream(1, 1) do |s| - [0].each do |idx| - assert_equal(expChar(idx), s[idx], idx.to_s) - end - [1, 2].each do |idx| - assert_equal(nil, s[idx], idx.to_s) - end - end - end - - def test_StreamBuf_AREF_n # '[idx, n]' - # At first, check ruby's behaviour. - assert_equal("", "abc"[3, 1]) - assert_equal(nil, "abc"[4, 1]) - - setupInputStream(22, 1024) do |s| - [0, 1, 9, 10, 19, 20, 21].each do |idx| - assert_equal(expStr(idx, 1), s[idx, 1], idx.to_s) - end - assert_equal("", s[22, 1]) - assert_equal(nil, s[23, 1]) - end - - setupInputStream(22, 1) do |s| - [0, 1, 9, 10, 19, 20, 21].each do |idx| - assert_equal(expStr(idx, 1), s[idx, 1], idx.to_s) - end - assert_equal("", s[22, 1]) - assert_equal(nil, s[23, 1]) - end - - setupInputStream(1024, 1) do |s| - [1023, 0].each do |idx| - assert_equal(expStr(idx, 1), s[idx, 1], idx.to_s) - end - assert_equal("", s[1024, 1]) - assert_equal(nil, s[1025, 1]) - end - - setupInputStream(1, 1) do |s| - [0].each do |idx| - assert_equal(expStr(idx, 1), s[idx, 1], idx.to_s) - end - assert_equal("", s[1, 1]) - assert_equal(nil, s[2, 1]) - end - - setupInputStream(22, 11) do |s| - [0, 1, 10, 11, 20].each do |idx| - assert_equal(expStr(idx, 2), s[idx, 2], idx.to_s) - end - assert_equal(expStr(21, 1), s[21, 2]) - - assert_equal(expStr(0, 12), s[0, 12]) - assert_equal(expStr(10, 12), s[10, 12]) - assert_equal(expStr(10, 12), s[10, 13]) - assert_equal(expStr(10, 12), s[10, 14]) - assert_equal(expStr(10, 12), s[10, 1024]) - - assert_equal(nil, s[0, -1]) - assert_equal(nil, s[21, -1]) - - assert_equal(nil, s[-1, 10]) - assert_equal(nil, s[-1, -1]) - end - end - - def test_StreamBuf_get - setupInputStream(22, 1024) do |s| - [0, 1, 9, 10, 19, 20, 21].each do |idx| - assert_equal(expChar(idx), s.get(idx), idx.to_s) - end - [22, 23].each do |idx| - assert_equal(nil, s.get(idx), idx.to_s) - end - assert_equal(nil, s.get(-1)) - end - end - - def test_StreamBuf_get_n - setupInputStream(22, 1024) do |s| - [0, 1, 9, 10, 19, 20, 21].each do |idx| - assert_equal(expStr(idx, 1), s.get(idx, 1), idx.to_s) - end - assert_equal("", s.get(22, 1)) - assert_equal(nil, s.get(23, 1)) - - assert_equal(nil, s.get(-1, 1)) - assert_equal(nil, s.get(-1, -1)) - end - end - - def test_StreamBuf_drop - setupInputStream(22, 1024) do |s| - assert_equal(expChar(0), s[0]) - assert_equal(expChar(21), s[21]) - assert_equal(nil, s[22]) - - dropped = s.drop(-1) - assert_equal(0, dropped) - assert_equal(expChar(0), s[0]) - - dropped = s.drop(0) - assert_equal(0, dropped) - assert_equal(expChar(0), s[0]) - - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(expChar(1), s[0]) - assert_equal(expChar(2), s[1]) - - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(expChar(2), s[0]) - assert_equal(expChar(3), s[1]) - end - - setupInputStream(4, 2) do |s| - dropped = s.drop(2) - assert_equal(2, dropped) - assert_equal(expChar(2), s[0]) - assert_equal(expChar(3), s[1]) - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(expChar(3), s[0]) - assert_equal(nil, s[1]) - dropped = s.drop(1) - assert_equal(1, dropped) - assert_equal(nil, s[0]) - assert_equal(nil, s[1]) - dropped = s.drop(0) - assert_equal(0, dropped) - assert_equal(nil, s[0]) - assert_equal(nil, s[1]) - end - - setupInputStream(6, 3) do |s| - dropped = s.drop(2) - assert_equal(2, dropped) - dropped = s.drop(2) - assert_equal(2, dropped) - assert_equal(expChar(4), s[0]) - assert_equal(expChar(5), s[1]) - dropped = s.drop(3) - assert_equal(2, dropped) - assert_equal(nil, s[0]) - assert_equal(nil, s[1]) - end - end - - def test_StreamBuf_is_eos? - setupInputStream(3, 1024) do |s| - assert(!s.is_eos?) - s.drop(1) - assert(!s.is_eos?) - s.drop(1) - assert(!s.is_eos?) - s.drop(1) - assert(s.is_eos?) - s.drop(1) - assert(s.is_eos?) - end - - setupInputStream(3, 2) do |s| - assert(!s.is_eos?) - s.drop(1) - assert(!s.is_eos?) - s.drop(1) - assert(!s.is_eos?) - s.drop(1) - assert(s.is_eos?) - s.drop(1) - assert(s.is_eos?) - end - end - - def test_StreamBuf_s_new - # NotImplementedError should be raised from StreamBuf#read. - assert_raises(NotImplementedError) do - CSV::StreamBuf.new - end - end - - def test_IOBuf_close - f = File.open(@outfile, "wb") - f << "tst" - f.close - - f = File.open(@outfile, "rb") - iobuf = CSV::IOBuf.new(f) - iobuf.close - assert(true) # iobuf.close does not raise any exception. - f.close - end - - def test_IOBuf_s_new - iobuf = CSV::IOBuf.new(Tempfile.new("in.csv")) - assert_instance_of(CSV::IOBuf, iobuf) - end - - - #### CSV functional test - - # sample data - # - # 1 2 3 4 5 6 7 8 - # +------+-------+---------+-------+--------+------+----+------+ - # | foo | "foo" | foo,bar | "" |(empty) |(null)| \r | \r\n | - # +------+-------+---------+-------+--------+------+----+------+ - # | NaHi | "Na" | Na,Hi | \r.\n | \r\n\n | " | \n | \r\n | - # +------+-------+---------+-------+--------+------+----+------+ - # - def test_s_parseAndCreate - colSize = 8 - csvStr = "foo,!!!foo!!!,!foo,bar!,!!!!!!,!!,,!\r!,!\r\n!\nNaHi,!!!Na!!!,!Na,Hi!,!\r.\n!,!\r\n\n!,!!!!,!\n!,!\r\n!".gsub!('!', '"') - csvStrTerminated = csvStr + "\n" - - myStr = csvStr.dup - res1 = []; res2 = [] - idx = 0 - col, idx = CSV::parse_row(myStr, 0, res1) - col, idx = CSV::parse_row(myStr, idx, res2) - - buf = '' - col = CSV::generate_row(res1, colSize, buf) - col = CSV::generate_row(res2, colSize, buf) - assert_equal(csvStrTerminated, buf) - - parsed = [] - CSV::Reader.parse(csvStrTerminated) do |row| - parsed << row - end - - buf = '' - CSV::Writer.generate(buf) do |writer| - parsed.each do |row| - writer.add_row(row) - end - end - assert_equal(csvStrTerminated, buf) - - buf = '' - CSV::Writer.generate(buf) do |writer| - parsed.each do |row| - writer << row - end - end - assert_equal(csvStrTerminated, buf) - end - - def test_writer_fs_rs_generate - buf = '' - CSV::Writer.generate(buf, ",,") do |writer| - writer << [] - end - assert_equal("\n", buf) - - buf = '' - CSV::Writer.generate(buf, ",,") do |writer| - writer << [] << [] - end - assert_equal("\n\n", buf) - - buf = '' - CSV::Writer.generate(buf, ",,") do |writer| - writer << [1] - end - assert_equal("1\n", buf) - - buf = '' - CSV::Writer.generate(buf, ",,") do |writer| - writer << [1, 2, 3] - writer << [4, ",,", 5] - end - assert_equal("1,,2,,3\n4,,\",,\",,5\n", buf) - - buf = '' - CSV::Writer.generate(buf, ",,:", ",,;") do |writer| - writer << [nil, nil, nil] - writer << [nil, ",,", nil] - end - assert_equal(",,:,,:,,;,,:,,,,:,,;", buf) - - buf = '' - CSV::Writer.generate(buf, "---") do |writer| - writer << [1, 2, 3] - writer << [4, "---\"---", 5] - end - assert_equal("1---2---3\n4---\"---\"\"---\"---5\n", buf) - - buf = '' - CSV::Writer.generate(buf, nil) do |writer| - writer << [1, 2, 3] - writer << [4, ",\",", 5] - end - assert_equal("1,2,3\n4,\",\"\",\",5\n", buf) - end - - def test_writer_fs_rs_parse - reader = CSV::Reader.create('a||b--c||d', '||', '--') - assert_equal(['a', 'b'], reader.shift) - assert_equal(['c', 'd'], reader.shift) - - reader = CSV::Reader.create("a@|b@-c@|d", "@|", "@-") - assert_equal(['a', 'b'], reader.shift) - assert_equal(['c', 'd'], reader.shift) - - reader = CSV::Reader.create("ababfsababrs", "abfs", "abrs") - assert_equal(['ab', 'ab'], reader.shift) - - reader = CSV::Reader.create('"ab"abfsababrs', "abfs", "abrs") - assert_equal(['ab', 'ab'], reader.shift) - - reader = CSV::Reader.create('"ab"aabfsababrs', "abfs", "abrs") - assert_raises(CSV::IllegalFormatError) do - reader.shift - end - - # fs match while matching rs progress - reader = CSV::Reader.create("ab,ababrs", nil, "abrs") - assert_equal(['ab', 'ab'], reader.shift) - - reader = CSV::Reader.create(',ababrs', nil, "abrs") - assert_equal([nil, 'ab'], reader.shift) - - reader = CSV::Reader.create('"",ababrs', nil, "abrs") - assert_equal(['', 'ab'], reader.shift) - - reader = CSV::Reader.create('ab,"ab"abrs', nil, "abrs") - assert_equal(['ab', 'ab'], reader.shift) - - reader = CSV::Reader.create('ab,"ab"aabrs', nil, "abrs") - assert_raises(CSV::IllegalFormatError) do - reader.shift - end - - # rs match while matching fs progress - reader = CSV::Reader.create("ab|abc", 'ab-', "ab|") - assert_equal([nil], reader.shift) - assert_equal(['abc'], reader.shift) - - reader = CSV::Reader.create("ab\ncdabcef", "abc", "\n") - assert_equal(['ab'], reader.shift) - assert_equal(['cd', "ef"], reader.shift) - - # EOF while fs/rs matching - reader = CSV::Reader.create("ab", 'ab-', "xyz") - assert_equal(['ab'], reader.shift) - - reader = CSV::Reader.create("ab", 'xyz', "ab|") - assert_equal(['ab'], reader.shift) - - reader = CSV::Reader.create("ab", 'ab-', "ab|") - assert_equal(['ab'], reader.shift) - - reader = CSV::Reader.create(",,:,,:,,;,,:,,,,:,,;", ",,:", ",,;") - assert_equal([nil, nil, nil], reader.shift) - assert_equal([nil, ",,", nil], reader.shift) - end - - def test_s_foreach - File.open(@outfile, "w") do |f| - f << "1,2,3\n4,5,6" - end - row = [] - CSV.foreach(@outfile) { |line| - row << line - } - assert_equal([['1', '2', '3'], ['4', '5', '6']], row) - - File.open(@outfile, "w") do |f| - f << "1,2,3\r4,5,6" - end - row = [] - CSV.foreach(@outfile, "\r") { |line| - row << line - } - assert_equal([['1', '2', '3'], ['4', '5', '6']], row) - end - - def test_s_readlines - File.open(@outfile, "w") do |f| - f << "1,2,3\n4,5,6" - end - assert_equal([["1", "2", "3"], ["4", "5", "6"]], CSV.readlines(@outfile)) - assert_equal([["1", "2", nil], [nil, "5", "6"]], CSV.readlines(@outfile, "3\n4")) - end - - def test_s_read - File.open(@outfile, "w") do |f| - f << "1,2,3\n4,5,6" - end - assert_equal([["1", "2", "3"], ["4", "5", "6"]], CSV.read(@outfile)) - assert_equal([["1", "2"]], CSV.read(@outfile, 3)) - assert_equal([[nil], ["4", nil]], CSV.read(@outfile, 3, 5)) - end -end diff --git a/test/csv/test_csv_parsing.rb b/test/csv/test_csv_parsing.rb new file mode 100755 index 0000000000..547e70e933 --- /dev/null +++ b/test/csv/test_csv_parsing.rb @@ -0,0 +1,244 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# frozen_string_literal: false + +# 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. + +require "timeout" + +require_relative "base" + +# +# Following tests are my interpretation of the +# {CSV RCF}[http://www.ietf.org/rfc/rfc4180.txt]. I only deviate from that +# document in one place (intentionally) and that is to make the default row +# separator <tt>$/</tt>. +# +class TestCSV::Parsing < TestCSV + extend DifferentOFS + + BIG_DATA = "123456789\n" * 1024 + + def test_mastering_regex_example + ex = %Q{Ten Thousand,10000, 2710 ,,"10,000","It's ""10 Grand"", baby",10K} + assert_equal( [ "Ten Thousand", "10000", " 2710 ", nil, "10,000", + "It's \"10 Grand\", baby", "10K" ], + CSV.parse_line(ex) ) + end + + # Old Ruby 1.8 CSV library tests. + def test_std_lib_csv + [ ["\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"]], + [";,;", [";", ";"]] ].each do |csv_test| + assert_equal(csv_test.last, CSV.parse_line(csv_test.first)) + end + + [ ["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"]] ].each do |csv_test| + assert_equal(csv_test.last, CSV.parse_line(csv_test.first)) + end + end + + # From: http://ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-core/6496 + def test_aras_edge_cases + [ [%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""",\nc}, ["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 |edge_case| + assert_equal(edge_case.last, CSV.parse_line(edge_case.first)) + end + end + + def test_james_edge_cases + # A read at eof? should return nil. + assert_equal(nil, CSV.parse_line("")) + # + # With Ruby 1.8 CSV it's impossible to tell an empty line from a line + # containing a single +nil+ field. The old CSV library returns + # <tt>[nil]</tt> in these cases, but <tt>Array.new</tt> makes more sense to + # me. + # + assert_equal(Array.new, CSV.parse_line("\n1,2,3\n")) + end + + def test_rob_edge_cases + [ [%Q{"a\nb"}, ["a\nb"]], + [%Q{"\n\n\n"}, ["\n\n\n"]], + [%Q{a,"b\n\nc"}, ['a', "b\n\nc"]], + [%Q{,"\r\n"}, [nil,"\r\n"]], + [%Q{,"\r\n."}, [nil,"\r\n."]], + [%Q{"a\na","one newline"}, ["a\na", 'one newline']], + [%Q{"a\n\na","two newlines"}, ["a\n\na", 'two newlines']], + [%Q{"a\r\na","one CRLF"}, ["a\r\na", 'one CRLF']], + [%Q{"a\r\n\r\na","two CRLFs"}, ["a\r\n\r\na", 'two CRLFs']], + [%Q{with blank,"start\n\nfinish"\n}, ['with blank', "start\n\nfinish"]], + ].each do |edge_case| + assert_equal(edge_case.last, CSV.parse_line(edge_case.first)) + end + end + + def test_non_regex_edge_cases + # An early version of the non-regex parser fails this test + [ [ "foo,\"foo,bar,baz,foo\",\"foo\"", + ["foo", "foo,bar,baz,foo", "foo"] ] ].each do |edge_case| + assert_equal(edge_case.last, CSV.parse_line(edge_case.first)) + end + + assert_raise(CSV::MalformedCSVError) do + CSV.parse_line("1,\"23\"4\"5\", 6") + end + end + + def test_malformed_csv + assert_raise(CSV::MalformedCSVError) do + CSV.parse_line("1,2\r,3", row_sep: "\n") + 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 ) + end + + assert_raise(CSV::MalformedCSVError) { CSV.parse_line('1,2,"3...') } + + bad_data = <<-END_DATA.gsub(/^ +/, "") + 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"/ }) + + 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) + end + end + + def test_the_parse_fails_fast_when_it_can_for_unquoted_fields + assert_parse_errors_out('valid,fields,bad start"' + BIG_DATA) + end + + def test_the_parse_fails_fast_when_it_can_for_unescaped_quotes + assert_parse_errors_out('valid,fields,"bad start"unescaped' + BIG_DATA) + end + + def test_field_size_limit_controls_lookahead + assert_parse_errors_out( 'valid,fields,"' + BIG_DATA + '"', + field_size_limit: 2048 ) + 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 + + private + + def assert_parse_errors_out(*args) + assert_raise(CSV::MalformedCSVError) do + Timeout.timeout(0.2) do + CSV.parse(*args) + fail("Parse didn't error out") + end + end + end +end diff --git a/test/csv/test_csv_writing.rb b/test/csv/test_csv_writing.rb new file mode 100755 index 0000000000..de82dae244 --- /dev/null +++ b/test/csv/test_csv_writing.rb @@ -0,0 +1,98 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# frozen_string_literal: false + +# 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 new file mode 100755 index 0000000000..0786ca6d0f --- /dev/null +++ b/test/csv/test_data_converters.rb @@ -0,0 +1,273 @@ +#!/usr/bin/env ruby -w +# encoding: 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 "base" + +class TestCSV::DataConverters < TestCSV + 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 + + def test_builtin_integer_converter + # does convert + [-5, 1, 10000000000].each do |n| + assert_equal(n, CSV::Converters[:integer][n.to_s]) + end + + # does not convert + (%w{junk 1.0} + [""]).each do |str| + assert_equal(str, CSV::Converters[:integer][str]) + end + end + + def test_builtin_float_converter + # does convert + [-5.1234, 0, 2.3e-11].each do |n| + assert_equal(n, CSV::Converters[:float][n.to_s]) + end + + # does not convert + (%w{junk 1..0 .015F} + [""]).each do |str| + assert_equal(str, CSV::Converters[:float][str]) + end + end + + def test_builtin_date_converter + # does convert + assert_instance_of( + Date, + CSV::Converters[:date][@win_safe_time_str.sub(/\d+:\d+:\d+ /, "")] + ) + + # does not convert + assert_instance_of(String, CSV::Converters[:date]["junk"]) + end + + def test_builtin_date_time_converter + # does convert + assert_instance_of( DateTime, + CSV::Converters[:date_time][@win_safe_time_str] ) + + # does not convert + 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) + 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) + 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 } ) + 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, Integer, String, Float], + @parser.shift.map { |field| field.class } ) + end + + def test_builtin_numeric_combo_converter + # setup parser... + assert_nothing_raised(Exception) { @parser.convert(:numeric) } + + # and use + assert_equal( [String, String, Integer, String, Float], + @parser.shift.map { |field| field.class } ) + 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, Integer, String, Float, DateTime], + @parser.shift.map { |field| field.class } ) + 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) + 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) + 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) + 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 + + # and use + assert_equal( ["Numbers", ":integer", 100, ":float", "3.015"], + @parser.shift.fields ) + end + + def test_custom_converter_with_blank_field + converter = lambda { |field| field.nil? } + row = nil + assert_nothing_raised(Exception) do + row = CSV.parse_line('nil,', converters: converter) + end + assert_equal([false, true], row); + end + + def test_shortcut_interface + assert_equal( ["Numbers", ":integer", 1, ":float", 3.015], + CSV.parse_line(@data, converters: :numeric) ) + + assert_equal( ["Numbers", ":integer", 1, ":float", 3.015], + CSV.parse_line(@data, converters: [:integer, :float]) ) + + assert_equal( ["Numbers", :integer, 1, :float, 3.015], + CSV.parse_line(@data, converters: [:numeric, @custom]) ) + 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 + + 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) + + 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) + + 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) + + 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) + end +end diff --git a/test/csv/test_encodings.rb b/test/csv/test_encodings.rb new file mode 100755 index 0000000000..7460a3ff34 --- /dev/null +++ b/test/csv/test_encodings.rb @@ -0,0 +1,338 @@ +#!/usr/bin/env ruby -w +# encoding: 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 "base" + +class TestCSV::Encodings < TestCSV + extend DifferentOFS + + def setup + super + require 'tempfile' + @temp_csv_file = Tempfile.new(%w"test_csv. .csv") + @temp_csv_path = @temp_csv_file.path + @temp_csv_file.close + end + + def teardown + @temp_csv_file.close! + super + end + + ######################################## + ### Hand Test Some Popular Encodings ### + ######################################## + + def test_parses_utf8_encoding + assert_parses( [ %w[ one two … ], + %w[ 1 … 3 ], + %w[ … 5 6 ] ], "UTF-8" ) + end + + def test_parses_latin1_encoding + assert_parses( [ %w[ one two Résumé ], + %w[ 1 Résumé 3 ], + %w[ Résumé 5 6 ] ], "ISO-8859-1" ) + end + + def test_parses_utf16be_encoding + assert_parses( [ %w[ one two … ], + %w[ 1 … 3 ], + %w[ … 5 6 ] ], "UTF-16BE" ) + end + + def test_parses_shift_jis_encoding + assert_parses( [ %w[ 一 二 三 ], + %w[ 四 五 六 ], + %w[ 七 八 九 ] ], "Shift_JIS" ) + end + + ########################################################### + ### Try Simple Reading for All Non-dummy Ruby Encodings ### + ########################################################### + + def test_reading_with_most_encodings + each_encoding do |encoding| + begin + assert_parses( [ %w[ abc def ], + %w[ ghi jkl ] ], encoding ) + rescue Encoding::ConverterNotFoundError + fail("Failed to support #{encoding.name}.") + end + end + end + + def test_regular_expression_escaping + each_encoding do |encoding| + begin + assert_parses( [ %w[ abc def ], + %w[ ghi jkl ] ], encoding, col_sep: "|" ) + rescue Encoding::ConverterNotFoundError + fail("Failed to properly escape #{encoding.name}.") + end + end + end + + def test_read_with_default_encoding + data = "abc" + default_external = Encoding.default_external + each_encoding do |encoding| + File.open(@temp_csv_path, "wb", encoding: encoding) {|f| f << data} + begin + no_warnings do + Encoding.default_external = encoding + end + result = CSV.read(@temp_csv_path)[0][0] + ensure + no_warnings do + Encoding.default_external = default_external + end + end + assert_equal(encoding, result.encoding) + end + end + + ####################################################################### + ### Stress Test ASCII Compatible and Non-ASCII Compatible Encodings ### + ####################################################################### + + def test_auto_line_ending_detection + # arrange data to place a \r at the end of CSV's read ahead point + encode_for_tests([["a" * 509]], row_sep: "\r\n") do |data| + assert_equal("\r\n".encode(data.encoding), CSV.new(data).row_sep) + end + end + + def test_csv_chars_are_transcoded + encode_for_tests([%w[abc def]]) do |data| + %w[col_sep row_sep quote_char].each do |csv_char| + assert_equal( "|".encode(data.encoding), + CSV.new(data, csv_char.to_sym => "|").send(csv_char) ) + end + end + end + + def test_parser_works_with_encoded_headers + encode_for_tests([%w[one two three], %w[1 2 3]]) do |data| + parsed = CSV.parse(data, headers: true) + assert_all?(parsed.headers, "Wrong data encoding.") {|h| h.encoding == data.encoding} + parsed.each do |row| + assert_all?(row.fields, "Wrong data encoding.") {|f| f.encoding == data.encoding} + end + end + end + + def test_built_in_converters_transcode_to_utf_8_then_convert + encode_for_tests([%w[one two three], %w[1 2 3]]) do |data| + parsed = CSV.parse(data, converters: :integer) + assert_all?(parsed[0], "Wrong data encoding.") {|f| f.encoding == data.encoding} + assert_equal([1, 2, 3], parsed[1]) + end + end + + def test_built_in_header_converters_transcode_to_utf_8_then_convert + encode_for_tests([%w[one two three], %w[1 2 3]]) do |data| + parsed = CSV.parse( data, headers: true, + header_converters: :downcase ) + assert_all?(parsed.headers, "Wrong data encoding.") {|h| h.encoding.name == "UTF-8"} + assert_all?(parsed[0].fields, "Wrong data encoding.") {|f| f.encoding == data.encoding} + end + end + + def test_open_allows_you_to_set_encodings + encode_for_tests([%w[abc def]]) do |data| + # read and write in encoding + File.open(@temp_csv_path, "wb:#{data.encoding.name}") { |f| f << data } + CSV.open(@temp_csv_path, "rb:#{data.encoding.name}") do |csv| + csv.each do |row| + assert_all?(row, "Wrong data encoding.") {|f| f.encoding == data.encoding} + end + end + + # read and write with transcoding + File.open(@temp_csv_path, "wb:UTF-32BE:#{data.encoding.name}") do |f| + f << data + end + CSV.open(@temp_csv_path, "rb:UTF-32BE:#{data.encoding.name}") do |csv| + csv.each do |row| + assert_all?(row, "Wrong data encoding.") {|f| f.encoding == data.encoding} + end + end + end + end + + def test_foreach_allows_you_to_set_encodings + encode_for_tests([%w[abc def]]) do |data| + # read and write in encoding + File.open(@temp_csv_path, "wb", encoding: data.encoding) { |f| f << data } + CSV.foreach(@temp_csv_path, encoding: data.encoding) do |row| + row.each {|f| assert_equal(f.encoding, data.encoding)} + end + + # read and write with transcoding + File.open(@temp_csv_path, "wb:UTF-32BE:#{data.encoding.name}") do |f| + f << data + end + CSV.foreach( @temp_csv_path, + encoding: "UTF-32BE:#{data.encoding.name}" ) do |row| + assert_all?(row, "Wrong data encoding.") {|f| f.encoding == data.encoding} + end + end + end + + def test_read_allows_you_to_set_encodings + encode_for_tests([%w[abc def]]) do |data| + # read and write in encoding + File.open(@temp_csv_path, "wb:#{data.encoding.name}") { |f| f << data } + rows = CSV.read(@temp_csv_path, encoding: data.encoding.name) + assert_all?(rows.flatten, "Wrong data encoding.") {|f| f.encoding == data.encoding} + + # read and write with transcoding + File.open(@temp_csv_path, "wb:UTF-32BE:#{data.encoding.name}") do |f| + f << data + end + rows = CSV.read( @temp_csv_path, + encoding: "UTF-32BE:#{data.encoding.name}" ) + assert_all?(rows.flatten, "Wrong data encoding.") {|f| f.encoding == data.encoding} + end + end + + ################################# + ### Write CSV in any Encoding ### + ################################# + + def test_can_write_csv_in_any_encoding + each_encoding do |encoding| + # test generate_line with encoding hint + begin + csv = %w[abc d|ef].map { |f| f.encode(encoding) }. + to_csv(col_sep: "|", encoding: encoding.name) + rescue Encoding::ConverterNotFoundError + next + end + assert_equal(encoding, csv.encoding) + + # test generate_line with encoding guessing from fields + csv = %w[abc d|ef].map { |f| f.encode(encoding) }.to_csv(col_sep: "|") + assert_equal(encoding, csv.encoding) + + # writing to files + data = encode_ary([%w[abc d,ef], %w[123 456 ]], encoding) + CSV.open(@temp_csv_path, "wb:#{encoding.name}") do |f| + data.each { |row| f << row } + end + assert_equal(data, CSV.read(@temp_csv_path, encoding: encoding.name)) + end + end + + def test_encoding_is_upgraded_during_writing_as_needed + data = ["foo".force_encoding("US-ASCII"), "\u3042"] + assert_equal("US-ASCII", data.first.encoding.name) + assert_equal("UTF-8", data.last.encoding.name) + assert_equal("UTF-8", data.join('').encoding.name) + assert_equal("UTF-8", data.to_csv.encoding.name) + end + + def test_encoding_is_upgraded_for_ascii_content_during_writing_as_needed + data = ["foo".force_encoding("ISO-8859-1"), "\u3042"] + assert_equal("ISO-8859-1", data.first.encoding.name) + assert_equal("UTF-8", data.last.encoding.name) + assert_equal("UTF-8", data.join('').encoding.name) + assert_equal("UTF-8", data.to_csv.encoding.name) + end + + def test_explicit_encoding + bug9766 = '[ruby-core:62113] [Bug #9766]' + s = CSV.generate(encoding: "Windows-31J") do |csv| + csv << ["foo".force_encoding("ISO-8859-1"), "\u3042"] + end + assert_equal(["foo,\u3042\n".encode(Encoding::Windows_31J), Encoding::Windows_31J], [s, s.encoding], bug9766) + end + + private + + 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) + 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.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.each_with_index do |row, i| + assert_equal(orig_fields[i], row) + end + end unless encoding == __ENCODING__ + rescue Encoding::ConverterNotFoundError + end + options[:encoding] = encoding.name + CSV.open(@temp_csv_path, options) do |csv| + csv.each_with_index do |row, i| + assert_equal(fields[i], row) + end + end + options.delete(:encoding) + options[:external_encoding] = encoding.name + options[:internal_encoding] = __ENCODING__.name + begin + CSV.open(@temp_csv_path, options) do |csv| + csv.each_with_index do |row, i| + assert_equal(orig_fields[i], row) + end + end unless encoding == __ENCODING__ + rescue Encoding::ConverterNotFoundError + end + end + + def encode_ary(ary, encoding) + ary.map { |row| row.map { |field| field.encode(encoding) } } + end + + 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) + row_sep = (options[:row_sep] || "\n").encode(encoding) + ary.map { |row| + row.map { |field| + [quote_char, field.encode(encoding), quote_char].join('') + }.join(col_sep) + row_sep + }.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) + end + + def each_encoding + Encoding.list.each do |encoding| + next if encoding.dummy? # skip "dummy" encodings + yield encoding + end + end + + def no_warnings + old_verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = old_verbose + end +end diff --git a/test/csv/test_features.rb b/test/csv/test_features.rb new file mode 100755 index 0000000000..6b73093ad3 --- /dev/null +++ b/test/csv/test_features.rb @@ -0,0 +1,380 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# frozen_string_literal: false + +# 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. + +begin + require "zlib" +rescue LoadError +end + +require_relative "base" +require "tempfile" + +class TestCSV::Features < TestCSV + extend DifferentOFS + + TEST_CASES = [ [%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""",\nc}, ["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]] ] + + def setup + super + @sample_data = <<-END_DATA.gsub(/^ +/, "") + line,1,abc + line,2,"def\nghi" + + line,4,jkl + END_DATA + @csv = CSV.new(@sample_data) + end + + def test_col_sep + [";", "\t"].each do |sep| + TEST_CASES.each do |test_case| + assert_equal( test_case.last.map { |t| t.tr(",", sep) unless t.nil? }, + CSV.parse_line( test_case.first.tr(",", sep), + col_sep: sep ) ) + end + end + assert_equal([",,,", nil], CSV.parse_line(",,,;", col_sep: ";")) + 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") + end + 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: "'" ) ) + end + end + + def test_bug_8405 + 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_csv_char_readers + %w[col_sep row_sep quote_char].each do |reader| + csv = CSV.new("abc,def", reader.to_sym => "|") + assert_equal("|", csv.send(reader)) + end + end + + def test_row_sep_auto_discovery + ["\r\n", "\n", "\r"].each do |line_end| + data = "1,2,3#{line_end}4,5#{line_end}" + discovered = CSV.new(data).row_sep + assert_equal(line_end, discovered) + end + + assert_equal("\n", CSV.new("\n\r\n\r").row_sep) + + assert_equal($/, CSV.new("").row_sep) + + assert_equal($/, CSV.new(STDERR).row_sep) + end + + def test_line + lines = [ + %Q(abc,def\n), + %Q(abc,"d\nef"\n), + %Q(abc,"d\r\nef"\n), + %Q(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) + + 4.times do |line_count| + assert_equal(line_count, @csv.lineno) + assert_not_nil(@csv.shift) + assert_equal(line_count + 1, @csv.lineno) + end + assert_nil(@csv.shift) + end + + def test_readline + test_lineno + + @csv.rewind + + test_lineno + end + + def test_unknown_options + 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 + assert_equal(4, @csv.to_a.size) + + @csv = CSV.new(@sample_data, skip_blanks: true) + + count = 0 + @csv.each do |row| + count += 1 + assert_equal("line", row.first) + end + assert_equal(3, count) + end + + def test_liberal_parsing + input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' + assert_raise(CSV::MalformedCSVError) do + CSV.parse_line(input) + end + assert_equal(["Johnson, Dwayne", 'Dwayne "The Rock" Johnson'], + CSV.parse_line(input, liberal_parsing: true)) + + input = '"quoted" field' + assert_raise(CSV::MalformedCSVError) do + CSV.parse_line(input) + end + assert_equal(['"quoted" field'], + CSV.parse_line(input, liberal_parsing: true)) + + assert_raise(CSV::MalformedCSVError) do + CSV.parse_line('is,this "three," or four,fields', liberal_parsing: true) + end + + assert_equal(["is", 'this "three', ' or four"', "fields"], + CSV.parse_line('is,this "three, or four",fields', liberal_parsing: true)) + end + + def test_csv_behavior_readers + %w[ unconverted_fields return_headers write_headers + skip_blanks force_quotes ].each do |behavior| + assert_not_predicate(CSV.new("abc,def"), "#{behavior}?", "Behavior defaulted to on.") + csv = CSV.new("abc,def", behavior.to_sym => true) + assert_predicate(csv, "#{behavior}?", "Behavior change now registered.") + end + end + + def test_converters_reader + # no change + assert_equal( [:integer], + CSV.new("abc,def", converters: [:integer]).converters ) + + # just one + assert_equal( [:integer], + CSV.new("abc,def", converters: :integer).converters ) + + # expanded + assert_equal( [:integer, :float], + CSV.new("abc,def", converters: :numeric).converters ) + + # custom + csv = CSV.new("abc,def", converters: [:integer, lambda { }]) + assert_equal(2, csv.converters.size) + assert_equal(:integer, csv.converters.first) + assert_instance_of(Proc, csv.converters.last) + end + + def test_header_converters_reader + # no change + hc = :header_converters + assert_equal([:downcase], CSV.new("abc,def", hc => [:downcase]).send(hc)) + + # just one + assert_equal([:downcase], CSV.new("abc,def", hc => :downcase).send(hc)) + + # custom + csv = CSV.new("abc,def", hc => [:symbol, lambda { }]) + assert_equal(2, csv.send(hc).size) + assert_equal(:symbol, csv.send(hc).first) + assert_instance_of(Proc, csv.send(hc).last) + end + + # reported by Kev Jackson + def test_failing_to_escape_col_sep_bug_fix + 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 + 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 + zipped = nil + assert_nothing_raised(NoMethodError) do + zipped = CSV.new( + Zlib::GzipReader.open( + File.join(File.dirname(__FILE__), "line_endings.gz") + ) + ) + end + assert_equal("\r\n", zipped.row_sep) + ensure + zipped.close + end if defined?(Zlib::GzipReader) + + def test_gzip_writer_bug_fix + Tempfile.create(%w"temp .gz") {|tempfile| + tempfile.close + file = tempfile.path + zipped = nil + assert_nothing_raised(NoMethodError) do + zipped = CSV.new(Zlib::GzipWriter.open(file)) + end + zipped << %w[one two three] + zipped << [1, 2, 3] + zipped.close + + assert_include(Zlib::GzipReader.open(file) {|f| f.read}, + $INPUT_RECORD_SEPARATOR, "@row_sep did not default") + } + end if defined?(Zlib::GzipWriter) + + def test_inspect_is_smart_about_io_types + str = CSV.new("string,data").inspect + assert_include(str, "io_type:StringIO", "IO type not detected.") + + str = CSV.new($stderr).inspect + assert_include(str, "io_type:$stderr", "IO type not detected.") + + Tempfile.create(%w"temp .csv") {|tempfile| + tempfile.close + path = tempfile.path + File.open(path, "w") { |csv| csv << "one,two,three\n1,2,3\n" } + str = CSV.open(path) { |csv| csv.inspect } + assert_include(str, "io_type:File", "IO type not detected.") + } + end + + def test_inspect_shows_key_attributes + str = @csv.inspect + %w[lineno col_sep row_sep quote_char].each do |attr_name| + assert_match(/\b#{attr_name}:[^\s>]+/, str) + end + 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 + 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 + 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) + end + + def test_accepts_comment_skip_lines_option + assert_nothing_raised(ArgumentError) do + CSV.new(@sample_data, :skip_lines => /\A\s*#/) + end + end + + def test_accepts_comment_defaults_to_nil + c = CSV.new(@sample_data) + assert_nil(c.skip_lines) + end + + class RegexStub + 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 + 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 + end + + def test_comment_rows_are_ignored_with_heredoc + c = CSV.new(<<~EOL, skip_lines: ".") + 1,foo + .2,bar + 3,baz + EOL + assert_equal [["1", "foo"], ["3", "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 + 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 + end + + def test_table_nil_equality + assert_nothing_raised(NoMethodError) { CSV.parse("test", headers: true) == nil } + end +end diff --git a/test/csv/test_headers.rb b/test/csv/test_headers.rb new file mode 100755 index 0000000000..5ec8932d95 --- /dev/null +++ b/test/csv/test_headers.rb @@ -0,0 +1,305 @@ +#!/usr/bin/env ruby -w +# encoding: 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 "base" + +class TestCSV::Headers < TestCSV + extend DifferentOFS + + def setup + super + @data = <<-END_CSV.gsub(/^\s+/, "") + first,second,third + A,B,C + 1,2,3 + END_CSV + end + + def test_first_row + [:first_row, true].each do |setting| # two names for the same setting + # activate headers + csv = nil + assert_nothing_raised(Exception) do + csv = CSV.parse(@data, headers: setting) + end + + # first data row - skipping headers + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{first A}, %w{second B}, %w{third C}], row.to_a) + + # second data row + row = csv[1] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{first 1}, %w{second 2}, %w{third 3}], row.to_a) + + # empty + assert_nil(csv[2]) + end + end + + def test_array_of_headers + # activate headers + csv = nil + assert_nothing_raised(Exception) do + csv = CSV.parse(@data, headers: [:my, :new, :headers]) + end + + # first data row - skipping headers + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal( [[:my, "first"], [:new, "second"], [:headers, "third"]], + row.to_a ) + + # second data row + row = csv[1] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([[:my, "A"], [:new, "B"], [:headers, "C"]], row.to_a) + + # third data row + row = csv[2] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([[:my, "1"], [:new, "2"], [:headers, "3"]], row.to_a) + + # empty + assert_nil(csv[3]) + + # with return and convert + assert_nothing_raised(Exception) do + csv = CSV.parse( @data, headers: [:my, :new, :headers], + return_headers: true, + header_converters: lambda { |h| h.to_s } ) + end + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([["my", :my], ["new", :new], ["headers", :headers]], row.to_a) + assert_predicate(row, :header_row?) + assert_not_predicate(row, :field_row?) + end + + def test_csv_header_string + # activate headers + csv = nil + assert_nothing_raised(Exception) do + csv = CSV.parse(@data, headers: "my,new,headers") + end + + # first data row - skipping headers + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{my first}, %w{new second}, %w{headers third}], row.to_a) + + # second data row + row = csv[1] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{my A}, %w{new B}, %w{headers C}], row.to_a) + + # third data row + row = csv[2] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{my 1}, %w{new 2}, %w{headers 3}], row.to_a) + + # empty + assert_nil(csv[3]) + + # with return and convert + assert_nothing_raised(Exception) do + csv = CSV.parse( @data, headers: "my,new,headers", + return_headers: true, + header_converters: :symbol ) + end + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([[:my, "my"], [:new, "new"], [:headers, "headers"]], row.to_a) + assert_predicate(row, :header_row?) + assert_not_predicate(row, :field_row?) + end + + def test_csv_header_string_inherits_separators + # parse with custom col_sep + csv = nil + assert_nothing_raised(Exception) do + csv = CSV.parse( @data.tr(",", "|"), col_sep: "|", + headers: "my|new|headers" ) + end + + # verify headers were recognized + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{my first}, %w{new second}, %w{headers third}], row.to_a) + end + + def test_return_headers + # activate headers and request they are returned + csv = nil + assert_nothing_raised(Exception) do + csv = CSV.parse(@data, headers: true, return_headers: true) + end + + # header row + row = csv[0] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal( [%w{first first}, %w{second second}, %w{third third}], + row.to_a ) + assert_predicate(row, :header_row?) + assert_not_predicate(row, :field_row?) + + # first data row - skipping headers + row = csv[1] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{first A}, %w{second B}, %w{third C}], row.to_a) + assert_not_predicate(row, :header_row?) + assert_predicate(row, :field_row?) + + # second data row + row = csv[2] + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([%w{first 1}, %w{second 2}, %w{third 3}], row.to_a) + assert_not_predicate(row, :header_row?) + assert_predicate(row, :field_row?) + + # empty + assert_nil(csv[3]) + end + + 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 + + # normal converters do not affect headers + csv = CSV.parse( data, headers: true, + return_headers: true, + converters: :numeric ) + assert_equal([%w{1 1}, %w{2 2}, %w{3 3}], csv[0].to_a) + assert_equal([["1", 1], ["2", 2], ["3", 3]], csv[1].to_a) + assert_nil(csv[2]) + + # header converters do affect headers (only) + assert_nothing_raised(Exception) do + csv = CSV.parse( data, headers: true, + return_headers: true, + converters: :numeric, + header_converters: :symbol ) + end + assert_equal([[:"1", "1"], [:"2", "2"], [:"3", "3"]], csv[0].to_a) + assert_equal([[:"1", 1], [:"2", 2], [:"3", 3]], csv[1].to_a) + assert_nil(csv[2]) + end + + def test_builtin_downcase_converter + csv = CSV.parse( "One,TWO Three", headers: true, + return_headers: true, + header_converters: :downcase ) + assert_equal(%w{one two\ three}, csv.headers) + end + + def test_builtin_symbol_converter + # Note that the trailing space is intentional + 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_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] ) + assert_equal([:one, nil, :three], csv.headers) + end + + def test_custom_converter + converter = lambda { |header| header.tr(" ", "_") } + csv = CSV.parse( "One,TWO Three", + headers: true, + return_headers: true, + header_converters: converter ) + assert_equal(%w{One TWO_Three}, csv.headers) + end + + def test_table_support + csv = nil + assert_nothing_raised(Exception) do + csv = CSV.parse(@data, headers: true) + end + + assert_instance_of(CSV::Table, csv) + end + + def test_skip_blanks + @data = <<-END_CSV.gsub(/^ +/, "") + + + A,B,C + + 1,2,3 + + + + END_CSV + + expected = [%w[1 2 3]] + CSV.parse(@data, headers: true, skip_blanks: true) do |row| + assert_equal(expected.shift, row.fields) + end + + expected = [%w[A B C], %w[1 2 3]] + CSV.parse( @data, + headers: true, + return_headers: true, + skip_blanks: true ) do |row| + assert_equal(expected.shift, row.fields) + end + end + + def test_headers_reader + # no headers + assert_nil(CSV.new(@data).headers) + + # headers + csv = CSV.new(@data, headers: true) + assert_equal(true, csv.headers) # before headers are read + csv.shift # set headers + assert_equal(%w[first second third], csv.headers) # after headers are read + end + + def test_blank_row_bug_fix + @data += "\n#{@data}" # add a blank row + + # ensure that everything returned is a Row object + CSV.parse(@data, headers: true) do |row| + assert_instance_of(CSV::Row, row) + end + end +end diff --git a/test/csv/test_interface.rb b/test/csv/test_interface.rb new file mode 100755 index 0000000000..be27fcd616 --- /dev/null +++ b/test/csv/test_interface.rb @@ -0,0 +1,393 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# frozen_string_literal: false + +# 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 + + def test_open_handles_prematurely_closed_file_descriptor_gracefully + assert_nothing_raised(Exception) do + CSV.open(@path) do |csv| + csv.close + end + 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) + + line = CSV.generate_line(%w"1 2", row_sep: nil) + assert_equal("1,2", 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_write_headers_empty + File.unlink(@path) + + CSV.open( @path, "wb", headers: "b|a|c", + write_headers: true, + col_sep: "|" ) do |csv| + end + + File.open(@path, "rb") do |f| + assert_equal("b|a|c", f.gets.strip) + 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_row.rb b/test/csv/test_row.rb new file mode 100755 index 0000000000..1cb851b027 --- /dev/null +++ b/test/csv/test_row.rb @@ -0,0 +1,380 @@ +#!/usr/bin/env ruby -w +# encoding: 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 "base" + +class TestCSV::Row < TestCSV + extend DifferentOFS + + def setup + super + @row = CSV::Row.new(%w{A B C A A}, [1, 2, 3, 4]) + end + + def test_initialize + # basic + row = CSV::Row.new(%w{A B C}, [1, 2, 3]) + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([["A", 1], ["B", 2], ["C", 3]], row.to_a) + + # missing headers + row = CSV::Row.new(%w{A}, [1, 2, 3]) + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([["A", 1], [nil, 2], [nil, 3]], row.to_a) + + # missing fields + row = CSV::Row.new(%w{A B C}, [1, 2]) + assert_not_nil(row) + assert_instance_of(CSV::Row, row) + assert_equal([["A", 1], ["B", 2], ["C", nil]], row.to_a) + end + + def test_row_type + # field rows + row = CSV::Row.new(%w{A B C}, [1, 2, 3]) # implicit + assert_not_predicate(row, :header_row?) + assert_predicate(row, :field_row?) + row = CSV::Row.new(%w{A B C}, [1, 2, 3], false) # explicit + assert_not_predicate(row, :header_row?) + assert_predicate(row, :field_row?) + + # header row + row = CSV::Row.new(%w{A B C}, [1, 2, 3], true) + assert_predicate(row, :header_row?) + assert_not_predicate(row, :field_row?) + end + + def test_headers + assert_equal(%w{A B C A A}, @row.headers) + end + + def test_field + # by name + assert_equal(2, @row.field("B")) + assert_equal(2, @row["B"]) # alias + + # 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)) + + # minimum index + assert_equal(1, @row.field("A")) + assert_equal(1, @row.field("A", 0)) + assert_equal(4, @row.field("A", 1)) + assert_equal(4, @row.field("A", 2)) + assert_equal(4, @row.field("A", 3)) + assert_equal(nil, @row.field("A", 4)) + assert_equal(nil, @row.field("A", 5)) + end + + def test_fetch + # only by name + assert_equal(2, @row.fetch('B')) + + # missing header raises KeyError + assert_raise KeyError do + @row.fetch('foo') + end + + # missing header yields itself to block + assert_equal 'bar', @row.fetch('foo') { |header| + header == 'foo' ? 'bar' : false } + + # missing header returns the given default value + assert_equal 'bar', @row.fetch('foo', 'bar') + + # more than one vararg raises ArgumentError + assert_raise ArgumentError do + @row.fetch('foo', 'bar', 'baz') + end + end + + def test_has_key? + assert_equal(true, @row.has_key?('B')) + assert_equal(false, @row.has_key?('foo')) + end + + def test_set_field + # set field by name + assert_equal(100, @row["A"] = 100) + + # set field by index + assert_equal(300, @row[3] = 300) + + # set field by name and minimum index + assert_equal([:a, :b, :c], @row["A", 4] = [:a, :b, :c]) + + # verify the changes + assert_equal( [ ["A", 100], + ["B", 2], + ["C", 3], + ["A", 300], + ["A", [:a, :b, :c]] ], @row.to_a ) + + # assigning an index past the end + assert_equal("End", @row[10] = "End") + assert_equal( [ ["A", 100], + ["B", 2], + ["C", 3], + ["A", 300], + ["A", [:a, :b, :c]], + [nil, nil], + [nil, nil], + [nil, nil], + [nil, nil], + [nil, nil], + [nil, "End"] ], @row.to_a ) + + # assigning a new field by header + assert_equal("New", @row[:new] = "New") + assert_equal( [ ["A", 100], + ["B", 2], + ["C", 3], + ["A", 300], + ["A", [:a, :b, :c]], + [nil, nil], + [nil, nil], + [nil, nil], + [nil, nil], + [nil, nil], + [nil, "End"], + [:new, "New"] ], @row.to_a ) + end + + def test_append + # add a value + assert_equal(@row, @row << "Value") + assert_equal( [ ["A", 1], + ["B", 2], + ["C", 3], + ["A", 4], + ["A", nil], + [nil, "Value"] ], @row.to_a ) + + # add a pair + assert_equal(@row, @row << %w{Header Field}) + assert_equal( [ ["A", 1], + ["B", 2], + ["C", 3], + ["A", 4], + ["A", nil], + [nil, "Value"], + %w{Header Field} ], @row.to_a ) + + # a pair with Hash syntax + assert_equal(@row, @row << {key: :value}) + assert_equal( [ ["A", 1], + ["B", 2], + ["C", 3], + ["A", 4], + ["A", nil], + [nil, "Value"], + %w{Header Field}, + [:key, :value] ], @row.to_a ) + + # multiple fields at once + assert_equal(@row, @row.push(100, 200, [:last, 300])) + assert_equal( [ ["A", 1], + ["B", 2], + ["C", 3], + ["A", 4], + ["A", nil], + [nil, "Value"], + %w{Header Field}, + [:key, :value], + [nil, 100], + [nil, 200], + [:last, 300] ], @row.to_a ) + end + + def test_delete + # by index + assert_equal(["B", 2], @row.delete(1)) + + # by header + assert_equal(["C", 3], @row.delete("C")) + + end + + def test_delete_if + assert_equal(@row, @row.delete_if { |h, f| h == "A" and not f.nil? }) + 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 + # all fields + assert_equal([1, 2, 3, 4, nil], @row.fields) + + # by header + assert_equal([1, 3], @row.fields("A", "C")) + + # by index + assert_equal([2, 3, nil], @row.fields(1, 2, 10)) + + # by both + assert_equal([2, 3, 4], @row.fields("B", "C", 3)) + + # with minimum indices + assert_equal([2, 3, 4], @row.fields("B", "C", ["A", 3])) + + # by header range + assert_equal([2, 3], @row.values_at("B".."C")) + end + + def test_index + # basic usage + assert_equal(0, @row.index("A")) + assert_equal(1, @row.index("B")) + assert_equal(2, @row.index("C")) + assert_equal(nil, @row.index("Z")) + + # with minimum index + assert_equal(0, @row.index("A")) + assert_equal(0, @row.index("A", 0)) + assert_equal(3, @row.index("A", 1)) + assert_equal(3, @row.index("A", 2)) + assert_equal(3, @row.index("A", 3)) + assert_equal(4, @row.index("A", 4)) + assert_equal(nil, @row.index("A", 5)) + 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)) + assert(!@row.field?(10)) + end + + def test_each + # array style + ary = @row.to_a + @row.each do |pair| + assert_equal(ary.first.first, pair.first) + assert_equal(ary.shift.last, pair.last) + end + + # hash style + ary = @row.to_a + @row.each do |header, field| + assert_equal(ary.first.first, header) + assert_equal(ary.shift.last, field) + end + + # 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_enumerable + assert_equal( [["A", 1], ["A", 4], ["A", nil]], + @row.select { |pair| pair.first == "A" } ) + + assert_equal(10, @row.inject(0) { |sum, (_, n)| sum + (n || 0) }) + end + + def test_to_a + row = CSV::Row.new(%w{A B C}, [1, 2, 3]).to_a + assert_instance_of(Array, row) + row.each do |pair| + assert_instance_of(Array, pair) + assert_equal(2, pair.size) + end + assert_equal([["A", 1], ["B", 2], ["C", 3]], row) + end + + def test_to_hash + hash = @row.to_hash + assert_equal({"A" => nil, "B" => 2, "C" => 3}, hash) + hash.keys.each_with_index do |string_key, h| + assert_predicate(string_key, :frozen?) + assert_same(string_key, @row.headers[h]) + end + end + + def test_to_csv + # normal conversion + assert_equal("1,2,3,4,\n", @row.to_csv) + assert_equal("1,2,3,4,\n", @row.to_s) # alias + + # with options + assert_equal( "1|2|3|4|\r\n", + @row.to_csv(col_sep: "|", row_sep: "\r\n") ) + end + + def test_array_delegation + assert_not_empty(@row, "Row was empty.") + + assert_equal([@row.headers.size, @row.fields.size].max, @row.size) + end + + def test_inspect_shows_header_field_pairs + str = @row.inspect + @row.each do |header, field| + assert_include(str, "#{header.inspect}:#{field.inspect}", + "Header field pair not found.") + end + end + + def test_inspect_encoding_is_ascii_compatible + assert_send([Encoding, :compatible?, + Encoding.find("US-ASCII"), + @row.inspect.encoding], + "inspect() was not ASCII compatible.") + end + + def test_inspect_shows_symbol_headers_as_bare_attributes + str = CSV::Row.new(@row.headers.map { |h| h.to_sym }, @row.fields).inspect + @row.each do |header, field| + assert_include(str, "#{header}:#{field.inspect}", + "Header field pair not found.") + end + end + + def test_can_be_compared_with_other_classes + assert_not_nil(CSV::Row.new([ ], [ ]), "The row was nil") + end + + def test_can_be_compared_when_not_a_row + r = @row == [] + assert_equal false, r + end +end diff --git a/test/csv/test_table.rb b/test/csv/test_table.rb new file mode 100755 index 0000000000..25ef11a772 --- /dev/null +++ b/test/csv/test_table.rb @@ -0,0 +1,497 @@ +#!/usr/bin/env ruby -w +# encoding: 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 "base" + +class TestCSV::Table < TestCSV + extend DifferentOFS + + def setup + super + @rows = [ CSV::Row.new(%w{A B C}, [1, 2, 3]), + CSV::Row.new(%w{A B C}, [4, 5, 6]), + CSV::Row.new(%w{A B C}, [7, 8, 9]) ] + @table = CSV::Table.new(@rows) + + @header_table = CSV::Table.new( + [CSV::Row.new(%w{A B C}, %w{A B C}, true)] + @rows + ) + end + + def test_initialze + assert_not_nil(@table) + assert_instance_of(CSV::Table, @table) + end + + def test_modes + assert_equal(:col_or_row, @table.mode) + + # non-destructive changes, intended for one shot calls + cols = @table.by_col + assert_equal(:col_or_row, @table.mode) + assert_equal(:col, cols.mode) + assert_equal(@table, cols) + + rows = @table.by_row + assert_equal(:col_or_row, @table.mode) + assert_equal(:row, rows.mode) + assert_equal(@table, rows) + + # destructive mode changing calls + assert_equal(@table, @table.by_row!) + assert_equal(:row, @table.mode) + assert_equal(@table, @table.by_col_or_row!) + assert_equal(:col_or_row, @table.mode) + end + + def test_headers + assert_equal(@rows.first.headers, @table.headers) + end + + def test_headers_empty + t = CSV::Table.new([]) + assert_equal Array.new, t.headers + end + + def test_index + ################## + ### Mixed Mode ### + ################## + # by row + @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]) + end + assert_equal([nil] * @rows.size, @table["Z"]) # empty col + + # by cell, row then col + assert_equal(2, @table[0][1]) + assert_equal(6, @table[1]["C"]) + + # by cell, col then row + assert_equal(5, @table["B"][1]) + assert_equal(9, @table["C"][2]) + + # with headers (by col) + assert_equal(["B", 2, 5, 8], @header_table["B"]) + + ################### + ### Column Mode ### + ################### + @table.by_col! + + assert_equal([2, 5, 8], @table[1]) + assert_equal([2, 5, 8], @table["B"]) + + ################ + ### Row Mode ### + ################ + @table.by_row! + + assert_equal(@rows[1], @table[1]) + assert_raise(TypeError) { @table["B"] } + + ############################ + ### One Shot Mode Change ### + ############################ + assert_equal(@rows[1], @table[1]) + assert_equal([2, 5, 8], @table.by_col[1]) + assert_equal(@rows[1], @table[1]) + end + + def test_set_row_or_column + ################## + ### Mixed Mode ### + ################## + # set row + @table[2] = [10, 11, 12] + assert_equal([%w[A B C], [1, 2, 3], [4, 5, 6], [10, 11, 12]], @table.to_a) + + @table[3] = CSV::Row.new(%w[A B C], [13, 14, 15]) + assert_equal( [%w[A B C], [1, 2, 3], [4, 5, 6], [10, 11, 12], [13, 14, 15]], + @table.to_a ) + + # set col + @table["Type"] = "data" + assert_equal( [ %w[A B C Type], + [1, 2, 3, "data"], + [4, 5, 6, "data"], + [10, 11, 12, "data"], + [13, 14, 15, "data"] ], + @table.to_a ) + + @table["Index"] = [1, 2, 3] + assert_equal( [ %w[A B C Type Index], + [1, 2, 3, "data", 1], + [4, 5, 6, "data", 2], + [10, 11, 12, "data", 3], + [13, 14, 15, "data", nil] ], + @table.to_a ) + + @table["B"] = [100, 200] + assert_equal( [ %w[A B C Type Index], + [1, 100, 3, "data", 1], + [4, 200, 6, "data", 2], + [10, nil, 12, "data", 3], + [13, nil, 15, "data", nil] ], + @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 + + # with headers + @header_table["Type"] = "data" + assert_equal(%w[Type data data data], @header_table["Type"]) + + ################### + ### Column Mode ### + ################### + @table.by_col! + + @table[1] = [2, 5, 11, 14] + assert_equal( [ %w[A B C Type Index], + [1, 2, 3, "data", 1], + [4, 5, 6, "data", 2], + [10, 11, 12, "data", 3], + [13, 14, 15, "data", nil] ], + @table.to_a ) + + @table["Extra"] = "new stuff" + assert_equal( [ %w[A B C Type Index Extra], + [1, 2, 3, "data", 1, "new stuff"], + [4, 5, 6, "data", 2, "new stuff"], + [10, 11, 12, "data", 3, "new stuff"], + [13, 14, 15, "data", nil, "new stuff"] ], + @table.to_a ) + + ################ + ### Row Mode ### + ################ + @table.by_row! + + @table[1] = (1..6).to_a + assert_equal( [ %w[A B C Type Index Extra], + [1, 2, 3, "data", 1, "new stuff"], + [1, 2, 3, 4, 5, 6], + [10, 11, 12, "data", 3, "new stuff"], + [13, 14, 15, "data", nil, "new stuff"] ], + @table.to_a ) + + assert_raise(TypeError) { @table["Extra"] = nil } + end + + def test_set_by_col_with_header_row + r = [ CSV::Row.new(%w{X Y Z}, [97, 98, 99], true) ] + t = CSV::Table.new(r) + t.by_col! + t['A'] = [42] + assert_equal(['A'], t['A']) + end + + def test_each + ###################### + ### Mixed/Row Mode ### + ###################### + i = 0 + @table.each do |row| + assert_equal(@rows[i], row) + i += 1 + end + + # 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 ### + ################### + @table.by_col! + + headers = @table.headers + @table.each do |header, column| + assert_equal(headers.shift, header) + 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 ### + ############################ + @table.by_col_or_row! + + @table.each { |row| assert_instance_of(CSV::Row, row) } + @table.by_col.each { |tuple| assert_instance_of(Array, tuple) } + @table.each { |row| assert_instance_of(CSV::Row, row) } + end + + def test_enumerable + assert_equal( @rows.values_at(0, 2), + @table.select { |row| (row["B"] % 2).zero? } ) + + assert_equal(@rows[1], @table.find { |row| row["C"] > 5 }) + end + + def test_to_a + assert_equal([%w[A B C], [1, 2, 3], [4, 5, 6], [7, 8, 9]], @table.to_a) + + # with headers + assert_equal( [%w[A B C], [1, 2, 3], [4, 5, 6], [7, 8, 9]], + @header_table.to_a ) + end + + def test_to_csv + csv = <<-END_CSV.gsub(/^\s+/, "") + A,B,C + 1,2,3 + 4,5,6 + 7,8,9 + END_CSV + + # normal conversion + assert_equal(csv, @table.to_csv) + assert_equal(csv, @table.to_s) # alias + + # with options + assert_equal( csv.gsub(",", "|").gsub("\n", "\r\n"), + @table.to_csv(col_sep: "|", row_sep: "\r\n") ) + assert_equal( csv.lines.to_a[1..-1].join(''), + @table.to_csv(:write_headers => false) ) + + # with headers + assert_equal(csv, @header_table.to_csv) + end + + def test_append + # verify that we can chain the call + assert_equal(@table, @table << [10, 11, 12]) + + # Array append + assert_equal(CSV::Row.new(%w[A B C], [10, 11, 12]), @table[-1]) + + # Row append + assert_equal(@table, @table << CSV::Row.new(%w[A B C], [13, 14, 15])) + assert_equal(CSV::Row.new(%w[A B C], [13, 14, 15]), @table[-1]) + end + + def test_delete_mixed + ################## + ### Mixed Mode ### + ################## + # delete a row + assert_equal(@rows[1], @table.delete(1)) + + # delete a col + 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 + end + + def test_delete_column + ################### + ### Column Mode ### + ################### + @table.by_col! + + assert_equal(@rows.map { |row| row[0] }, @table.delete(0)) + 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 + end + + def test_delete_row + ################ + ### Row Mode ### + ################ + @table.by_row! + + assert_equal(@rows[1], @table.delete(1)) + 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 + end + + def test_delete_with_blank_rows + data = "col1,col2\nra1,ra2\n\nrb1,rb2" + table = CSV.parse(data, :headers => true) + assert_equal(["ra2", nil, "rb2"], table.delete("col2")) + end + + def test_delete_if_row + ###################### + ### Mixed/Row Mode ### + ###################### + # verify that we can chain the call + 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 + 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(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) + A,B,C + 4,5,6 + END_RESULT + end + + def test_delete_if_column + ################### + ### Column Mode ### + ################### + @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 + 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(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) + A + 1 + 4 + 7 + END_RESULT + end + + def test_values_at + ################## + ### Mixed Mode ### + ################## + # rows + assert_equal(@rows.values_at(0, 2), @table.values_at(0, 2)) + assert_equal(@rows.values_at(1..2), @table.values_at(1..2)) + + # cols + assert_equal([[1, 3], [4, 6], [7, 9]], @table.values_at("A", "C")) + assert_equal([[2, 3], [5, 6], [8, 9]], @table.values_at("B".."C")) + + ################### + ### Column Mode ### + ################### + @table.by_col! + + assert_equal([[1, 3], [4, 6], [7, 9]], @table.values_at(0, 2)) + assert_equal([[1, 3], [4, 6], [7, 9]], @table.values_at("A", "C")) + + ################ + ### Row Mode ### + ################ + @table.by_row! + + assert_equal(@rows.values_at(0, 2), @table.values_at(0, 2)) + assert_raise(TypeError) { @table.values_at("A", "C") } + + ############################ + ### One Shot Mode Change ### + ############################ + assert_equal(@rows.values_at(0, 2), @table.values_at(0, 2)) + assert_equal([[1, 3], [4, 6], [7, 9]], @table.by_col.values_at(0, 2)) + assert_equal(@rows.values_at(0, 2), @table.values_at(0, 2)) + end + + def test_array_delegation + assert_not_empty(@table, "Table was empty.") + + assert_equal(@rows.size, @table.size) + end + + def test_inspect_shows_current_mode + str = @table.inspect + assert_include(str, "mode:#{@table.mode}", "Mode not shown.") + + @table.by_col! + str = @table.inspect + assert_include(str, "mode:#{@table.mode}", "Mode not shown.") + end + + def test_inspect_encoding_is_ascii_compatible + assert_send([Encoding, :compatible?, + Encoding.find("US-ASCII"), + @table.inspect.encoding], + "inspect() was not ASCII compatible." ) + end +end diff --git a/test/csv/ts_all.rb b/test/csv/ts_all.rb new file mode 100644 index 0000000000..9eadf12918 --- /dev/null +++ b/test/csv/ts_all.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# frozen_string_literal: false + +# 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" |
