blob: b5b9a746582a054759079e552e6c23c45501afad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.drill;
import static org.apache.drill.test.TestBuilder.listOf;
import static org.apache.drill.test.TestBuilder.mapOf;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.Types;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.planner.physical.PlannerSettings;
import com.google.common.collect.Lists;
import org.apache.drill.test.BaseTestQuery;
import org.apache.drill.test.TestBuilder;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
// TODO - update framework to remove any dependency on the Drill engine
// for reading baseline result sets currently using it with the assumption
// that the csv and json readers are well tested, and handling diverse
// types in the test framework would require doing some redundant work
// to enable casting outside of Drill or some better tooling to generate
// parquet files that have all of the parquet types
public class TestFrameworkTest extends BaseTestQuery {
private static String CSV_COLS = " cast(columns[0] as bigint) employee_id, columns[1] as first_name, columns[2] as last_name ";
@Test(expected = AssertionError.class)
public void testSchemaTestBuilderSetInvalidBaselineValues() throws Exception {
final String query = "SELECT ltrim('drill') as col FROM (VALUES(1)) limit 0";
List<Pair<SchemaPath, TypeProtos.MajorType>> expectedSchema = Lists.newArrayList();
TypeProtos.MajorType majorType = TypeProtos.MajorType.newBuilder()
.setMinorType(TypeProtos.MinorType.VARCHAR)
.setMode(TypeProtos.DataMode.REQUIRED)
.build();
expectedSchema.add(Pair.of(SchemaPath.getSimplePath("col"), majorType));
testBuilder()
.sqlQuery(query)
.schemaBaseLine(expectedSchema)
.baselineValues(new Object[0])
.build()
.run();
}
@Test(expected = AssertionError.class)
public void testSchemaTestBuilderSetInvalidBaselineRecords() throws Exception {
final String query = "SELECT ltrim('drill') as col FROM (VALUES(1)) limit 0";
List<Pair<SchemaPath, TypeProtos.MajorType>> expectedSchema = Lists.newArrayList();
TypeProtos.MajorType majorType = TypeProtos.MajorType.newBuilder()
.setMinorType(TypeProtos.MinorType.VARCHAR)
.setMode(TypeProtos.DataMode.REQUIRED)
.build();
expectedSchema.add(Pair.of(SchemaPath.getSimplePath("col"), majorType));
testBuilder()
.sqlQuery(query)
.schemaBaseLine(expectedSchema)
.baselineRecords(Collections.<Map<String, Object>>emptyList())
.build()
.run();
}
@Test(expected = AssertionError.class)
public void testSchemaTestBuilderSetInvalidBaselineColumns() throws Exception {
final String query = "SELECT ltrim('drill') as col FROM (VALUES(1)) limit 0";
List<Pair<SchemaPath, TypeProtos.MajorType>> expectedSchema = Lists.newArrayList();
TypeProtos.MajorType majorType = TypeProtos.MajorType.newBuilder()
.setMinorType(TypeProtos.MinorType.VARCHAR)
.setMode(TypeProtos.DataMode.REQUIRED)
.build();
expectedSchema.add(Pair.of(SchemaPath.getSimplePath("col"), majorType));
testBuilder()
.sqlQuery(query)
.baselineColumns("col")
.schemaBaseLine(expectedSchema)
.build()
.run();
}
@Test
public void testCSVVerification() throws Exception {
testBuilder()
.sqlQuery("select employee_id, first_name, last_name from cp.`testframework/small_test_data.json`")
.ordered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineTypes(TypeProtos.MinorType.BIGINT, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR)
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
}
@Test
public void testBaselineValsVerification() throws Exception {
testBuilder()
.sqlQuery("select employee_id, first_name, last_name from cp.`testframework/small_test_data.json` limit 1")
.ordered()
.baselineColumns("employee_id", "first_name", "last_name")
.baselineValues(12l, "Jewel", "Creek")
.build().run();
testBuilder()
.sqlQuery("select employee_id, first_name, last_name from cp.`testframework/small_test_data.json` limit 1")
.unOrdered()
.baselineColumns("employee_id", "first_name", "last_name")
.baselineValues(12l, "Jewel", "Creek")
.build().run();
}
@Test
public void testDecimalBaseline() throws Exception {
try {
test(String.format("alter session set `%s` = true", PlannerSettings.ENABLE_DECIMAL_DATA_TYPE_KEY));
// type information can be provided explicitly
testBuilder()
.sqlQuery("select cast(dec_col as decimal(38,2)) dec_col from cp.`testframework/decimal_test.json`")
.unOrdered()
.csvBaselineFile("testframework/decimal_test.tsv")
.baselineTypes(Types.withPrecisionAndScale(TypeProtos.MinorType.VARDECIMAL, TypeProtos.DataMode.REQUIRED, 38, 2))
.baselineColumns("dec_col")
.build().run();
// type information can also be left out, this will prompt the result types of the test query to drive the
// interpretation of the test file
testBuilder()
.sqlQuery("select cast(dec_col as decimal(38,2)) dec_col from cp.`testframework/decimal_test.json`")
.unOrdered()
.csvBaselineFile("testframework/decimal_test.tsv")
.baselineColumns("dec_col")
.build().run();
// Or you can provide explicit values to the builder itself to avoid going through the drill engine at all to
// populate the baseline results
testBuilder()
.sqlQuery("select cast(dec_col as decimal(38,2)) dec_col from cp.`testframework/decimal_test.json`")
.unOrdered()
.baselineColumns("dec_col")
.baselineValues(new BigDecimal("3.70"))
.build().run();
} finally {
test(String.format("alter session set `%s` = false", PlannerSettings.ENABLE_DECIMAL_DATA_TYPE_KEY));
}
}
@Test
public void testMapOrdering() throws Exception {
testBuilder()
.sqlQuery("select * from cp.`testframework/map_reordering.json`")
.unOrdered()
.jsonBaselineFile("testframework/map_reordering2.json")
.build().run();
}
@Test
public void testBaselineValsVerificationWithNulls() throws Exception {
testBuilder()
.sqlQuery("select * from cp.`store/json/json_simple_with_null.json`")
.ordered()
.baselineColumns("a", "b")
.baselineValues(5l, 10l)
.baselineValues(7l, null)
.baselineValues(null, null)
.baselineValues(9l, 11l)
.build().run();
testBuilder()
.sqlQuery("select * from cp.`store/json/json_simple_with_null.json`")
.unOrdered()
.baselineColumns("a", "b")
.baselineValues(5l, 10l)
.baselineValues(9l, 11l)
.baselineValues(7l, null)
.baselineValues(null, null)
.build().run();
}
@Test
public void testBaselineValsVerificationWithComplexAndNulls() throws Exception {
LocalDateTime localDT = LocalDateTime.of(2019, 9, 30, 20, 47, 43, 123);
Instant instant = localDT.atZone(ZoneId.systemDefault()).toInstant();
long ts = instant.toEpochMilli() + instant.getNano();
ts = ts + ZoneId.systemDefault().getRules().getOffset(instant).getTotalSeconds() * 1000L;
try {
testBuilder()
.ordered()
.enableSessionOption(ExecConstants.JSON_EXTENDED_TYPES_KEY)
.sqlQuery("select * from cp.`jsoninput/input2.json` limit 1")
.baselineColumns("integer", "float", "x", "z", "l", "rl", "`date`")
.baselineValues(2010l,
17.4,
mapOf("y", "kevin",
"z", "paul"),
listOf(mapOf("orange", "yellow",
"pink", "red"),
mapOf("pink", "purple")),
listOf(4l, 2l),
listOf(listOf(2l, 1l),
listOf(4l, 6l)),
LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneId.systemDefault()))
.build().run();
} finally {
resetSessionOption(ExecConstants.JSON_EXTENDED_TYPES_KEY);
}
}
@Test
public void testCSVVerification_missing_records_fails() throws Exception {
try {
testBuilder()
.sqlQuery("select employee_id, first_name, last_name from cp.`testframework/small_test_data.json`")
.ordered()
.csvBaselineFile("testframework/small_test_data_extra.tsv")
.baselineTypes(TypeProtos.MinorType.BIGINT, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR)
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
} catch (AssertionError ex) {
assertEquals("Incorrect number of rows returned by query. expected:<7> but was:<5>", ex.getMessage());
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on missing records.");
}
@Test
public void testCSVVerification_extra_records_fails() throws Exception {
try {
testBuilder()
.sqlQuery("select %s from cp.`testframework/small_test_data_extra.tsv`", CSV_COLS)
.ordered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineTypes(TypeProtos.MinorType.BIGINT, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR)
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
} catch (AssertionError ex) {
assertEquals("Incorrect number of rows returned by query. expected:<5> but was:<7>", ex.getMessage());
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure for extra records.");
}
@Test
public void testCSVVerification_extra_column_fails() throws Exception {
try {
testBuilder()
.sqlQuery("select " + CSV_COLS + ", columns[3] as address from cp.`testframework/small_test_data_extra_col.tsv`")
.ordered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineTypes(TypeProtos.MinorType.BIGINT, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR)
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
} catch (AssertionError ex) {
assertEquals("Unexpected extra column `address` returned by query.", ex.getMessage());
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on extra column.");
}
@Test
public void testCSVVerification_missing_column_fails() throws Exception {
try {
testBuilder()
.sqlQuery("select employee_id, first_name, last_name from cp.`testframework/small_test_data.json`")
.ordered()
.csvBaselineFile("testframework/small_test_data_extra_col.tsv")
.baselineTypes(TypeProtos.MinorType.BIGINT, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR)
.baselineColumns("employee_id", "first_name", "last_name", "address")
.build().run();
} catch (Exception ex) {
assertTrue(ex.getMessage(), ex.getMessage().startsWith("Expected column(s) `address`, not found in result set"));
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on missing column.");
}
@Test
public void testCSVVerificationOfTypes() throws Throwable {
try {
testBuilder()
.sqlQuery("select employee_id, first_name, last_name from cp.`testframework/small_test_data.json`")
.ordered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineTypes(TypeProtos.MinorType.INT, TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.VARCHAR)
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
} catch (Exception ex) {
assertThat(ex.getMessage(), CoreMatchers.containsString(
"at position 0 column '`employee_id`' mismatched values, expected: 12(Integer) but received 12(Long)"));
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on type check.");
}
@Test
public void testCSVVerificationOfOrder_checkFailure() throws Throwable {
try {
testBuilder()
.sqlQuery("select columns[0] as employee_id, columns[1] as first_name, columns[2] as last_name from cp.`testframework/small_test_data_reordered.tsv`")
.ordered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
} catch (Exception ex) {
assertThat(ex.getMessage(), CoreMatchers.containsString(
"at position 0 column '`employee_id`' mismatched values, expected: 12(String) but received 16(String)"));
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on order check.");
}
@Test
public void testCSVVerificationOfUnorderedComparison() throws Throwable {
testBuilder()
.sqlQuery("select columns[0] as employee_id, columns[1] as first_name, columns[2] as last_name from cp.`testframework/small_test_data_reordered.tsv`")
.unOrdered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineColumns("employee_id", "first_name", "last_name")
.build().run();
}
// TODO - enable more advanced type handling for JSON, currently basic support works
// add support for type information taken from test query, or explicit type expectations
@Test
public void testBasicJSON() throws Exception {
testBuilder()
.sqlQuery("select * from cp.`scan_json_test_3.json`")
.ordered()
.jsonBaselineFile("/scan_json_test_3.json")
.build().run();
testBuilder()
.sqlQuery("select * from cp.`scan_json_test_3.json`")
.unOrdered() // Check other verification method with same files
.jsonBaselineFile("/scan_json_test_3.json")
.build().run();
}
@Test
public void testComplexJSON_all_text() throws Exception {
testBuilder()
.sqlQuery("select * from cp.`store/json/schema_change_int_to_string.json`")
.optionSettingQueriesForTestQuery("alter system set `store.json.all_text_mode` = true")
.ordered()
.jsonBaselineFile("store/json/schema_change_int_to_string.json")
.optionSettingQueriesForBaseline("alter system set `store.json.all_text_mode` = true")
.build().run();
testBuilder()
.sqlQuery("select * from cp.`store/json/schema_change_int_to_string.json`")
.optionSettingQueriesForTestQuery("alter system set `store.json.all_text_mode` = true")
.unOrdered() // Check other verification method with same files
.jsonBaselineFile("store/json/schema_change_int_to_string.json")
.optionSettingQueriesForBaseline("alter system set `store.json.all_text_mode` = true")
.build().run();
test("alter system set `store.json.all_text_mode` = false");
}
@Test
public void testRepeatedColumnMatching() throws Exception {
try {
testBuilder()
.sqlQuery("select * from cp.`store/json/schema_change_int_to_string.json`")
.optionSettingQueriesForTestQuery("alter system set `store.json.all_text_mode` = true")
.ordered()
.jsonBaselineFile("testframework/schema_change_int_to_string_non-matching.json")
.optionSettingQueriesForBaseline("alter system set `store.json.all_text_mode` = true")
.build().run();
} catch (Exception ex) {
assertThat(ex.getMessage(), CoreMatchers.containsString(
"at position 1 column '`field_1`' mismatched values, " +
"expected: [\"5\",\"2\",\"3\",\"4\",\"1\",\"2\"](JsonStringArrayList) but received [\"5\"](JsonStringArrayList)"));
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on order check.");
}
@Test
public void testEmptyResultSet() throws Exception {
testBuilder()
.sqlQuery("select * from cp.`store/json/json_simple_with_null.json` where 1=0")
.expectsEmptyResultSet()
.build().run();
try {
testBuilder()
.sqlQuery("select * from cp.`store/json/json_simple_with_null.json`")
.expectsEmptyResultSet()
.build().run();
} catch (AssertionError ex) {
assertEquals("Different number of records returned expected:<0> but was:<4>", ex.getMessage());
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on unexpected records.");
}
@Test
public void testCSVVerificationTypeMap() throws Throwable {
Map<SchemaPath, TypeProtos.MajorType> typeMap = new HashMap<>();
typeMap.put(TestBuilder.parsePath("first_name"), Types.optional(TypeProtos.MinorType.VARCHAR));
typeMap.put(TestBuilder.parsePath("employee_id"), Types.optional(TypeProtos.MinorType.INT));
typeMap.put(TestBuilder.parsePath("last_name"), Types.optional(TypeProtos.MinorType.VARCHAR));
testBuilder()
.sqlQuery("select cast(columns[0] as int) employee_id, columns[1] as first_name, columns[2] as last_name from cp.`testframework/small_test_data_reordered.tsv`")
.unOrdered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineColumns("employee_id", "first_name", "last_name")
// This should work without this line because of the default type casts added based on the types that come out of the test query.
// To write a test that enforces strict typing you must pass type information using a CSV with a list of types,
// or any format with a Map of types like is constructed above and include the call to pass it into the test, which is commented out below
//.baselineTypes(typeMap)
.build().run();
typeMap.clear();
typeMap.put(TestBuilder.parsePath("first_name"), Types.optional(TypeProtos.MinorType.VARCHAR));
// This is the wrong type intentionally to ensure failures happen when expected
typeMap.put(TestBuilder.parsePath("employee_id"), Types.optional(TypeProtos.MinorType.VARCHAR));
typeMap.put(TestBuilder.parsePath("last_name"), Types.optional(TypeProtos.MinorType.VARCHAR));
try {
testBuilder()
.sqlQuery("select cast(columns[0] as int) employee_id, columns[1] as first_name, columns[2] as last_name from cp.`testframework/small_test_data_reordered.tsv`")
.unOrdered()
.csvBaselineFile("testframework/small_test_data.tsv")
.baselineColumns("employee_id", "first_name", "last_name")
.baselineTypes(typeMap)
.build().run();
} catch (Exception ex) {
// this indicates successful completion of the test
return;
}
throw new Exception("Test framework verification failed, expected failure on type check.");
}
}