diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ExplainAction.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ExplainAction.groovy index 287d8136c0f0c6..58a979b465f3d7 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ExplainAction.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ExplainAction.groovy @@ -22,7 +22,7 @@ import groovy.transform.stc.FromString import org.apache.doris.regression.suite.SuiteContext import org.apache.doris.regression.util.JdbcUtils import groovy.util.logging.Slf4j - +import java.sql.ResultSetMetaData import java.util.stream.Collectors @Slf4j @@ -112,13 +112,14 @@ class ExplainAction implements SuiteAction { log.info("Execute sql:\n${explainSql}".toString()) long startTime = System.currentTimeMillis() String explainString = null + ResultSetMetaData meta = null try { - explainString = JdbcUtils.executeToList(context.getConnection(), explainSql).stream() - .map({row -> row.get(0).toString()}) - .collect(Collectors.joining("\n")) - return new ActionResult(explainString, null, startTime, System.currentTimeMillis()) + def temp = null + (temp, meta) = JdbcUtils.executeToList(context.getConnection(), explainSql) + explainString = temp.stream().map({row -> row.get(0).toString()}).collect(Collectors.joining("\n")) + return new ActionResult(explainString, null, startTime, System.currentTimeMillis(), meta) } catch (Throwable t) { - return new ActionResult(explainString, t, startTime, System.currentTimeMillis()) + return new ActionResult(explainString, t, startTime, System.currentTimeMillis(), meta) } } @@ -127,12 +128,14 @@ class ExplainAction implements SuiteAction { Throwable exception long startTime long endTime + ResultSetMetaData meta - ActionResult(String result, Throwable exception, long startTime, long endTime) { + ActionResult(String result, Throwable exception, long startTime, long endTime, ResultSetMetaData meta) { this.result = result this.exception = exception this.startTime = startTime this.endTime = endTime + this.meta = meta } } } diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/TestAction.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/TestAction.groovy index 362b37c431e41a..bd03b76d9ea2f1 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/TestAction.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/TestAction.groovy @@ -33,7 +33,7 @@ import org.apache.http.util.EntityUtils import javax.swing.text.html.parser.Entity import java.nio.charset.StandardCharsets import java.sql.Connection - +import java.sql.ResultSetMetaData import org.apache.doris.regression.suite.SuiteContext import org.apache.doris.regression.util.JdbcUtils import org.junit.Assert @@ -94,10 +94,10 @@ class TestAction implements SuiteAction { if (row instanceof List) { return OutputUtils.toCsvString(row as List) } else { - return OutputUtils.columnToCsvString(row) + return OutputUtils.toCsvString(row as Object) } }, - { List row -> OutputUtils.toCsvString(row) }, "Check failed") + { List row -> OutputUtils.toCsvString(row) }, "Check failed", result.meta) if (errorMsg != null) { throw new IllegalStateException(errorMsg) } @@ -109,7 +109,7 @@ class TestAction implements SuiteAction { String errMsg = OutputUtils.checkOutput(csvIt, result.result.iterator(), { List row -> OutputUtils.toCsvString(row as List) }, { List row -> OutputUtils.toCsvString(row) }, - "Check failed compare to") + "Check failed compare to", result.meta) if (errMsg != null) { throw new IllegalStateException(errMsg) } @@ -136,7 +136,8 @@ class TestAction implements SuiteAction { if (!file.exists()) { log.warn("Result file not exists: ${file}".toString()) } - log.warn("Compare to local file: ${file}".toString()) + + log.info("Compare to local file: ${file}".toString()) file.newInputStream().withCloseable { inputStream -> checkFunc(inputStream) } @@ -150,11 +151,13 @@ class TestAction implements SuiteAction { ActionResult doRun(Connection conn) { List> result = null + ResultSetMetaData meta = null Throwable ex = null + long startTime = System.currentTimeMillis() try { log.info("Execute ${isOrder ? "order_" : ""}sql:\n${sql}".toString()) - result = JdbcUtils.executeToList(conn, sql) + (result, meta) = JdbcUtils.executeToList(conn, sql) if (isOrder) { result = DataUtils.sortByToString(result) } @@ -162,7 +165,8 @@ class TestAction implements SuiteAction { ex = t } long endTime = System.currentTimeMillis() - return new ActionResult(result, ex, startTime, endTime) + + return new ActionResult(result, ex, startTime, endTime, meta) } void sql(String sql) { @@ -234,12 +238,14 @@ class TestAction implements SuiteAction { Throwable exception long startTime long endTime + ResultSetMetaData meta - ActionResult(List> result, Throwable exception, long startTime, long endTime) { + ActionResult(List> result, Throwable exception, long startTime, long endTime, ResultSetMetaData meta) { this.result = result this.exception = exception this.startTime = startTime this.endTime = endTime + this.meta = meta } } } diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy index c8f2b03b3026e0..17a0d152bfd95a 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy @@ -184,7 +184,7 @@ class Suite implements GroovyInterceptable { List> sql(String sqlStr, boolean isOrder = false) { logger.info("Execute ${isOrder ? "order_" : ""}sql: ${sqlStr}".toString()) - def result = JdbcUtils.executeToList(context.getConnection(), sqlStr) + def (result, meta) = JdbcUtils.executeToList(context.getConnection(), sqlStr) if (isOrder) { result = DataUtils.sortByToString(result) } @@ -265,7 +265,7 @@ class Suite implements GroovyInterceptable { logger.info("Execute tag: ${tag}, ${isOrder ? "order_" : ""}sql: ${sql}".toString()) if (context.config.generateOutputFile || context.config.forceGenerateOutputFile) { - def result = JdbcUtils.executorToStringList(context.getConnection(), sql) + def (result, meta) = JdbcUtils.executeToStringList(context.getConnection(), sql) if (isOrder) { result = sortByToString(result) } @@ -283,7 +283,8 @@ class Suite implements GroovyInterceptable { } OutputUtils.TagBlockIterator expectCsvResults = context.getOutputIterator().next() - List> realResults = JdbcUtils.executorToStringList(context.getConnection(), sql) + + def (realResults, meta) = JdbcUtils.executeToStringList(context.getConnection(), sql) if (isOrder) { realResults = sortByToString(realResults) } @@ -291,8 +292,8 @@ class Suite implements GroovyInterceptable { try { errorMsg = OutputUtils.checkOutput(expectCsvResults, realResults.iterator(), { row -> OutputUtils.toCsvString(row as List) }, - {row -> OutputUtils.toCsvString(row) }, - "Check tag '${tag}' failed") + { row -> OutputUtils.toCsvString(row) }, + "Check tag '${tag}' failed", meta) } catch (Throwable t) { throw new IllegalStateException("Check tag '${tag}' failed, sql:\n${sql}", t) } diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy index b0a0bcdecbde6a..e7fb1901030537 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy @@ -18,34 +18,36 @@ package org.apache.doris.regression.util import com.google.common.collect.ImmutableList +import groovy.lang.Tuple2 import java.sql.Connection import java.sql.ResultSet +import java.sql.ResultSetMetaData class JdbcUtils { - static List> executeToList(Connection conn, String sql) { + static Tuple2>, ResultSetMetaData> executeToList(Connection conn, String sql) { conn.prepareStatement(sql).withCloseable { stmt -> boolean hasResultSet = stmt.execute() if (!hasResultSet) { - return ImmutableList.of(ImmutableList.of(stmt.getUpdateCount())) + return [ImmutableList.of(ImmutableList.of(stmt.getUpdateCount())), null] } else { - toList(stmt.resultSet) + return toList(stmt.resultSet) } } } - static List> executorToStringList(Connection conn, String sql) { + static Tuple2>, ResultSetMetaData> executeToStringList(Connection conn, String sql) { conn.prepareStatement(sql).withCloseable { stmt -> boolean hasResultSet = stmt.execute() if (!hasResultSet) { - return ImmutableList.of(ImmutableList.of(stmt.getUpdateCount())) + return [ImmutableList.of(ImmutableList.of(stmt.getUpdateCount())), null] } else { - toStringList(stmt.resultSet) + return toStringList(stmt.resultSet) } } } - static List> toList(ResultSet resultSet) { + static Tuple2>, ResultSetMetaData> toList(ResultSet resultSet) { resultSet.withCloseable { List> rows = new ArrayList<>() def columnCount = resultSet.metaData.columnCount @@ -56,19 +58,17 @@ class JdbcUtils { } rows.add(row) } - return rows + return [rows, resultSet.metaData] } } - static List> toStringList(ResultSet resultSet) { + static Tuple2>, ResultSetMetaData> toStringList(ResultSet resultSet) { resultSet.withCloseable { List> rows = new ArrayList<>() def columnCount = resultSet.metaData.columnCount while (resultSet.next()) { def row = new ArrayList<>() for (int i = 1; i <= columnCount; ++i) { - // row.add(resultSet.getObject(i)) - // row.add(resultSet.getString(i)) try { row.add(resultSet.getObject(i)) } catch (Throwable t) { @@ -81,7 +81,7 @@ class JdbcUtils { } rows.add(row) } - return rows + return [rows, resultSet.metaData] } } } diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/OutputUtils.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/OutputUtils.groovy index c30c893474b3fb..ce483d3b8544a3 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/OutputUtils.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/OutputUtils.groovy @@ -25,13 +25,22 @@ import org.apache.commons.csv.CSVRecord import org.apache.commons.io.LineIterator import java.util.function.Function +import java.sql.ResultSetMetaData @CompileStatic class OutputUtils { - static String columnToCsvString(Object column) { + private static List castList(Object obj) { + List result = new ArrayList(); + for (Object o: (List) obj) { + result.add(toCsvString(o)); + } + return result; + } + + static String toCsvString(Object cell) { StringWriter writer = new StringWriter() def printer = new CSVPrinter(new PrintWriter(writer), CSVFormat.MYSQL) - printer.print(column) + printer.print(cell) return writer.toString() } @@ -44,9 +53,36 @@ class OutputUtils { return writer.toString() } + static String checkCell(String info, int line, String expectCell, String realCell, String dataType) { + if(dataType == "FLOAT" || dataType == "DOUBLE") { + double expectDouble = Double.parseDouble(expectCell) + double realDouble = Double.parseDouble(realCell) + + double realRelativeError = Math.abs(expectDouble - realDouble) / realDouble + double expectRelativeError = 1e-10 + + if(expectRelativeError < realRelativeError) { + return "${info}, line ${line}, ${dataType} result mismatch.\nExpect cell is: ${expectCell}\nBut real is: ${realCell}\nrelative error is: ${realRelativeError}, bigger than ${expectRelativeError}" + } + } else if(dataType == "DATE" || dataType =="DATETIME") { + expectCell = expectCell.replace("T", " ") + realCell = realCell.replace("T", " ") + + if(!expectCell.equals(realCell)) { + return "${info}, line ${line}, ${dataType} result mismatch.\nExpect cell is: ${expectCell}\nBut real is: ${realCell}" + } + } else { + if(!expectCell.equals(realCell)) { + return "${info}, line ${line}, ${dataType} result mismatch.\nExpect cell is: ${expectCell}\nBut real is: ${realCell}" + } + } + + return null + } + static String checkOutput(Iterator expect, Iterator real, Function transform1, Function transform2, - String info) { + String info, ResultSetMetaData meta) { int line = 1 while (true) { if (expect.hasNext() && !real.hasNext()) { @@ -59,11 +95,32 @@ class OutputUtils { break } - def expectCsvString = transform1.apply(expect.next()) - def realCsvString = transform2.apply(real.next()) - if (!expectCsvString.equals(realCsvString)) { - return "${info}, line ${line} mismatch.\nExpect line is: ${expectCsvString}\nBut real is : ${realCsvString}" + def expectRaw = expect.next() + def realRaw = real.next() + + if (expectRaw instanceof List && meta != null) { + List expectList = castList(expectRaw) + List realList = castList(realRaw) + + def columnCount = meta.columnCount + for (int i = 1; i <= columnCount; i++) { + String expectCell = toCsvString(expectList[i - 1]) + String realCell = toCsvString(realList[i - 1]) + String dataType = meta.getColumnTypeName(i) + + def res = checkCell(info, line, expectCell, realCell, dataType) + if(res != null) { + return res + } + } + } else { + def expectCsvString = transform1.apply(expectRaw) + def realCsvString = transform2.apply(realRaw) + if (!expectCsvString.equals(realCsvString)) { + return "${info}, line ${line} mismatch.\nExpect line is: ${expectCsvString}\nBut real is: ${realCsvString}" + } } + line++ } }