package tap.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static tap.config.TAPConfiguration.KEY_DEFAULT_OUTPUT_LIMIT;
import static tap.config.TAPConfiguration.KEY_FILE_MANAGER;
import static tap.config.TAPConfiguration.KEY_MAX_OUTPUT_LIMIT;
import static tap.config.TAPConfiguration.KEY_TAP_FACTORY;
import static tap.config.TAPConfiguration.fetchClass;
import static tap.config.TAPConfiguration.hasConstructor;
import static tap.config.TAPConfiguration.isClassName;
import static tap.config.TAPConfiguration.newInstance;
import static tap.config.TAPConfiguration.parseLimit;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import tap.ServiceConnection;
import tap.ServiceConnection.LimitUnit;
import tap.TAPException;
import tap.TAPFactory;
import tap.metadata.TAPMetadata;
import tap.metadata.TAPSchema;
import adql.query.ColumnReference;
public class TestTAPConfiguration {
@Before
public void setUp() throws Exception{}
/**
* TEST isClassName(String):
* - null, "", "{}", "an incorrect syntax" => FALSE must be returned
* - "{ }", "{ }", "{class.path}", "{ class.path }" => TRUE must be returned
*
* @see ConfigurableServiceConnection#isClassName(String)
*/
@Test
public void testIsClassPath(){
// NULL and EMPTY:
assertFalse(isClassName(null));
assertFalse(isClassName(""));
// EMPTY CLASSPATH:
assertFalse(isClassName("{}"));
// INCORRECT CLASSPATH:
assertFalse(isClassName("incorrect class name ; missing {}"));
// VALID CLASSPATH:
assertTrue(isClassName("{class.path}"));
// CLASSPATH VALID ONLY IN THE SYNTAX:
assertTrue(isClassName("{ }"));
assertTrue(isClassName("{ }"));
// NOT TRIM CLASSPATH:
assertTrue(isClassName("{ class.name }"));
}
/**
* TEST getClass(String,String,Class):
* - null, "", "{}", "an incorrect syntax", "{ }", "{ }" => NULL must be returned
* - "{java.lang.String}", "{ java.lang.String }" => a valid DefaultServiceConnection must be returned
* - "{mypackage.foo}", "{java.util.ArrayList}" (while a String is expected) => a TAPException must be thrown
*/
@Test
public void testGetClassStringStringClass(){
// NULL and EMPTY:
try{
assertNull(fetchClass(null, KEY_FILE_MANAGER, String.class));
}catch(TAPException e){
fail("If a NULL value is provided as class name: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
}
try{
assertNull(fetchClass("", KEY_FILE_MANAGER, String.class));
}catch(TAPException e){
fail("If an EMPTY value is provided as class name: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
}
// EMPTY CLASS NAME:
try{
assertNull(fetchClass("{}", KEY_FILE_MANAGER, String.class));
}catch(TAPException e){
fail("If an EMPTY class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
}
// INCORRECT SYNTAX:
try{
assertNull(fetchClass("incorrect class name ; missing {}", KEY_FILE_MANAGER, String.class));
}catch(TAPException e){
fail("If an incorrect class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
}
// VALID CLASS NAME:
try{
Class<? extends String> classObject = fetchClass("{java.lang.String}", KEY_FILE_MANAGER, String.class);
assertNotNull(classObject);
assertEquals(classObject.getName(), "java.lang.String");
}catch(TAPException e){
fail("If a VALID class name is provided: getClass(...) MUST return a Class object of the wanted type!\nCaught exception: " + getPertinentMessage(e));
}
// INCORRECT CLASS NAME:
try{
fetchClass("{mypackage.foo}", KEY_FILE_MANAGER, String.class);
fail("This MUST have failed because an incorrect class name is provided!");
}catch(TAPException e){
assertEquals(e.getClass(), TAPException.class);
assertEquals(e.getMessage(), "The class specified by the property \"" + KEY_FILE_MANAGER + "\" ({mypackage.foo}) can not be found.");
}
// INCOMPATIBLE TYPES:
try{
@SuppressWarnings("unused")
Class<? extends String> classObject = fetchClass("{java.util.ArrayList}", KEY_FILE_MANAGER, String.class);
fail("This MUST have failed because a class of a different type has been asked!");
}catch(TAPException e){
assertEquals(e.getClass(), TAPException.class);
assertEquals(e.getMessage(), "The class specified by the property \"" + KEY_FILE_MANAGER + "\" ({java.util.ArrayList}) is not implementing " + String.class.getName() + ".");
}
// CLASS NAME VALID ONLY IN THE SYNTAX:
try{
assertNull(fetchClass("{ }", KEY_FILE_MANAGER, String.class));
}catch(TAPException e){
fail("If an EMPTY class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
}
try{
assertNull(fetchClass("{ }", KEY_FILE_MANAGER, String.class));
}catch(TAPException e){
fail("If an EMPTY class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
}
// NOT TRIM CLASS NAME:
try{
Class<?> classObject = fetchClass("{ java.lang.String }", KEY_FILE_MANAGER, String.class);
assertNotNull(classObject);
assertEquals(classObject.getName(), "java.lang.String");
}catch(TAPException e){
fail("If a VALID class name is provided: getClass(...) MUST return a Class object of the wanted type!\nCaught exception: " + getPertinentMessage(e));
}
}
/**
* TEST hasConstructor(String,String,Class,Class[]):
* (tests already performed by {@link #testGetClassStringStringClass()})
* - null, "", "{}", "an incorrect syntax", "{ }", "{ }" => must fail with a TAPException
* - "{java.lang.String}", "{ java.lang.String }" => a valid DefaultServiceConnection must be returned
* - "{mypackage.foo}", "{java.util.ArrayList}" (while a String is expected) => a TAPException must be thrown
* (new tests)
* - if the specified constructor exists return <code>true</code>, else <code>false</code> must be returned.
*/
@Test
public void testHasConstructor(){
/* hasConstructor(...) must throw an exception if the specification of the class (1st and 3rd parameters)
* is wrong. But that is performed by fetchClass(...) which is called at the beginning of the function
* and is not surrounded by a try-catch. So all these tests are already done by testGetClassStringStringClass(). */
// With a missing list of parameters:
try{
assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, null));
}catch(TAPException te){
te.printStackTrace();
fail("\"No list of parameters\" MUST be interpreted as the specification of a constructor with no parameter! This test has failed.");
}
// With an empty list of parameters
try{
assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, new Class[0]));
}catch(TAPException te){
te.printStackTrace();
fail("\"An empty list of parameters\" MUST be interpreted as the specification of a constructor with no parameter! This test has failed.");
}
// With a wrong list of parameters - 1
try{
assertFalse(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{}));
}catch(TAPException te){
te.printStackTrace();
fail("ConfigurableTAPFactory does not have an empty constructor ; this test should have failed!");
}
// With a wrong list of parameters - 2
try{
assertFalse(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{String.class,String.class}));
}catch(TAPException te){
te.printStackTrace();
fail("ConfigurableTAPFactory does not have a constructor with 2 Strings as parameter ; this test should have failed!");
}
// With a good list of parameters - 1
try{
assertTrue(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{ServiceConnection.class,Properties.class}));
}catch(TAPException te){
te.printStackTrace();
fail("ConfigurableTAPFactory has a constructor with a ServiceConnection and a Properties in parameters ; this test should have failed!");
}
// With a good list of parameters - 2
try{
assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, new Class[]{String.class}));
}catch(TAPException te){
te.printStackTrace();
fail("String has a constructor with a String as parameter ; this test should have failed!");
}
}
@Test
public void testNewInstance(){
// VALID CONSTRUCTOR with no parameters:
try{
TAPMetadata metadata = newInstance("{tap.metadata.TAPMetadata}", "metadata", TAPMetadata.class);
assertNotNull(metadata);
assertEquals("tap.metadata.TAPMetadata", metadata.getClass().getName());
}catch(Exception ex){
ex.printStackTrace();
fail("This test should have succeeded: the parameters of newInstance(...) are all valid.");
}
// VALID CONSTRUCTOR with some parameters:
try{
final String schemaName = "MySuperSchema", description = "And its less super description.", utype = "UTYPE";
TAPSchema schema = newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{String.class,String.class,String.class}, new String[]{schemaName,description,utype});
assertNotNull(schema);
assertEquals("tap.metadata.TAPSchema", schema.getClass().getName());
assertEquals(schemaName, schema.getADQLName());
assertEquals(description, schema.getDescription());
assertEquals(utype, schema.getUtype());
}catch(Exception ex){
ex.printStackTrace();
fail("This test should have succeeded: the constructor TAPSchema(String,String,String) exists.");
}
// VALID CONSTRUCTOR with some parameters whose the type is an extension (not the exact type):
OutputStream output = null;
File tmp = new File("tmp.empty");
try{
output = newInstance("{java.io.BufferedOutputStream}", "stream", OutputStream.class, new Class<?>[]{OutputStream.class}, new OutputStream[]{new FileOutputStream(tmp)});
assertNotNull(output);
assertEquals(BufferedOutputStream.class, output.getClass());
}catch(Exception ex){
ex.printStackTrace();
fail("This test should have succeeded: the constructor TAPSchema(String,String,String) exists.");
}finally{
try{
tmp.delete();
if (output != null)
output.close();
}catch(IOException ioe){}
}
// NOT A CLASS NAME:
try{
newInstance("tap.metadata.TAPMetadata", "metadata", TAPMetadata.class);
fail("This MUST have failed because the property value is not a class name!");
}catch(Exception ex){
assertEquals(TAPException.class, ex.getClass());
assertEquals("Class name expected for the property \"metadata\" instead of: \"tap.metadata.TAPMetadata\"! The specified class must extend/implement tap.metadata.TAPMetadata.", ex.getMessage());
}
// NO MATCHING CONSTRUCTOR:
try{
newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{Integer.class}, new Object[]{new Integer(123)});
fail("This MUST have failed because the specified class does not have any expected constructor!");
}catch(Exception ex){
assertEquals(TAPException.class, ex.getClass());
assertEquals("Missing constructor tap.metadata.TAPSchema(java.lang.Integer)! See the value \"{tap.metadata.TAPSchema}\" of the property \"schema\".", ex.getMessage());
}
// VALID CONSTRUCTOR with primitive type:
try{
ColumnReference colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{int.class}, new Object[]{123});
assertNotNull(colRef);
assertEquals(ColumnReference.class, colRef.getClass());
assertEquals(123, colRef.getColumnIndex());
colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{int.class}, new Object[]{new Integer(123)});
assertNotNull(colRef);
assertEquals(ColumnReference.class, colRef.getClass());
assertEquals(123, colRef.getColumnIndex());
}catch(Exception ex){
ex.printStackTrace();
fail("This test should have succeeded: the constructor ColumnReference(int) exists.");
}
// WRONG CONSTRUCTOR with primitive type:
try{
newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{Integer.class}, new Object[]{new Integer(123)});
fail("This MUST have failed because the constructor of the specified class expects an int, not an java.lang.Integer!");
}catch(Exception ex){
assertEquals(TAPException.class, ex.getClass());
assertEquals("Missing constructor adql.query.ColumnReference(java.lang.Integer)! See the value \"{adql.query.ColumnReference}\" of the property \"colRef\".", ex.getMessage());
}
// THE CONSTRUCTOR THROWS AN EXCEPTION:
try{
newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{String.class}, new Object[]{null});
fail("This MUST have failed because the constructor of the specified class throws an exception!");
}catch(Exception ex){
assertEquals(TAPException.class, ex.getClass());
assertNotNull(ex.getCause());
assertEquals(NullPointerException.class, ex.getCause().getClass());
assertEquals("Missing schema name!", ex.getCause().getMessage());
}
// THE CONSTRUCTOR THROWS A TAPEXCEPTION:
try{
newInstance("{tap.config.TestTAPConfiguration$ClassAlwaysThrowTAPError}", "tapError", ClassAlwaysThrowTAPError.class);
fail("This MUST have failed because the constructor of the specified class throws a TAPException!");
}catch(Exception ex){
assertEquals(TAPException.class, ex.getClass());
assertEquals("This error is always thrown by ClassAlwaysThrowTAPError ^^", ex.getMessage());
}
}
/**
* TEST parseLimit(String,String):
* - nothing, -123, 0 => {-1,LimitUnit.rows}
* - 20, 20r, 20R => {20,LimitUnit.rows}
* - 100B, 100 B => {100,LimitUnit.bytes}
* - 100kB, 100 k B => {100000,LimitUnit.bytes}
* - 100MB, 1 0 0MB => {100000000,LimitUnit.bytes}
* - 100GB, 1 0 0 G B => {100000000000,LimitUnit.bytes}
* - r => {-1,LimitUnit.rows}
* - kB => {-1,LimitUnit.bytes}
* - foo, 100b, 100TB, 1foo => an exception must occur
*/
@Test
public void testParseLimitStringString(){
final String propertyName = KEY_DEFAULT_OUTPUT_LIMIT + " or " + KEY_MAX_OUTPUT_LIMIT;
// Test empty or negative or null values => OK!
try{
String[] testValues = new String[]{null,""," ","-123"};
Object[] limit;
for(String v : testValues){
limit = parseLimit(v, propertyName, false);
assertEquals(limit[0], -1);
assertEquals(limit[1], LimitUnit.rows);
}
// 0 test:
limit = parseLimit("0", propertyName, false);
assertEquals(limit[0], 0);
assertEquals(limit[1], LimitUnit.rows);
}catch(TAPException te){
fail("All these empty limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test all accepted rows values:
try{
String[] testValues = new String[]{"20","20r","20 R"};
Object[] limit;
for(String v : testValues){
limit = parseLimit(v, propertyName, false);
assertEquals(limit[0], 20);
assertEquals(limit[1], LimitUnit.rows);
}
}catch(TAPException te){
fail("All these rows limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test all accepted bytes values:
try{
String[] testValues = new String[]{"100B","100 B"};
Object[] limit;
for(String v : testValues){
limit = parseLimit(v, propertyName, true);
assertEquals(limit[0], 100);
assertEquals(limit[1], LimitUnit.bytes);
}
}catch(TAPException te){
fail("All these bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test all accepted kilo-bytes values:
try{
String[] testValues = new String[]{"100kB","100 k B"};
Object[] limit;
for(String v : testValues){
limit = parseLimit(v, propertyName, true);
assertEquals(limit[0], 100);
assertEquals(limit[1], LimitUnit.kilobytes);
}
}catch(TAPException te){
fail("All these kilo-bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test all accepted mega-bytes values:
try{
String[] testValues = new String[]{"100MB","1 0 0MB"};
Object[] limit;
for(String v : testValues){
limit = parseLimit(v, propertyName, true);
assertEquals(limit[0], 100);
assertEquals(limit[1], LimitUnit.megabytes);
}
}catch(TAPException te){
fail("All these mega-bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test all accepted giga-bytes values:
try{
String[] testValues = new String[]{"100GB","1 0 0 G B"};
Object[] limit;
for(String v : testValues){
limit = parseLimit(v, propertyName, true);
assertEquals(limit[0], 100);
assertEquals(limit[1], LimitUnit.gigabytes);
}
}catch(TAPException te){
fail("All these giga-bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test with only the ROWS unit provided:
try{
Object[] limit = parseLimit("r", propertyName, false);
assertEquals(limit[0], -1);
assertEquals(limit[1], LimitUnit.rows);
}catch(TAPException te){
fail("Providing only the ROWS unit is valid, so this test should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test with only the BYTES unit provided:
try{
Object[] limit = parseLimit("kB", propertyName, true);
assertEquals(limit[0], -1);
assertEquals(limit[1], LimitUnit.kilobytes);
}catch(TAPException te){
fail("Providing only the BYTES unit is valid, so this test should have succeeded!\nCaught exception: " + getPertinentMessage(te));
}
// Test with incorrect limit formats:
String[] values = new String[]{"","100","100","1"};
String[] unitPart = new String[]{"foo","b","TB","foo"};
for(int i = 0; i < values.length; i++){
try{
parseLimit(values[i] + unitPart[i], propertyName, true);
fail("This test should have failed because an incorrect limit is provided: \"" + values[i] + unitPart[i] + "\"!");
}catch(TAPException te){
assertEquals(te.getClass(), TAPException.class);
assertEquals(te.getMessage(), "Unknown limit unit (" + unitPart[i] + ") for the property " + propertyName + ": \"" + values[i] + unitPart[i] + "\"!");
}
}
// Test with an incorrect numeric limit value:
try{
parseLimit("abc100b", propertyName, true);
fail("This test should have failed because an incorrect limit is provided: \"abc100b\"!");
}catch(TAPException te){
assertEquals(te.getClass(), TAPException.class);
assertEquals(te.getMessage(), "Integer expected for the property " + propertyName + " for the substring \"abc100\" of the whole value: \"abc100b\"!");
}
// Test with a BYTES unit whereas the BYTES unit is forbidden:
try{
parseLimit("100B", propertyName, false);
fail("This test should have failed because an incorrect limit is provided: \"100B\"!");
}catch(TAPException te){
assertEquals(te.getClass(), TAPException.class);
assertEquals(te.getMessage(), "BYTES unit is not allowed for the property " + propertyName + " (100B)!");
}
}
public static final String getPertinentMessage(final Exception ex){
return (ex.getCause() == null || ex.getMessage().equals(ex.getCause().getMessage())) ? ex.getMessage() : ex.getCause().getMessage();
}
private static class ClassAlwaysThrowTAPError {
@SuppressWarnings("unused")
public ClassAlwaysThrowTAPError() throws TAPException{
throw new TAPException("This error is always thrown by ClassAlwaysThrowTAPError ^^");
}
}
}