summaryrefslogtreecommitdiff
path: root/test/csv
diff options
context:
space:
mode:
authorjeg2 <jeg2@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2007-12-25 02:46:26 +0000
committerjeg2 <jeg2@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2007-12-25 02:46:26 +0000
commit29ba327bd16fd7eb7114050724e4784bdcaac83a (patch)
tree2779c5d0c96275f397aaaa286686479ef4f83649 /test/csv
parent0d570142ba2b7488804729b3cc1a9b4d87db1014 (diff)
* lib/csv.rb: Import the FasterCSV source as the new CSV class.
* test/csv/*: Added all applicable tests from FasterCSV. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@14646 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'test/csv')
-rw-r--r--test/csv/line_endings.gzbin0 -> 59 bytes
-rw-r--r--test/csv/tc_csv_parsing.rb164
-rw-r--r--test/csv/tc_csv_writing.rb96
-rw-r--r--test/csv/tc_data_converters.rb260
-rw-r--r--test/csv/tc_features.rb177
-rw-r--r--test/csv/tc_headers.rb261
-rw-r--r--test/csv/tc_interface.rb235
-rw-r--r--test/csv/tc_row.rb289
-rw-r--r--test/csv/tc_serialization.rb155
-rw-r--r--test/csv/tc_table.rb392
-rw-r--r--test/csv/ts_all.rb19
11 files changed, 2048 insertions, 0 deletions
diff --git a/test/csv/line_endings.gz b/test/csv/line_endings.gz
new file mode 100644
index 0000000..39e1729
--- /dev/null
+++ b/test/csv/line_endings.gz
Binary files differ
diff --git a/test/csv/tc_csv_parsing.rb b/test/csv/tc_csv_parsing.rb
new file mode 100644
index 0000000..9eb2e39
--- /dev/null
+++ b/test/csv/tc_csv_parsing.rb
@@ -0,0 +1,164 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+#
+# 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 TestCSVParsing < Test::Unit::TestCase
+ 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_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.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.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("Unclosed quoted field on line 4.", $!.message)
+ end
+ end
+end
diff --git a/test/csv/tc_csv_writing.rb b/test/csv/tc_csv_writing.rb
new file mode 100644
index 0000000..4677284
--- /dev/null
+++ b/test/csv/tc_csv_writing.rb
@@ -0,0 +1,96 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+class TestCSVWriting < Test::Unit::TestCase
+ 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/tc_data_converters.rb b/test/csv/tc_data_converters.rb
new file mode 100644
index 0000000..24c6b6b
--- /dev/null
+++ b/test/csv/tc_data_converters.rb
@@ -0,0 +1,260 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+class TestDataConverters < Test::Unit::TestCase
+ def setup
+ @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
+ # setup parser...
+ assert(@parser.respond_to?(:convert))
+ assert_nothing_raised(Exception) { @parser.convert(:integer) }
+
+ # and use
+ assert_equal(["Numbers", ":integer", 1, ":float", "3.015"], @parser.shift)
+
+ setup # reset
+
+ # setup parser...
+ 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
+ # 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 } )
+
+ setup # reset
+
+ # integers have precendance...
+ assert_nothing_raised(Exception) do
+ @parser.convert(:integer)
+ @parser.convert(:float)
+ end
+
+ # gives us proper number conversion
+ assert_equal( [String, String, Fixnum, String, Float],
+ @parser.shift.map { |field| field.class } )
+ end
+
+ def test_builtin_numeric_combo_converter
+ # setup parser...
+ assert_nothing_raised(Exception) { @parser.convert(:numeric) }
+
+ # and use
+ assert_equal( [String, String, Fixnum, String, Float],
+ @parser.shift.map { |field| field.class } )
+ end
+
+ def test_builtin_all_nested_combo_converter
+ # setup parser...
+ @data << ",#{@win_safe_time_str}" # add a DateTime field
+ @parser = CSV.new(@data) # reset parser
+ assert_nothing_raised(Exception) { @parser.convert(:all) }
+
+ # and use
+ assert_equal( [String, String, Fixnum, String, Float, DateTime],
+ @parser.shift.map { |field| field.class } )
+ 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)
+
+ setup # reset
+
+ # 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_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/tc_features.rb b/test/csv/tc_features.rb
new file mode 100644
index 0000000..6766839
--- /dev/null
+++ b/test/csv/tc_features.rb
@@ -0,0 +1,177 @@
+#!/usr/local/bin/ruby -w
+
+# 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.
+
+require "test/unit"
+require "zlib"
+
+require "csv"
+
+class TestCSVFeatures < Test::Unit::TestCase
+ 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
+ @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_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).instance_eval { @row_sep }
+ assert_equal(line_end, discovered)
+ end
+
+ assert_equal("\n", CSV.new("\n\r\n\r").instance_eval { @row_sep })
+
+ assert_equal($/, CSV.new("").instance_eval { @row_sep })
+
+ assert_equal($/, CSV.new(STDERR).instance_eval { @row_sep })
+ end
+
+ def test_lineno
+ assert_equal(5, @sample_data.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(ArgumentError) { CSV.new(String.new, :unknown => :error) }
+ 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
+
+ # 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 row.header_row? }
+ csv.rewind
+ csv.each { |row| assert 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.instance_eval { @row_sep })
+ end
+
+ def test_gzip_writer_bug_fix
+ file = File.join(File.dirname(__FILE__), "temp.gz")
+ 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( Zlib::GzipReader.open(file) { |f| f.read }.
+ include?($INPUT_RECORD_SEPARATOR),
+ "@row_sep did not default" )
+ File.unlink(file)
+ end
+
+ def test_version
+ assert_not_nil(CSV::VERSION)
+ assert_instance_of(String, CSV::VERSION)
+ assert(CSV::VERSION.frozen?)
+ assert_match(/\A\d\.\d\.\d\Z/, CSV::VERSION)
+ end
+end
diff --git a/test/csv/tc_headers.rb b/test/csv/tc_headers.rb
new file mode 100644
index 0000000..74e2f54
--- /dev/null
+++ b/test/csv/tc_headers.rb
@@ -0,0 +1,261 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+class TestCSVHeaders < Test::Unit::TestCase
+ def setup
+ @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(row.header_row?)
+ assert(!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(row.header_row?)
+ assert(!row.field_row?)
+ 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(row.header_row?)
+ assert(!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(!row.header_row?)
+ assert(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(!row.header_row?)
+ assert(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
+ csv = CSV.parse( "One,TWO Three", :headers => true,
+ :return_headers => true,
+ :header_converters => :symbol )
+ assert_equal([:one, :two_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_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/tc_interface.rb b/test/csv/tc_interface.rb
new file mode 100644
index 0000000..e8cc920
--- /dev/null
+++ b/test/csv/tc_interface.rb
@@ -0,0 +1,235 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+class TestCSVInterface < Test::Unit::TestCase
+ def setup
+ @path = File.join(File.dirname(__FILE__), "temp_test_data.csv")
+
+ File.open(@path, "w") 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
+ File.unlink(@path)
+ 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_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_equal(false, csv.closed?)
+ csv.close
+ assert(csv.closed?)
+
+ ret = CSV.open(@path) do |csv|
+ assert_instance_of(CSV, csv)
+ "Return value."
+ end
+ assert(csv.closed?)
+ assert_equal("Return value.", ret)
+ end
+
+ def test_parse
+ data = File.read(@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_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, "r+", :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
+
+ ### Test Write Interface ###
+
+ def test_generate
+ str = CSV.generate do |csv| # default empty String
+ assert_instance_of(CSV, csv)
+ assert_equal(csv, csv << [1, 2, 3])
+ assert_equal(csv, csv << [4, nil, 5])
+ end
+ assert_not_nil(str)
+ assert_instance_of(String, str)
+ assert_equal("1,2,3\n4,,5\n", str)
+
+ CSV.generate(str) do |csv| # appending to a String
+ assert_equal(csv, csv << ["last", %Q{"row"}])
+ end
+ assert_equal(%Q{1,2,3\n4,,5\nlast,"""row"""\n}, str)
+ end
+
+ def test_generate_line
+ line = CSV.generate_line(%w{1 2 3}, :col_sep => ";")
+ assert_not_nil(line)
+ assert_instance_of(String, line)
+ assert_equal("1;2;3\n", line)
+
+ # shortcut interface
+ line = %w{1 2 3}.to_csv(:col_sep => ";")
+ assert_not_nil(line)
+ assert_instance_of(String, line)
+ assert_equal("1;2;3\n", line)
+ end
+
+ def test_write_header_detection
+ File.unlink(@path)
+
+ headers = %w{a b c}
+ CSV.open(@path, "w", :headers => true) do |csv|
+ csv << headers
+ csv << %w{1 2 3}
+ assert_equal(headers, csv.instance_variable_get(:@headers))
+ end
+ end
+
+ def test_write_lineno
+ File.unlink(@path)
+
+ CSV.open(@path, "w") do |csv|
+ lines = 20
+ lines.times { csv << %w{a b c} }
+ assert_equal(lines, csv.lineno)
+ end
+ end
+
+ def test_write_hash
+ File.unlink(@path)
+
+ lines = [{:a => 1, :b => 2, :c => 3}, {:a => 4, :b => 5, :c => 6}]
+ CSV.open( @path, "w", :headers => true,
+ :converters => :all,
+ :header_converters => :symbol ) do |csv|
+ csv << lines.first.keys
+ lines.each { |line| csv << line }
+ end
+ CSV.open( @path, "w", :headers => true,
+ :converters => :all,
+ :header_converters => :symbol ) do |csv|
+ csv.each { |line| assert_equal(lines.shift, line.to_hash) }
+ end
+ end
+
+ def test_append # aliased add_row() and puts()
+ File.unlink(@path)
+
+ CSV.open(@path, "w", :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, "w", :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 { |csv| csv.instance_eval { @io } })
+ end
+end
diff --git a/test/csv/tc_row.rb b/test/csv/tc_row.rb
new file mode 100644
index 0000000..a9b7f04
--- /dev/null
+++ b/test/csv/tc_row.rb
@@ -0,0 +1,289 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+class TestCSVRow < Test::Unit::TestCase
+ def setup
+ @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(!row.header_row?)
+ assert(row.field_row?)
+ row = CSV::Row.new(%w{A B C}, [1, 2, 3], false) # explicit
+ assert(!row.header_row?)
+ assert(row.field_row?)
+
+ # header row
+ row = CSV::Row.new(%w{A B C}, [1, 2, 3], true)
+ assert(row.header_row?)
+ assert(!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))
+
+ # 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_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"))
+
+ # using a block
+ assert_equal(@row, @row.delete_if { |h, f| h == "A" and not f.nil? })
+ assert_equal([["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(@row.header?("A"))
+ assert(@row.header?("C"))
+ assert(!@row.header?("Z"))
+ assert(@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 { })
+ 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, (header, 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
+ assert_equal({"A" => nil, "B" => 2, "C" => 3}, @row.to_hash)
+ 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(!@row.empty?, "Row was empty.")
+
+ assert_equal([@row.headers.size, @row.fields.size].max, @row.size)
+ end
+end
diff --git a/test/csv/tc_serialization.rb b/test/csv/tc_serialization.rb
new file mode 100644
index 0000000..45e26bc
--- /dev/null
+++ b/test/csv/tc_serialization.rb
@@ -0,0 +1,155 @@
+#!/usr/local/bin/ruby -w
+
+# tc_serialization.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 "csv"
+
+# An example of how to provide custom CSV serialization.
+class Hash
+ def self.csv_load( meta, headers, fields )
+ self[*headers.zip(fields).flatten.map { |e| eval(e) }]
+ end
+
+ def csv_headers
+ keys.map { |key| key.inspect }
+ end
+
+ def csv_dump( headers )
+ headers.map { |header| fetch(eval(header)).inspect }
+ end
+end
+
+class TestSerialization < Test::Unit::TestCase
+
+ ### Classes Used to Test Serialization ###
+
+ class ReadOnlyName
+ def initialize( first, last )
+ @first, @last = first, last
+ end
+
+ attr_reader :first, :last
+
+ def ==( other )
+ %w{first last}.all? { |att| send(att) == other.send(att) }
+ end
+ end
+
+ Name = Struct.new(:first, :last)
+
+ class FullName < Name
+ def initialize( first, last, suffix = nil )
+ super(first, last)
+
+ @suffix = suffix
+ end
+
+ attr_accessor :suffix
+
+ def ==( other )
+ %w{first last suffix}.all? { |att| send(att) == other.send(att) }
+ end
+ end
+
+ ### Tests ###
+
+ def test_class_dump
+ @names = [ %w{James Gray},
+ %w{Dana Gray},
+ %w{Greg Brown} ].map do |first, last|
+ ReadOnlyName.new(first, last)
+ end
+
+ assert_nothing_raised(Exception) do
+ @data = CSV.dump(@names)
+ end
+ assert_equal(<<-END_CLASS_DUMP.gsub(/^\s*/, ""), @data)
+ class,TestSerialization::ReadOnlyName
+ @first,@last
+ James,Gray
+ Dana,Gray
+ Greg,Brown
+ END_CLASS_DUMP
+ end
+
+ def test_struct_dump
+ @names = [ %w{James Gray},
+ %w{Dana Gray},
+ %w{Greg Brown} ].map do |first, last|
+ Name.new(first, last)
+ end
+
+ assert_nothing_raised(Exception) do
+ @data = CSV.dump(@names)
+ end
+ assert_equal(<<-END_STRUCT_DUMP.gsub(/^\s*/, ""), @data)
+ class,TestSerialization::Name
+ first=,last=
+ James,Gray
+ Dana,Gray
+ Greg,Brown
+ END_STRUCT_DUMP
+ end
+
+ def test_inherited_struct_dump
+ @names = [ %w{James Gray II},
+ %w{Dana Gray},
+ %w{Greg Brown} ].map do |first, last, suffix|
+ FullName.new(first, last, suffix)
+ end
+
+ assert_nothing_raised(Exception) do
+ @data = CSV.dump(@names)
+ end
+ assert_equal(<<-END_STRUCT_DUMP.gsub(/^\s*/, ""), @data)
+ class,TestSerialization::FullName
+ @suffix,first=,last=
+ II,James,Gray
+ ,Dana,Gray
+ ,Greg,Brown
+ END_STRUCT_DUMP
+ end
+
+ def test_load
+ %w{ test_class_dump
+ test_struct_dump
+ test_inherited_struct_dump }.each do |test|
+ send(test)
+ CSV.load(@data).each do |loaded|
+ assert_instance_of(@names.first.class, loaded)
+ assert_equal(@names.shift, loaded)
+ end
+ end
+ end
+
+ def test_io
+ test_class_dump
+
+ data_file = File.join(File.dirname(__FILE__), "temp_test_data.csv")
+ CSV.dump(@names, File.open(data_file, "w"))
+
+ assert(File.exist?(data_file))
+ assert_equal(<<-END_IO_DUMP.gsub(/^\s*/, ""), File.read(data_file))
+ class,TestSerialization::ReadOnlyName
+ @first,@last
+ James,Gray
+ Dana,Gray
+ Greg,Brown
+ END_IO_DUMP
+
+ assert_equal(@names, CSV.load(File.open(data_file)))
+
+ File.unlink(data_file)
+ end
+
+ def test_custom_dump_and_load
+ obj = {1 => "simple", :test => Hash}
+ assert_equal(obj, CSV.load(CSV.dump([obj])).first)
+ end
+end
diff --git a/test/csv/tc_table.rb b/test/csv/tc_table.rb
new file mode 100644
index 0000000..028274d
--- /dev/null
+++ b/test/csv/tc_table.rb
@@ -0,0 +1,392 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "test/unit"
+
+require "csv"
+
+class TestCSVTable < Test::Unit::TestCase
+ def setup
+ @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_index
+ ##################
+ ### Mixed Mode ###
+ ##################
+ # by row
+ @rows.each_index { |i| assert_equal(@rows[i], @table[i]) }
+ assert_equal(nil, @table[100]) # empty row
+
+ # 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_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 { })
+
+ ###################
+ ### Column Mode ###
+ ###################
+ @table.by_col!
+
+ headers = @table.headers
+ @table.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") )
+
+ # 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 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
+
+ ###################
+ ### Column Mode ###
+ ###################
+ setup
+ @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
+
+ ################
+ ### Row Mode ###
+ ################
+ setup
+ @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_if
+ ######################
+ ### 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
+
+ ###################
+ ### Column Mode ###
+ ###################
+ setup
+ @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_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(!@table.empty?, "Table was empty.")
+
+ assert_equal(@rows.size, @table.size)
+ end
+end
diff --git a/test/csv/ts_all.rb b/test/csv/ts_all.rb
new file mode 100644
index 0000000..c930523
--- /dev/null
+++ b/test/csv/ts_all.rb
@@ -0,0 +1,19 @@
+#!/usr/local/bin/ruby -w
+
+# 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 "tc_csv_parsing"
+require "tc_features"
+require "tc_interface"
+require "tc_csv_writing"
+require "tc_data_converters"
+require "tc_row"
+require "tc_table"
+require "tc_headers"
+require "tc_serialization"