/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Initial developer(s): Robert Hodges
* Contributor(s):
*/
package com.continuent.tungsten.common.csv;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import org.junit.Assert;
import org.junit.Test;
/**
* Implements a basic unit test of CSV input and output.
*
* @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
* @version 1.0
*/
public class CsvTest
{
/**
* Verify that we can write a file to output and read it back in.
*/
@Test
public void testOutputInput() throws Exception
{
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
// Write values.
csvWriter.addColumnName("a");
csvWriter.addColumnName("bb");
csvWriter.addColumnName("ccc");
csvWriter.put("a", "r1a");
csvWriter.put("bb", "r1b");
csvWriter.put("ccc", "r1c");
csvWriter.write();
csvWriter.put("a", "r2a");
csvWriter.put("bb", "r2b");
csvWriter.put("ccc", "r2c");
csvWriter.flush();
String csv = sw.toString();
// Read values back in again and validate.
StringReader sr = new StringReader(csv);
CsvReader csvReader = new CsvReader(sr);
// Validate names.
Assert.assertTrue("First read succeeded", csvReader.next());
Assert.assertEquals("size of names", 3, csvReader.getNames().size());
Assert.assertEquals("a", csvReader.getNames().get(0));
Assert.assertEquals("bb", csvReader.getNames().get(1));
Assert.assertEquals("ccc", csvReader.getNames().get(2));
// Check row 1 values using index and names.
Assert.assertEquals("r1 val1", "r1a", csvReader.getString(1));
Assert.assertEquals("r1 val1", "r1a", csvReader.getString("a"));
Assert.assertEquals("r1 val2", "r1b", csvReader.getString(2));
Assert.assertEquals("r1 val2", "r1b", csvReader.getString("bb"));
Assert.assertEquals("r1 val1", "r1c", csvReader.getString(3));
Assert.assertEquals("r1 val1", "r1c", csvReader.getString("ccc"));
// Check row 2 values.
Assert.assertTrue("Second read succeeded", csvReader.next());
Assert.assertEquals("r1 val1", "r2a", csvReader.getString(1));
Assert.assertEquals("r1 val1", "r2a", csvReader.getString("a"));
Assert.assertEquals("r1 val2", "r2b", csvReader.getString(2));
Assert.assertEquals("r1 val2", "r2b", csvReader.getString("bb"));
Assert.assertEquals("r1 val1", "r2c", csvReader.getString(3));
Assert.assertEquals("r1 val1", "r2c", csvReader.getString("ccc"));
// Ensure we are done.
Assert.assertFalse("Third read failed", csvReader.next());
}
/**
* Verify that we can write a file to output and read it back in using
* CsvReader and CsvWriter instances generated from a CsvSpecification.
*/
@Test
public void testOutputInputFromSpecification() throws Exception
{
// Generate a default specification but set the field separator just for
// fun.
CsvSpecification csvSpec = new CsvSpecification();
csvSpec.setFieldSeparator(":");
csvSpec.setUseHeaders(true);
StringWriter sw = new StringWriter();
CsvWriter csvWriter = csvSpec.createCsvWriter(sw);
// Write values.
csvWriter.addColumnName("a");
csvWriter.addColumnName("bb");
csvWriter.addColumnName("ccc");
csvWriter.put("a", "r1a");
csvWriter.put("bb", "r1b");
csvWriter.put("ccc", "r1c");
csvWriter.flush();
String csv = sw.toString();
// Read values back in again and validate.
StringReader sr = new StringReader(csv);
CsvReader csvReader = csvSpec.createCsvReader(sr);
// Validate names.
Assert.assertTrue("First read succeeded", csvReader.next());
Assert.assertEquals("size of names", 3, csvReader.getNames().size());
Assert.assertEquals("a", csvReader.getNames().get(0));
Assert.assertEquals("bb", csvReader.getNames().get(1));
Assert.assertEquals("ccc", csvReader.getNames().get(2));
// Check row 1 values using index and names.
Assert.assertEquals("r1 val1", "r1a", csvReader.getString(1));
Assert.assertEquals("r1 val1", "r1a", csvReader.getString("a"));
Assert.assertEquals("r1 val2", "r1b", csvReader.getString(2));
Assert.assertEquals("r1 val2", "r1b", csvReader.getString("bb"));
Assert.assertEquals("r1 val1", "r1c", csvReader.getString(3));
Assert.assertEquals("r1 val1", "r1c", csvReader.getString("ccc"));
// Ensure we are done.
Assert.assertFalse("Read failed", csvReader.next());
}
/**
* Verify that a CsvException results if the client tries to add a new
* column name after writing the first row.
*/
@Test
public void testSetHeaderAfterWrite() throws Exception
{
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
csvWriter.addColumnName("a");
csvWriter.put("a", "r1a");
csvWriter.write();
try
{
csvWriter.addColumnName("bb");
throw new Exception("Can add column after writing!");
}
catch (CsvException e)
{
// Expected.
}
}
/**
* Verify that a CsvException results if the client issues a write while we
* have an incomplete row.
*/
@Test
public void testWriteIncompleteRow() throws Exception
{
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
csvWriter.addColumnName("a");
csvWriter.addColumnName("bb");
csvWriter.put("a", "r1a");
try
{
csvWriter.write();
throw new Exception("Can write partial row!");
}
catch (CsvException e)
{
// Expected.
}
try
{
csvWriter.write();
throw new Exception("Can flush partial row!");
}
catch (CsvException e)
{
// Expected.
}
// Verify that we can write as well as flush once we add the extra row.
csvWriter.put("bb", "r1b");
csvWriter.write();
csvWriter.flush();
}
/**
* Verify that a CsvException results if the client issues a write past the
* end of the row.
*/
@Test
public void testWriteExtraColumn() throws Exception
{
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
csvWriter.addColumnName("a");
csvWriter.put(1, "good value");
try
{
csvWriter.put(2, "bad value");
throw new Exception("Can write extra column!");
}
catch (CsvException e)
{
// Expected.
}
}
/**
* Verify that if a client attempts to read a non-existent column an
* IOException results.
*/
@Test
public void testReadNonExistent() throws Exception
{
// Load a CSV file.
String[] colNames = {"a", "b", "c"};
String csv = this.createCsvFile(colNames, 10);
StringReader sr = new StringReader(csv);
CsvReader csvReader = new CsvReader(sr);
// Validate reading existing fields.
Assert.assertTrue("First read succeeded", csvReader.next());
Assert.assertEquals("size of names", 3, csvReader.getNames().size());
Assert.assertEquals("r1_c1", csvReader.getString("a"));
Assert.assertEquals("r1_c2", csvReader.getString("b"));
Assert.assertEquals("r1_c3", csvReader.getString("c"));
// Read non-existing column name.
try
{
csvReader.getString("d");
throw new Exception("Able to read invalid column name");
}
catch (CsvException e)
{
// Expected.
}
// Read non-existing column index.
try
{
csvReader.getString(4);
throw new Exception("Able to read invalid column index");
}
catch (CsvException e)
{
// Expected.
}
}
/**
* Verify that enabling a row ID allows us to output and read back
* automatically generated row values.
*/
@Test
public void testRowIds() throws Exception
{
// Create a file with row IDs enabled.
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
csvWriter.addColumnName("a");
csvWriter.addRowIdName("my_row_id");
csvWriter.addColumnName("c");
csvWriter.setWriteHeaders(true);
// Write values and flush to a string we can read again.
for (int i = 1; i <= 10; i++)
{
csvWriter.put("a", new Integer(i).toString());
csvWriter.put("c", new Integer(i * 2).toString());
csvWriter.write();
}
csvWriter.flush();
String csv = sw.toString();
// Read CSV data back in and check that row ID is correct.
StringReader sr = new StringReader(csv);
CsvReader csvReader = new CsvReader(sr);
csvReader.setUseHeaders(true);
for (int i = 1; i <= 10; i++)
{
// Row ID is i + 1 because we have a header row.
csvReader.next();
Assert.assertEquals(new Integer(i).toString(),
csvReader.getString("a"));
Assert.assertEquals(new Integer(i + 1).toString(),
csvReader.getString("my_row_id"));
Assert.assertEquals(new Integer(i * 2).toString(),
csvReader.getString("c"));
}
}
/**
* Verify that quoting, character escaping and suppression within strings
* works properly.
*/
@Test
public void testEscapingAndSuppression() throws Exception
{
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
csvWriter.setEscapeChar('X');
csvWriter.setEscapedChars("ABC");
csvWriter.setSuppressedChars("abc");
csvWriter.setQuoteChar('Q');
csvWriter.setQuoted(true);
// Write values.
csvWriter.addColumnName("data");
csvWriter.put("data", "abc").write();
csvWriter.put("data", "ABC").write();
csvWriter.put("data", "aAbBcC").write();
csvWriter.put("data", "Q").write();
csvWriter.flush();
String csv = sw.toString();
// Read values back in again.
StringReader sr = new StringReader(csv);
CsvReader csvReader = new CsvReader(sr);
// Characters in first row should be empty string surrounded by quotes.
Assert.assertTrue("First read", csvReader.next());
Assert.assertEquals("suppressed characters", "QQ",
csvReader.getString(1));
// Characters in second row should be escaped with quotes.
Assert.assertTrue("Second read", csvReader.next());
Assert.assertEquals("escaped characters", "QXAXBXCQ",
csvReader.getString(1));
// Characters in third row should be escaped with quotes with suppressed
// characters omitted.
Assert.assertTrue("Second read", csvReader.next());
Assert.assertEquals("escaped characters", "QXAXBXCQ",
csvReader.getString(1));
// Characters in fourth row be an escaped quote character.
Assert.assertTrue("Second read", csvReader.next());
Assert.assertEquals("escaped characters", "QXQQ",
csvReader.getString(1));
}
/**
* Verify that partially written rows are accepted if nullAutofill is true
* or rejected if not.
*/
@Test
public void testNullAutofill() throws Exception
{
// Write with autofill disabled. This generates an exception.
StringWriter sw = new StringWriter();
CsvWriter csvWriter = new CsvWriter(sw);
csvWriter.setNullPolicy(NullPolicy.emptyString);
// Write values.
csvWriter.addColumnName("d1");
csvWriter.addColumnName("d2");
try
{
csvWriter.put("d1", "something").write();
throw new Exception(
"Able to write partial row when nullAutofill=false");
}
catch (CsvException e)
{
// Expected.
}
// Write with autofill enabled. This should add "NULL" for null fields.
sw = new StringWriter();
csvWriter = new CsvWriter(sw);
csvWriter.setNullPolicy(NullPolicy.nullValue);
csvWriter.setNullValue("NULL");
csvWriter.setNullAutofill(true);
// Add two rows.
csvWriter.addColumnName("d1");
csvWriter.addColumnName("d2");
csvWriter.put("d1", "something").write();
csvWriter.put("d1", null).write();
csvWriter.put("d2", "else").write();
csvWriter.flush();
String csv = sw.toString();
// Read values back in again.
StringReader sr = new StringReader(csv);
CsvReader csvReader = new CsvReader(sr);
// First row should have d1 + null value.
Assert.assertTrue("First read", csvReader.next());
Assert.assertEquals("d1 written", "something", csvReader.getString(1));
Assert.assertEquals("d2 NULL", "NULL", csvReader.getString(2));
// Second row should have only null values.
Assert.assertTrue("Second read", csvReader.next());
Assert.assertEquals("d1 NULL", "NULL", csvReader.getString(1));
Assert.assertEquals("d2 NULL", "NULL", csvReader.getString(2));
// Third row should have null + d2 value.
Assert.assertTrue("Second read", csvReader.next());
Assert.assertEquals("d1 NULL", "NULL", csvReader.getString(1));
Assert.assertEquals("d2 written", "else", csvReader.getString(2));
}
// Create a CSV file with no extra separators.
private String createCsvFile(String[] colNames, int rows)
throws IOException
{
// Set up output.
StringWriter sw = new StringWriter();
BufferedWriter bw = new BufferedWriter(sw);
// Write headers.
for (int c = 1; c <= colNames.length; c++)
{
if (c > 1)
bw.append(",");
bw.append(colNames[c - 1]);
}
// Write each row.
for (int r = 1; r <= rows; r++)
{
bw.newLine();
for (int c = 1; c <= colNames.length; c++)
{
if (c > 1)
bw.append(",");
String value = "r" + r + "_c" + c;
bw.append(value);
}
}
// Flush output and return value.
bw.flush();
return sw.toString();
}
}