[refactor](test) Refactor FE unit test framework that starts a FE server. (#9388)
Currently, we use `UtFrameUtils` to start a FE server in the FE unit test. Each test class has to do some initialization and clean up stuff with the JUnit4 `@BeforeClass` and `@AfterClass` annotation. It's redundant and boring. Besides, almost all the APIs in `UtFrameUtils` has a `ConnectContext` parameter, which is not easy to use. This PR proposes to use an inherit-manner, i.e., wrap all the common logic in base class `TestWithFeService`, leveraging the JUnit5 `@BeforeAll` and `@AfterAll` annotation to narrow down the setup and cleanup lifecycle to each test class instance. At the same time, the derived concrete test class could directly use utility methods inherited from the base class, without calling a util class and passing a `ConnectContext` argument. `UtFrameUtils` and `DorisAssert` are marked as deprecated. We could remove these two classes if this refactor works well for a time.
This commit is contained in:
@ -220,11 +220,25 @@ under the License.
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/junit/junit -->
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.vintage/junit-vintage-engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
|
||||
|
||||
@ -20,54 +20,37 @@ package org.apache.doris.analysis;
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.common.AnalysisException;
|
||||
import org.apache.doris.common.DdlException;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AdminSetConfigStmtTest {
|
||||
private static String runningDir = "fe/mocked/AdminSetConfigStmtTest/" + UUID.randomUUID().toString() + "/";
|
||||
|
||||
private static ConnectContext connectContext;
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedEx = ExpectedException.none();
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
|
||||
// create connect context
|
||||
connectContext = UtFrameUtils.createDefaultCtx();
|
||||
}
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class AdminSetConfigStmtTest extends TestWithFeService {
|
||||
@Test
|
||||
public void testNormal() throws Exception {
|
||||
String stmt = "admin set frontend config(\"alter_table_timeout_second\" = \"60\");";
|
||||
AdminSetConfigStmt adminSetConfigStmt = (AdminSetConfigStmt) UtFrameUtils.parseAndAnalyzeStmt(stmt, connectContext);
|
||||
AdminSetConfigStmt adminSetConfigStmt = (AdminSetConfigStmt) parseAndAnalyzeStmt(stmt);
|
||||
Catalog.getCurrentCatalog().setConfig(adminSetConfigStmt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownConfig() throws Exception {
|
||||
String stmt = "admin set frontend config(\"unknown_config\" = \"unknown\");";
|
||||
AdminSetConfigStmt adminSetConfigStmt = (AdminSetConfigStmt) UtFrameUtils.parseAndAnalyzeStmt(stmt, connectContext);
|
||||
expectedEx.expect(DdlException.class);
|
||||
expectedEx.expectMessage("errCode = 2, detailMessage = Config 'unknown_config' does not exist");
|
||||
Catalog.getCurrentCatalog().setConfig(adminSetConfigStmt);
|
||||
AdminSetConfigStmt adminSetConfigStmt = (AdminSetConfigStmt) parseAndAnalyzeStmt(stmt);
|
||||
DdlException exception = assertThrows(DdlException.class,
|
||||
() -> Catalog.getCurrentCatalog().setConfig(adminSetConfigStmt));
|
||||
assertEquals("errCode = 2, detailMessage = Config 'unknown_config' does not exist",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyConfig() throws Exception {
|
||||
String stmt = "admin set frontend config;";
|
||||
expectedEx.expect(AnalysisException.class);
|
||||
expectedEx.expectMessage("errCode = 2, detailMessage = config parameter size is not equal to 1");
|
||||
AdminSetConfigStmt adminSetConfigStmt = (AdminSetConfigStmt) UtFrameUtils.parseAndAnalyzeStmt(stmt, connectContext);
|
||||
public void testEmptyConfig() {
|
||||
AnalysisException exception =
|
||||
assertThrows(AnalysisException.class,
|
||||
() -> parseAndAnalyzeStmt("admin set frontend config;"));
|
||||
assertEquals("errCode = 2, detailMessage = config parameter size is not equal to 1",
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,76 +25,43 @@ import org.apache.doris.catalog.Partition;
|
||||
import org.apache.doris.catalog.Replica;
|
||||
import org.apache.doris.catalog.Tablet;
|
||||
import org.apache.doris.common.util.SqlParserUtils;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.qe.ShowExecutor;
|
||||
import org.apache.doris.qe.ShowResultSet;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AdminShowReplicaTest {
|
||||
|
||||
// use a unique dir so that it won't be conflict with other unit test which
|
||||
// may also start a Mocked Frontend
|
||||
private static String runningDir = "fe/mocked/AdminShowReplicaTest/" + UUID.randomUUID().toString() + "/";
|
||||
|
||||
private static ConnectContext connectContext;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
|
||||
// create connect context
|
||||
connectContext = UtFrameUtils.createDefaultCtx();
|
||||
|
||||
// create database
|
||||
String createDbStmtStr = "create database test;";
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, connectContext);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
|
||||
public class AdminShowReplicaTest extends TestWithFeService {
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
createDatabase("test");
|
||||
createTable("create table test.tbl1\n" +
|
||||
"(k1 date, k2 int)\n" +
|
||||
"partition by range(k1)\n" +
|
||||
"(\n" +
|
||||
" partition p1 values less than(\"2021-07-01\"),\n" +
|
||||
" partition p2 values less than(\"2021-08-01\")\n" +
|
||||
")\n" +
|
||||
"distributed by hash(k2) buckets 10\n" +
|
||||
"properties(\"replication_num\" = \"1\");");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
File file = new File(runningDir);
|
||||
file.delete();
|
||||
}
|
||||
|
||||
private static void createTable(String sql) throws Exception {
|
||||
CreateTableStmt createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, connectContext);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
"(k1 date, k2 int)\n" +
|
||||
"partition by range(k1)\n" +
|
||||
"(\n" +
|
||||
" partition p1 values less than(\"2021-07-01\"),\n" +
|
||||
" partition p2 values less than(\"2021-08-01\")\n" +
|
||||
")\n" +
|
||||
"distributed by hash(k2) buckets 10\n" +
|
||||
"properties(\"replication_num\" = \"1\");");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShowReplicaDistribution() throws Exception {
|
||||
String stmtStr = "admin show replica distribution from test.tbl1 partition(p1)";
|
||||
AdminShowReplicaDistributionStmt stmt = (AdminShowReplicaDistributionStmt) UtFrameUtils.parseAndAnalyzeStmt(
|
||||
stmtStr, connectContext);
|
||||
AdminShowReplicaDistributionStmt stmt = (AdminShowReplicaDistributionStmt) parseAndAnalyzeStmt(
|
||||
stmtStr);
|
||||
ShowExecutor executor = new ShowExecutor(connectContext, stmt);
|
||||
ShowResultSet resultSet = executor.execute();
|
||||
Assert.assertEquals(1, resultSet.getResultRows().size());
|
||||
Assert.assertEquals(7, resultSet.getResultRows().get(0).size());
|
||||
|
||||
stmtStr = "show data skew from test.tbl1 partition(p1)";
|
||||
ShowDataSkewStmt skewStmt = (ShowDataSkewStmt) UtFrameUtils.parseAndAnalyzeStmt(
|
||||
stmtStr, connectContext);
|
||||
ShowDataSkewStmt skewStmt = (ShowDataSkewStmt) parseAndAnalyzeStmt(stmtStr);
|
||||
executor = new ShowExecutor(connectContext, skewStmt);
|
||||
resultSet = executor.execute();
|
||||
Assert.assertEquals(10, resultSet.getResultRows().size());
|
||||
@ -190,5 +157,4 @@ public class AdminShowReplicaTest {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -21,34 +21,26 @@ import org.apache.doris.common.AnalysisException;
|
||||
import org.apache.doris.common.FeConstants;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.utframe.DorisAssert;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AggregateTest {
|
||||
|
||||
private static String baseDir = "fe";
|
||||
private static String runningDir = baseDir + "/mocked/AggregateTest/"
|
||||
+ UUID.randomUUID().toString() + "/";
|
||||
public class AggregateTest extends TestWithFeService {
|
||||
private static final String TABLE_NAME = "table1";
|
||||
private static final String DB_NAME = "db1";
|
||||
private static DorisAssert dorisAssert;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception{
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
FeConstants.runningUnitTest = true;
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
dorisAssert = new DorisAssert();
|
||||
dorisAssert.withDatabase(DB_NAME).useDatabase(DB_NAME);
|
||||
String createTableSQL = "create table " + DB_NAME + "." + TABLE_NAME + " (empid int, name varchar, " +
|
||||
"deptno int, salary int, commission int, time DATETIME) "
|
||||
+ "distributed by hash(empid) buckets 3 properties('replication_num' = '1');";
|
||||
dorisAssert.withTable(createTableSQL);
|
||||
"deptno int, salary int, commission int, time DATETIME) "
|
||||
+ "distributed by hash(empid) buckets 3 properties('replication_num' = '1');";
|
||||
createTable(createTableSQL);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,9 +173,4 @@ public class AggregateTest {
|
||||
Assert.fail("must be AnalysisException.");
|
||||
} while(false);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
UtFrameUtils.cleanDorisFeDir(baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,29 +17,16 @@
|
||||
|
||||
package org.apache.doris.analysis;
|
||||
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.common.FeConstants;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ListPartitionPrunerTest extends PartitionPruneTestBase {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
FeConstants.runningUnitTest = true;
|
||||
runningDir = "fe/mocked/ListPartitionPrunerTest/" + UUID.randomUUID().toString() + "/";
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
|
||||
connectContext = UtFrameUtils.createDefaultCtx();
|
||||
|
||||
String createDbStmtStr = "create database test;";
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, connectContext);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
createDatabase("test");
|
||||
|
||||
String createSinglePartColWithSinglePartKey =
|
||||
"create table test.t1\n"
|
||||
@ -84,15 +71,10 @@ public class ListPartitionPrunerTest extends PartitionPruneTestBase {
|
||||
+ "distributed by hash(k2) buckets 1\n"
|
||||
+ "properties('replication_num' = '1');";
|
||||
|
||||
createTable(createSinglePartColWithSinglePartKey);
|
||||
createTable(createSinglePartColWithMultiPartKey);
|
||||
createTable(createMultiPartColWithSinglePartKey);
|
||||
createTable(createMultiPartColWithMultiPartKey);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() throws Exception {
|
||||
UtFrameUtils.cleanDorisFeDir(runningDir);
|
||||
createTables(createSinglePartColWithSinglePartKey,
|
||||
createSinglePartColWithMultiPartKey,
|
||||
createMultiPartColWithSinglePartKey,
|
||||
createMultiPartColWithMultiPartKey);
|
||||
}
|
||||
|
||||
private void initTestCases() {
|
||||
|
||||
@ -17,19 +17,14 @@
|
||||
|
||||
package org.apache.doris.analysis;
|
||||
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PartitionPruneTestBase {
|
||||
protected static String runningDir;
|
||||
protected static ConnectContext connectContext;
|
||||
|
||||
public abstract class PartitionPruneTestBase extends TestWithFeService {
|
||||
protected List<TestCase> cases = new ArrayList<>();
|
||||
|
||||
protected void doTest() throws Exception {
|
||||
@ -41,16 +36,10 @@ public class PartitionPruneTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected static void createTable(String sql) throws Exception {
|
||||
CreateTableStmt createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, connectContext);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
}
|
||||
|
||||
private void assertExplainContains(int version, String sql, String subString) throws Exception {
|
||||
Assert.assertTrue(String.format("version=%d, sql=%s, expectResult=%s",
|
||||
version, sql, subString),
|
||||
UtFrameUtils.getSQLPlanOrErrorMsg(connectContext, "explain " + sql)
|
||||
.contains(subString));
|
||||
version, sql, subString),
|
||||
getSQLPlanOrErrorMsg("explain " + sql).contains(subString));
|
||||
}
|
||||
|
||||
protected void addCase(String sql, String v1Result, String v2Result) {
|
||||
|
||||
@ -17,29 +17,16 @@
|
||||
|
||||
package org.apache.doris.analysis;
|
||||
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.common.FeConstants;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class RangePartitionPruneTest extends PartitionPruneTestBase {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
FeConstants.runningUnitTest = true;
|
||||
runningDir = "fe/mocked/RangePartitionPruneTest/" + UUID.randomUUID().toString() + "/";
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
|
||||
connectContext = UtFrameUtils.createDefaultCtx();
|
||||
|
||||
String createDbStmtStr = "create database test;";
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, connectContext);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
createDatabase("test");
|
||||
|
||||
String singleColumnPartitionTable =
|
||||
"CREATE TABLE `test`.`t1` (\n" +
|
||||
@ -117,15 +104,10 @@ public class RangePartitionPruneTest extends PartitionPruneTestBase {
|
||||
"DISTRIBUTED BY HASH(`k1`) BUCKETS 10\n" +
|
||||
"PROPERTIES ('replication_num' = '1');";
|
||||
|
||||
createTable(singleColumnPartitionTable);
|
||||
createTable(notNullSingleColumnPartitionTable);
|
||||
createTable(multipleColumnsPartitionTable);
|
||||
createTable(notNullMultipleColumnsPartitionTable);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() throws Exception {
|
||||
UtFrameUtils.cleanDorisFeDir(runningDir);
|
||||
createTables(singleColumnPartitionTable,
|
||||
notNullSingleColumnPartitionTable,
|
||||
multipleColumnsPartitionTable,
|
||||
notNullMultipleColumnsPartitionTable);
|
||||
}
|
||||
|
||||
private void initTestCases() {
|
||||
|
||||
@ -18,22 +18,17 @@
|
||||
package org.apache.doris.catalog;
|
||||
|
||||
import org.apache.doris.analysis.AdminSetReplicaStatusStmt;
|
||||
import org.apache.doris.analysis.CreateDbStmt;
|
||||
import org.apache.doris.analysis.CreateTableStmt;
|
||||
import org.apache.doris.catalog.MaterializedIndex.IndexExtState;
|
||||
import org.apache.doris.catalog.Replica.ReplicaStatus;
|
||||
import org.apache.doris.common.AnalysisException;
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.persist.SetReplicaStatusOperationLog;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@ -42,44 +37,20 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AdminStmtTest {
|
||||
|
||||
// use a unique dir so that it won't be conflict with other unit test which
|
||||
// may also start a Mocked Frontend
|
||||
private static String runningDir = "fe/mocked/AdminStmtTest/" + UUID.randomUUID().toString() + "/";
|
||||
|
||||
private static ConnectContext connectContext;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
|
||||
// create connect context
|
||||
connectContext = UtFrameUtils.createDefaultCtx();
|
||||
// create database
|
||||
String createDbStmtStr = "create database test;";
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, connectContext);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
|
||||
String sql = "CREATE TABLE test.tbl1 (\n" +
|
||||
" `id` int(11) NULL COMMENT \"\",\n" +
|
||||
" `id2` bitmap bitmap_union NULL\n" +
|
||||
") ENGINE=OLAP\n" +
|
||||
"AGGREGATE KEY(`id`)\n" +
|
||||
"DISTRIBUTED BY HASH(`id`) BUCKETS 3\n" +
|
||||
"PROPERTIES (\n" +
|
||||
" \"replication_num\" = \"1\"\n" +
|
||||
");";
|
||||
CreateTableStmt createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, connectContext);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
File file = new File(runningDir);
|
||||
file.delete();
|
||||
public class AdminStmtTest extends TestWithFeService {
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
createDatabase("test");
|
||||
createTable( "CREATE TABLE test.tbl1 (\n" +
|
||||
" `id` int(11) NULL COMMENT \"\",\n" +
|
||||
" `id2` bitmap bitmap_union NULL\n" +
|
||||
") ENGINE=OLAP\n" +
|
||||
"AGGREGATE KEY(`id`)\n" +
|
||||
"DISTRIBUTED BY HASH(`id`) BUCKETS 3\n" +
|
||||
"PROPERTIES (\n" +
|
||||
" \"replication_num\" = \"1\"\n" +
|
||||
");");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -108,7 +79,7 @@ public class AdminStmtTest {
|
||||
// set replica to bad
|
||||
String adminStmt = "admin set replica status properties ('tablet_id' = '" + tabletId + "', 'backend_id' = '"
|
||||
+ backendId + "', 'status' = 'bad');";
|
||||
AdminSetReplicaStatusStmt stmt = (AdminSetReplicaStatusStmt) UtFrameUtils.parseAndAnalyzeStmt(adminStmt, connectContext);
|
||||
AdminSetReplicaStatusStmt stmt = (AdminSetReplicaStatusStmt) parseAndAnalyzeStmt(adminStmt);
|
||||
Catalog.getCurrentCatalog().setReplicaStatus(stmt);
|
||||
replica = Catalog.getCurrentInvertedIndex().getReplica(tabletId, backendId);
|
||||
Assert.assertTrue(replica.isBad());
|
||||
@ -116,7 +87,7 @@ public class AdminStmtTest {
|
||||
// set replica to ok
|
||||
adminStmt = "admin set replica status properties ('tablet_id' = '" + tabletId + "', 'backend_id' = '"
|
||||
+ backendId + "', 'status' = 'ok');";
|
||||
stmt = (AdminSetReplicaStatusStmt) UtFrameUtils.parseAndAnalyzeStmt(adminStmt, connectContext);
|
||||
stmt = (AdminSetReplicaStatusStmt) parseAndAnalyzeStmt(adminStmt);
|
||||
Catalog.getCurrentCatalog().setReplicaStatus(stmt);
|
||||
replica = Catalog.getCurrentInvertedIndex().getReplica(tabletId, backendId);
|
||||
Assert.assertFalse(replica.isBad());
|
||||
|
||||
@ -17,63 +17,54 @@
|
||||
|
||||
package org.apache.doris.planner;
|
||||
|
||||
import org.apache.doris.analysis.CreateDbStmt;
|
||||
import org.apache.doris.analysis.CreateTableStmt;
|
||||
import org.apache.doris.analysis.ExplainOptions;
|
||||
import org.apache.doris.analysis.Expr;
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.common.AnalysisException;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.qe.StmtExecutor;
|
||||
import org.apache.doris.utframe.UtFrameUtils;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlannerTest {
|
||||
private static String runningDir = "fe/mocked/DemoTest/" + UUID.randomUUID().toString() + "/";
|
||||
private static ConnectContext ctx;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedEx = ExpectedException.none();
|
||||
public class PlannerTest extends TestWithFeService {
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
FileUtils.deleteDirectory(new File(runningDir));
|
||||
}
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
// Create database `db1`.
|
||||
createDatabase("db1");
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws Exception {
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
ctx = UtFrameUtils.createDefaultCtx();
|
||||
String createDbStmtStr = "create database db1;";
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, ctx);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
// 3. create table tbl1
|
||||
String createTblStmtStr = "create table db1.tbl1(k1 varchar(32), k2 varchar(32), k3 varchar(32), k4 int) "
|
||||
+ "AGGREGATE KEY(k1, k2,k3,k4) distributed by hash(k1) buckets 3 properties('replication_num' = '1');";
|
||||
CreateTableStmt createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(createTblStmtStr, ctx);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
// Create tables.
|
||||
String tbl1 = "create table db1.tbl1(" +
|
||||
"k1 varchar(32), " +
|
||||
"k2 varchar(32), " +
|
||||
"k3 varchar(32), " +
|
||||
"k4 int) " +
|
||||
"AGGREGATE KEY(k1, k2,k3,k4) " +
|
||||
"distributed by hash(k1) buckets 3 " +
|
||||
"properties('replication_num' = '1');";
|
||||
|
||||
createTblStmtStr = "create table db1.tbl2(k1 int, k2 int sum) "
|
||||
+ "AGGREGATE KEY(k1) partition by range(k1) () distributed by hash(k1) buckets 3 properties('replication_num' = '1');";
|
||||
createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(createTblStmtStr, ctx);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
String tbl2 = "create table db1.tbl2(" +
|
||||
"k1 int, " +
|
||||
"k2 int sum) " +
|
||||
"AGGREGATE KEY(k1) " +
|
||||
"partition by range(k1) () " +
|
||||
"distributed by hash(k1) buckets 3 " +
|
||||
"properties('replication_num' = '1');";
|
||||
|
||||
createTblStmtStr = "create table db1.tbl3 (k1 date, k2 varchar(128) NULL, k3 varchar(5000) NULL) "
|
||||
+ "DUPLICATE KEY(k1, k2, k3) distributed by hash(k1) buckets 1 properties ('replication_num' = '1');";
|
||||
createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(createTblStmtStr, ctx);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
String tbl3 = "create table db1.tbl3 (" +
|
||||
"k1 date, " +
|
||||
"k2 varchar(128) NULL, " +
|
||||
"k3 varchar(5000) NULL) " +
|
||||
"DUPLICATE KEY(k1, k2, k3) " +
|
||||
"distributed by hash(k1) buckets 1 " +
|
||||
"properties ('replication_num' = '1');";
|
||||
createTables(tbl1, tbl2, tbl3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -87,7 +78,7 @@ public class PlannerTest {
|
||||
+ " db1.tbl1 b\n"
|
||||
+ " on (a.k1 = b.k1)\n"
|
||||
+ "where b.k1 = 'a'";
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(ctx, sql1);
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
|
||||
stmtExecutor1.execute();
|
||||
Planner planner1 = stmtExecutor1.planner();
|
||||
List<PlanFragment> fragments1 = planner1.getFragments();
|
||||
@ -112,7 +103,7 @@ public class PlannerTest {
|
||||
+ " union all\n"
|
||||
+ " (select * from db1.tbl1 where k1='b' and k4=5)\n"
|
||||
+ " order by 3 limit 3)";
|
||||
StmtExecutor stmtExecutor2 = new StmtExecutor(ctx, sql2);
|
||||
StmtExecutor stmtExecutor2 = new StmtExecutor(connectContext, sql2);
|
||||
stmtExecutor2.execute();
|
||||
Planner planner2 = stmtExecutor2.planner();
|
||||
List<PlanFragment> fragments2 = planner2.getFragments();
|
||||
@ -128,7 +119,7 @@ public class PlannerTest {
|
||||
+ " db1.tbl1 b\n"
|
||||
+ " on (a.k1 = b.k1)\n"
|
||||
+ "where b.k1 = 'a'";
|
||||
StmtExecutor stmtExecutor3 = new StmtExecutor(ctx, sql3);
|
||||
StmtExecutor stmtExecutor3 = new StmtExecutor(connectContext, sql3);
|
||||
stmtExecutor3.execute();
|
||||
Planner planner3 = stmtExecutor3.planner();
|
||||
List<PlanFragment> fragments3 = planner3.getFragments();
|
||||
@ -154,7 +145,7 @@ public class PlannerTest {
|
||||
+ " (select * from db1.tbl1 where k1='b' and k4=5)\n"
|
||||
+ " order by 3 limit 3)";
|
||||
|
||||
StmtExecutor stmtExecutor4 = new StmtExecutor(ctx, sql4);
|
||||
StmtExecutor stmtExecutor4 = new StmtExecutor(connectContext, sql4);
|
||||
stmtExecutor4.execute();
|
||||
Planner planner4 = stmtExecutor4.planner();
|
||||
List<PlanFragment> fragments4 = planner4.getFragments();
|
||||
@ -170,7 +161,7 @@ public class PlannerTest {
|
||||
+ " db1.tbl1 b\n"
|
||||
+ " on (a.k1 = b.k1)\n"
|
||||
+ "where b.k1 = 'a'";
|
||||
StmtExecutor stmtExecutor5 = new StmtExecutor(ctx, sql5);
|
||||
StmtExecutor stmtExecutor5 = new StmtExecutor(connectContext, sql5);
|
||||
stmtExecutor5.execute();
|
||||
Planner planner5 = stmtExecutor5.planner();
|
||||
List<PlanFragment> fragments5 = planner5.getFragments();
|
||||
@ -185,7 +176,7 @@ public class PlannerTest {
|
||||
+ "except distinct\n"
|
||||
+ "(select * from db1.tbl1 where k1='a' and k4=2)\n"
|
||||
+ "order by 3 limit 3";
|
||||
StmtExecutor stmtExecutor6 = new StmtExecutor(ctx, sql6);
|
||||
StmtExecutor stmtExecutor6 = new StmtExecutor(connectContext, sql6);
|
||||
stmtExecutor6.execute();
|
||||
Planner planner6 = stmtExecutor6.planner();
|
||||
List<PlanFragment> fragments6 = planner6.getFragments();
|
||||
@ -200,7 +191,7 @@ public class PlannerTest {
|
||||
+ "except\n"
|
||||
+ "(select * from db1.tbl1 where k1='a' and k4=2)\n"
|
||||
+ "order by 3 limit 3";
|
||||
StmtExecutor stmtExecutor7 = new StmtExecutor(ctx, sql7);
|
||||
StmtExecutor stmtExecutor7 = new StmtExecutor(connectContext, sql7);
|
||||
stmtExecutor7.execute();
|
||||
Planner planner7 = stmtExecutor7.planner();
|
||||
List<PlanFragment> fragments7 = planner7.getFragments();
|
||||
@ -216,7 +207,7 @@ public class PlannerTest {
|
||||
+ "intersect\n"
|
||||
+ "(select * from db1.tbl1 where k1='a' and k4=2)\n"
|
||||
+ "order by 3 limit 3";
|
||||
StmtExecutor stmtExecutor8 = new StmtExecutor(ctx, sql8);
|
||||
StmtExecutor stmtExecutor8 = new StmtExecutor(connectContext, sql8);
|
||||
stmtExecutor8.execute();
|
||||
Planner planner8 = stmtExecutor8.planner();
|
||||
List<PlanFragment> fragments8 = planner8.getFragments();
|
||||
@ -245,7 +236,7 @@ public class PlannerTest {
|
||||
+ " (select * from db1.tbl1 where k1='b' and k4=5)\n"
|
||||
+ " order by 3 limit 3)";
|
||||
|
||||
StmtExecutor stmtExecutor9 = new StmtExecutor(ctx, sql9);
|
||||
StmtExecutor stmtExecutor9 = new StmtExecutor(connectContext, sql9);
|
||||
stmtExecutor9.execute();
|
||||
Planner planner9 = stmtExecutor9.planner();
|
||||
List<PlanFragment> fragments9 = planner9.getFragments();
|
||||
@ -255,7 +246,7 @@ public class PlannerTest {
|
||||
Assert.assertEquals(2, StringUtils.countMatches(plan9, "EXCEPT"));
|
||||
|
||||
String sql10 = "select 499 union select 670 except select 499";
|
||||
StmtExecutor stmtExecutor10 = new StmtExecutor(ctx, sql10);
|
||||
StmtExecutor stmtExecutor10 = new StmtExecutor(connectContext, sql10);
|
||||
stmtExecutor10.execute();
|
||||
Planner planner10 = stmtExecutor10.planner();
|
||||
List<PlanFragment> fragments10 = planner10.getFragments();
|
||||
@ -268,7 +259,7 @@ public class PlannerTest {
|
||||
"(SELECT '01' x) a \n" +
|
||||
"INNER JOIN\n" +
|
||||
"(SELECT '01' x UNION all SELECT '02') b";
|
||||
StmtExecutor stmtExecutor11 = new StmtExecutor(ctx, sql11);
|
||||
StmtExecutor stmtExecutor11 = new StmtExecutor(connectContext, sql11);
|
||||
stmtExecutor11.execute();
|
||||
Planner planner11 = stmtExecutor11.planner();
|
||||
SetOperationNode setNode11 = (SetOperationNode)(planner11.getFragments().get(1).getPlanRoot());
|
||||
@ -280,7 +271,7 @@ public class PlannerTest {
|
||||
"(SELECT k1 from db1.tbl1 \n" +
|
||||
"UNION all \n" +
|
||||
"SELECT k1 from db1.tbl1) b;";
|
||||
StmtExecutor stmtExecutor12 = new StmtExecutor(ctx, sql12);
|
||||
StmtExecutor stmtExecutor12 = new StmtExecutor(connectContext, sql12);
|
||||
stmtExecutor12.execute();
|
||||
Planner planner12 = stmtExecutor12.planner();
|
||||
SetOperationNode setNode12 = (SetOperationNode)(planner12.getFragments().get(1).getPlanRoot());
|
||||
@ -314,7 +305,7 @@ public class PlannerTest {
|
||||
" )\n" +
|
||||
") t\n" +
|
||||
"WHERE IF(k2 IS NULL, 'ALL', k2) = 'ALL'";
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(ctx, sql1);
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
|
||||
stmtExecutor1.execute();
|
||||
Planner planner1 = stmtExecutor1.planner();
|
||||
List<PlanFragment> fragments1 = planner1.getFragments();
|
||||
@ -341,7 +332,7 @@ public class PlannerTest {
|
||||
" GROUP BY k1, k2, k3\n" +
|
||||
") t\n" +
|
||||
"WHERE IF(k2 IS NULL, 'ALL', k2) = 'ALL'";
|
||||
StmtExecutor stmtExecutor2 = new StmtExecutor(ctx, sql2);
|
||||
StmtExecutor stmtExecutor2 = new StmtExecutor(connectContext, sql2);
|
||||
stmtExecutor2.execute();
|
||||
Planner planner2 = stmtExecutor2.planner();
|
||||
List<PlanFragment> fragments2 = planner2.getFragments();
|
||||
@ -356,7 +347,7 @@ public class PlannerTest {
|
||||
"b as ( select '543' as user_id) " +
|
||||
"select user_id from a union all select user_id from b";
|
||||
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(ctx, sql1);
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
|
||||
stmtExecutor1.execute();
|
||||
Planner planner1 = stmtExecutor1.planner();
|
||||
List<PlanFragment> fragments1 = planner1.getFragments();
|
||||
@ -367,7 +358,7 @@ public class PlannerTest {
|
||||
@Test
|
||||
public void testAccessingVisibleColumnWithoutPartition() throws Exception {
|
||||
String sql = "select count(k1) from db1.tbl2";
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(ctx, sql);
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, sql);
|
||||
stmtExecutor.execute();
|
||||
Assert.assertNotNull(stmtExecutor.planner());
|
||||
}
|
||||
@ -379,7 +370,7 @@ public class PlannerTest {
|
||||
"LEFT JOIN (SELECT 1 AS line, k1, k2, k3 FROM db1.tbl3) t\n" +
|
||||
"ON t.k1 = a.k1 AND t.k3 = a.k3\n" +
|
||||
"GROUP BY a.k1, a.k3";
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(ctx, sql);
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, sql);
|
||||
stmtExecutor.execute();
|
||||
Assert.assertNotNull(stmtExecutor.planner());
|
||||
Planner planner = stmtExecutor.planner();
|
||||
@ -403,7 +394,7 @@ public class PlannerTest {
|
||||
@Test
|
||||
public void testBigintSlotRefCompareDecimalLiteral() {
|
||||
java.util.function.BiConsumer<String, String> compare = (sql1, sql2) -> {
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(ctx, sql1);
|
||||
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
|
||||
try {
|
||||
stmtExecutor1.execute();
|
||||
} catch (Exception e) {
|
||||
@ -413,7 +404,7 @@ public class PlannerTest {
|
||||
List<PlanFragment> fragments1 = planner1.getFragments();
|
||||
String plan1 = planner1.getExplainString(fragments1, new ExplainOptions(false, false));
|
||||
|
||||
StmtExecutor stmtExecutor2 = new StmtExecutor(ctx, sql2);
|
||||
StmtExecutor stmtExecutor2 = new StmtExecutor(connectContext, sql2);
|
||||
try {
|
||||
stmtExecutor2.execute();
|
||||
} catch (Exception e) {
|
||||
@ -441,12 +432,11 @@ public class PlannerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringType() throws Exception {
|
||||
public void testStringType() {
|
||||
String createTbl1 = "create table db1.tbl1(k1 string, k2 varchar(32), k3 varchar(32), k4 int) "
|
||||
+ "AGGREGATE KEY(k1, k2,k3,k4) distributed by hash(k1) buckets 3 properties('replication_num' = '1')";
|
||||
expectedEx.expect(AnalysisException.class);
|
||||
expectedEx.expectMessage("String Type should not be used in key column[k1].");
|
||||
UtFrameUtils.parseAndAnalyzeStmt(createTbl1, ctx);
|
||||
AnalysisException exception =
|
||||
assertThrows(AnalysisException.class, () -> parseAndAnalyzeStmt(createTbl1));
|
||||
assertTrue(exception.getMessage().contains("String Type should not be used in key column[k1]."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -18,34 +18,24 @@
|
||||
package org.apache.doris.utframe;
|
||||
|
||||
import org.apache.doris.alter.AlterJobV2;
|
||||
import org.apache.doris.alter.AlterJobV2.JobState;
|
||||
import org.apache.doris.analysis.AlterTableStmt;
|
||||
import org.apache.doris.analysis.CreateDbStmt;
|
||||
import org.apache.doris.analysis.CreateTableStmt;
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.catalog.Database;
|
||||
import org.apache.doris.catalog.MaterializedIndexMeta;
|
||||
import org.apache.doris.catalog.OlapTable;
|
||||
import org.apache.doris.catalog.Table;
|
||||
import org.apache.doris.common.DdlException;
|
||||
import org.apache.doris.common.FeConstants;
|
||||
import org.apache.doris.planner.OlapScanNode;
|
||||
import org.apache.doris.planner.PlanFragment;
|
||||
import org.apache.doris.planner.Planner;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.qe.StmtExecutor;
|
||||
import org.apache.doris.utframe.MockedFrontend.EnvVarNotSetException;
|
||||
import org.apache.doris.utframe.MockedFrontend.FeStartException;
|
||||
import org.apache.doris.utframe.MockedFrontend.NotInitException;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/*
|
||||
* This demo shows how to run unit test with mocked FE and BE.
|
||||
@ -55,88 +45,77 @@ import java.util.UUID;
|
||||
* 3. Make a schema change to tbl.
|
||||
* 4. send a query and get query plan
|
||||
*/
|
||||
public class DemoTest {
|
||||
public class DemoTest extends TestWithFeService {
|
||||
|
||||
// use a unique dir so that it won't be conflict with other unit test which
|
||||
// may also start a Mocked Frontend
|
||||
private static String runningDirBase = "fe";
|
||||
private static String runningDir = runningDirBase + "/mocked/DemoTest/" + UUID.randomUUID().toString() + "/";
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws EnvVarNotSetException, IOException,
|
||||
FeStartException, NotInitException, DdlException, InterruptedException {
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
FeConstants.default_scheduler_interval_millisecond = 10;
|
||||
UtFrameUtils.createDorisCluster(runningDir);
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void TearDown() {
|
||||
UtFrameUtils.cleanDorisFeDir(runningDirBase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDbAndTable() throws Exception {
|
||||
// 1. create connect context
|
||||
ConnectContext ctx = UtFrameUtils.createDefaultCtx();
|
||||
connectContext = createDefaultCtx();
|
||||
|
||||
// 2. create database db1
|
||||
String createDbStmtStr = "create database db1;";
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, ctx);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
createDatabase("db1");
|
||||
System.out.println(Catalog.getCurrentCatalog().getDbNames());
|
||||
|
||||
// 3. create table tbl1
|
||||
String createTblStmtStr = "create table db1.tbl1(k1 int) distributed by hash(k1) buckets 3 properties('replication_num' = '1');";
|
||||
CreateTableStmt createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(createTblStmtStr, ctx);
|
||||
Catalog.getCurrentCatalog().createTable(createTableStmt);
|
||||
createTable("create table db1.tbl1(k1 int) distributed by hash(k1) buckets 3 properties('replication_num' = '1');");
|
||||
|
||||
// 4. get and test the created db and table
|
||||
Database db = Catalog.getCurrentCatalog().getDbOrMetaException("default_cluster:db1");
|
||||
OlapTable tbl = db.getTableOrMetaException("tbl1", Table.TableType.OLAP);
|
||||
tbl.readLock();
|
||||
try {
|
||||
Assert.assertNotNull(tbl);
|
||||
Assertions.assertNotNull(tbl);
|
||||
System.out.println(tbl.getName());
|
||||
Assert.assertEquals("Doris", tbl.getEngine());
|
||||
Assert.assertEquals(1, tbl.getBaseSchema().size());
|
||||
Assertions.assertEquals("Doris", tbl.getEngine());
|
||||
Assertions.assertEquals(1, tbl.getBaseSchema().size());
|
||||
} finally {
|
||||
tbl.readUnlock();
|
||||
}
|
||||
|
||||
// 5. process a schema change job
|
||||
String alterStmtStr = "alter table db1.tbl1 add column k2 int default '1'";
|
||||
AlterTableStmt alterTableStmt = (AlterTableStmt) UtFrameUtils.parseAndAnalyzeStmt(alterStmtStr, ctx);
|
||||
AlterTableStmt alterTableStmt = (AlterTableStmt) parseAndAnalyzeStmt(alterStmtStr);
|
||||
Catalog.getCurrentCatalog().getAlterInstance().processAlterTable(alterTableStmt);
|
||||
|
||||
// 6. check alter job
|
||||
Map<Long, AlterJobV2> alterJobs = Catalog.getCurrentCatalog().getSchemaChangeHandler().getAlterJobsV2();
|
||||
Assert.assertEquals(1, alterJobs.size());
|
||||
Assertions.assertEquals(1, alterJobs.size());
|
||||
for (AlterJobV2 alterJobV2 : alterJobs.values()) {
|
||||
while (!alterJobV2.getJobState().isFinalState()) {
|
||||
System.out.println("alter job " + alterJobV2.getJobId() + " is running. state: " + alterJobV2.getJobState());
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
System.out.println("alter job " + alterJobV2.getJobId() + " is done. state: " + alterJobV2.getJobState());
|
||||
Assert.assertEquals(AlterJobV2.JobState.FINISHED, alterJobV2.getJobState());
|
||||
Assertions.assertEquals(JobState.FINISHED, alterJobV2.getJobState());
|
||||
}
|
||||
|
||||
OlapTable tbl1 = db.getTableOrMetaException("tbl1", Table.TableType.OLAP);
|
||||
tbl1.readLock();
|
||||
try {
|
||||
Assert.assertEquals(2, tbl1.getBaseSchema().size());
|
||||
Assertions.assertEquals(2, tbl1.getBaseSchema().size());
|
||||
String baseIndexName = tbl1.getIndexNameById(tbl.getBaseIndexId());
|
||||
Assert.assertEquals(baseIndexName, tbl1.getName());
|
||||
Assertions.assertEquals(baseIndexName, tbl1.getName());
|
||||
MaterializedIndexMeta indexMeta = tbl1.getIndexMetaByIndexId(tbl1.getBaseIndexId());
|
||||
Assert.assertNotNull(indexMeta);
|
||||
Assertions.assertNotNull(indexMeta);
|
||||
} finally {
|
||||
tbl1.readUnlock();
|
||||
}
|
||||
|
||||
// 7. query
|
||||
// TODO: we can not process real query for now. So it has to be a explain query
|
||||
String queryStr = "explain select * from db1.tbl1";
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(ctx, queryStr);
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, queryStr);
|
||||
stmtExecutor.execute();
|
||||
Planner planner = stmtExecutor.planner();
|
||||
List<PlanFragment> fragments = planner.getFragments();
|
||||
Assert.assertEquals(1, fragments.size());
|
||||
Assertions.assertEquals(1, fragments.size());
|
||||
PlanFragment fragment = fragments.get(0);
|
||||
Assert.assertTrue(fragment.getPlanRoot() instanceof OlapScanNode);
|
||||
Assert.assertEquals(0, fragment.getChildren().size());
|
||||
Assertions.assertTrue(fragment.getPlanRoot() instanceof OlapScanNode);
|
||||
Assertions.assertEquals(0, fragment.getChildren().size());
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +50,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This class is deprecated.
|
||||
* If you want to start a FE server in unit test, please let your
|
||||
* test class extend {@link TestWithFeService}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DorisAssert {
|
||||
|
||||
private ConnectContext ctx;
|
||||
|
||||
@ -0,0 +1,382 @@
|
||||
// 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.doris.utframe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import org.apache.doris.analysis.Analyzer;
|
||||
import org.apache.doris.analysis.CreateDbStmt;
|
||||
import org.apache.doris.analysis.CreateTableStmt;
|
||||
import org.apache.doris.analysis.CreateViewStmt;
|
||||
import org.apache.doris.analysis.ExplainOptions;
|
||||
import org.apache.doris.analysis.SqlParser;
|
||||
import org.apache.doris.analysis.SqlScanner;
|
||||
import org.apache.doris.analysis.StatementBase;
|
||||
import org.apache.doris.analysis.UserIdentity;
|
||||
import org.apache.doris.catalog.Catalog;
|
||||
import org.apache.doris.catalog.DiskInfo;
|
||||
import org.apache.doris.common.AnalysisException;
|
||||
import org.apache.doris.common.Config;
|
||||
import org.apache.doris.common.DdlException;
|
||||
import org.apache.doris.common.FeConstants;
|
||||
import org.apache.doris.common.util.SqlParserUtils;
|
||||
import org.apache.doris.mysql.privilege.PaloAuth;
|
||||
import org.apache.doris.planner.Planner;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.qe.QueryState;
|
||||
import org.apache.doris.qe.StmtExecutor;
|
||||
import org.apache.doris.system.Backend;
|
||||
import org.apache.doris.system.SystemInfoService;
|
||||
import org.apache.doris.thrift.TNetworkAddress;
|
||||
import org.apache.doris.utframe.MockedBackendFactory.DefaultBeThriftServiceImpl;
|
||||
import org.apache.doris.utframe.MockedBackendFactory.DefaultHeartbeatServiceImpl;
|
||||
import org.apache.doris.utframe.MockedBackendFactory.DefaultPBackendServiceImpl;
|
||||
import org.apache.doris.utframe.MockedFrontend.EnvVarNotSetException;
|
||||
import org.apache.doris.utframe.MockedFrontend.FeStartException;
|
||||
import org.apache.doris.utframe.MockedFrontend.NotInitException;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
|
||||
/**
|
||||
* This is the base class for unit class that wants to start a FE service.
|
||||
* <p>
|
||||
* Concrete test class must be derived class of {@link TestWithFeService}, {@link DemoTest} is
|
||||
* an example.
|
||||
* <p>
|
||||
* This class use {@link TestInstance} in JUnit5 to do initialization and cleanup stuff. Unlike
|
||||
* deprecated legacy combination-based implementation {@link UtFrameUtils}, we use an inherit-manner,
|
||||
* thus we could wrap common logic in this base class. It's more easy to use.
|
||||
* <p>
|
||||
* Note:
|
||||
* Unit-test method in derived classes must use the JUnit5 {@link org.junit.jupiter.api.Test}
|
||||
* annotation, rather than the old JUnit4 {@link org.junit.Test} or others.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public abstract class TestWithFeService {
|
||||
protected String runningDir =
|
||||
"fe/mocked/" + getClass().getSimpleName() + "/" + UUID.randomUUID() + "/";
|
||||
protected ConnectContext connectContext;
|
||||
|
||||
@BeforeAll
|
||||
public final void beforeAll() throws Exception {
|
||||
connectContext = createDefaultCtx();
|
||||
createDorisCluster();
|
||||
runBeforeAll();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public final void afterAll() throws Exception {
|
||||
runAfterAll();
|
||||
cleanDorisFeDir(runningDir);
|
||||
}
|
||||
|
||||
protected void runBeforeAll() throws Exception {
|
||||
}
|
||||
|
||||
protected void runAfterAll() throws Exception {
|
||||
}
|
||||
|
||||
// Help to create a mocked ConnectContext.
|
||||
protected ConnectContext createDefaultCtx() throws IOException {
|
||||
SocketChannel channel = SocketChannel.open();
|
||||
ConnectContext ctx = new ConnectContext(channel);
|
||||
ctx.setCluster(SystemInfoService.DEFAULT_CLUSTER);
|
||||
ctx.setCurrentUserIdentity(UserIdentity.ROOT);
|
||||
ctx.setQualifiedUser(PaloAuth.ROOT_USER);
|
||||
ctx.setRemoteIP("127.0.0.1");
|
||||
ctx.setCatalog(Catalog.getCurrentCatalog());
|
||||
ctx.setThreadLocalInfo();
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// Parse an origin stmt and analyze it. Return a StatementBase instance.
|
||||
protected StatementBase parseAndAnalyzeStmt(String originStmt)
|
||||
throws Exception {
|
||||
System.out.println("begin to parse stmt: " + originStmt);
|
||||
SqlScanner input =
|
||||
new SqlScanner(new StringReader(originStmt),
|
||||
connectContext.getSessionVariable().getSqlMode());
|
||||
SqlParser parser = new SqlParser(input);
|
||||
Analyzer analyzer = new Analyzer(connectContext.getCatalog(), connectContext);
|
||||
StatementBase statementBase = null;
|
||||
try {
|
||||
statementBase = SqlParserUtils.getFirstStmt(parser);
|
||||
} catch (AnalysisException e) {
|
||||
String errorMessage = parser.getErrorMsg(originStmt);
|
||||
System.err.println("parse failed: " + errorMessage);
|
||||
if (errorMessage == null) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new AnalysisException(errorMessage, e);
|
||||
}
|
||||
}
|
||||
statementBase.analyze(analyzer);
|
||||
return statementBase;
|
||||
}
|
||||
|
||||
// for analyzing multi statements
|
||||
protected List<StatementBase> parseAndAnalyzeStmts(String originStmt) throws Exception {
|
||||
System.out.println("begin to parse stmts: " + originStmt);
|
||||
SqlScanner input = new SqlScanner(new StringReader(originStmt), connectContext.getSessionVariable().getSqlMode());
|
||||
SqlParser parser = new SqlParser(input);
|
||||
Analyzer analyzer = new Analyzer(connectContext.getCatalog(), connectContext);
|
||||
List<StatementBase> statementBases = null;
|
||||
try {
|
||||
statementBases = SqlParserUtils.getMultiStmts(parser);
|
||||
} catch (AnalysisException e) {
|
||||
String errorMessage = parser.getErrorMsg(originStmt);
|
||||
System.err.println("parse failed: " + errorMessage);
|
||||
if (errorMessage == null) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new AnalysisException(errorMessage, e);
|
||||
}
|
||||
}
|
||||
for (StatementBase stmt : statementBases) {
|
||||
stmt.analyze(analyzer);
|
||||
}
|
||||
return statementBases;
|
||||
}
|
||||
|
||||
protected String generateRandomFeRunningDir(Class testSuiteClass) {
|
||||
return generateRandomFeRunningDir(testSuiteClass.getSimpleName());
|
||||
}
|
||||
|
||||
protected String generateRandomFeRunningDir(String testSuiteName) {
|
||||
return "fe" + "/mocked/" + testSuiteName + "/" + UUID.randomUUID().toString() + "/";
|
||||
}
|
||||
|
||||
protected int startFEServer(String runningDir) throws EnvVarNotSetException, IOException,
|
||||
FeStartException, NotInitException, DdlException, InterruptedException {
|
||||
// get DORIS_HOME
|
||||
String dorisHome = System.getenv("DORIS_HOME");
|
||||
if (Strings.isNullOrEmpty(dorisHome)) {
|
||||
dorisHome = Files.createTempDirectory("DORIS_HOME").toAbsolutePath().toString();
|
||||
}
|
||||
Config.plugin_dir = dorisHome + "/plugins";
|
||||
Config.custom_config_dir = dorisHome + "/conf";
|
||||
File file = new File(Config.custom_config_dir);
|
||||
if (!file.exists()) {
|
||||
file.mkdir();
|
||||
}
|
||||
|
||||
int fe_http_port = findValidPort();
|
||||
int fe_rpc_port = findValidPort();
|
||||
int fe_query_port = findValidPort();
|
||||
int fe_edit_log_port = findValidPort();
|
||||
|
||||
// start fe in "DORIS_HOME/fe/mocked/"
|
||||
MockedFrontend frontend = MockedFrontend.getInstance();
|
||||
Map<String, String> feConfMap = Maps.newHashMap();
|
||||
// set additional fe config
|
||||
feConfMap.put("http_port", String.valueOf(fe_http_port));
|
||||
feConfMap.put("rpc_port", String.valueOf(fe_rpc_port));
|
||||
feConfMap.put("query_port", String.valueOf(fe_query_port));
|
||||
feConfMap.put("edit_log_port", String.valueOf(fe_edit_log_port));
|
||||
feConfMap.put("tablet_create_timeout_second", "10");
|
||||
frontend.init(dorisHome + "/" + runningDir, feConfMap);
|
||||
frontend.start(new String[0]);
|
||||
return fe_rpc_port;
|
||||
}
|
||||
|
||||
protected void createDorisCluster()
|
||||
throws InterruptedException, NotInitException, IOException, DdlException,
|
||||
EnvVarNotSetException, FeStartException {
|
||||
createDorisCluster(runningDir, 1);
|
||||
}
|
||||
|
||||
protected void createDorisCluster(String runningDir, int backendNum)
|
||||
throws EnvVarNotSetException, IOException, FeStartException,
|
||||
NotInitException, DdlException, InterruptedException {
|
||||
int fe_rpc_port = startFEServer(runningDir);
|
||||
for (int i = 0; i < backendNum; i++) {
|
||||
createBackend("127.0.0.1", fe_rpc_port);
|
||||
// sleep to wait first heartbeat
|
||||
Thread.sleep(6000);
|
||||
}
|
||||
}
|
||||
|
||||
// Create multi backends with different host for unit test.
|
||||
// the host of BE will be "127.0.0.1", "127.0.0.2"
|
||||
protected void createDorisClusterWithMultiTag(String runningDir,
|
||||
int backendNum)
|
||||
throws EnvVarNotSetException, IOException, FeStartException, NotInitException,
|
||||
DdlException, InterruptedException {
|
||||
// set runningUnitTest to true, so that for ut, the agent task will be send to "127.0.0.1" to make cluster running well.
|
||||
FeConstants.runningUnitTest = true;
|
||||
int fe_rpc_port = startFEServer(runningDir);
|
||||
for (int i = 0; i < backendNum; i++) {
|
||||
String host = "127.0.0." + (i + 1);
|
||||
createBackend(host, fe_rpc_port);
|
||||
}
|
||||
// sleep to wait first heartbeat
|
||||
Thread.sleep(6000);
|
||||
}
|
||||
|
||||
protected void createBackend(String beHost, int fe_rpc_port)
|
||||
throws IOException, InterruptedException {
|
||||
int be_heartbeat_port = findValidPort();
|
||||
int be_thrift_port = findValidPort();
|
||||
int be_brpc_port = findValidPort();
|
||||
int be_http_port = findValidPort();
|
||||
|
||||
// start be
|
||||
MockedBackend backend = MockedBackendFactory.createBackend(beHost,
|
||||
be_heartbeat_port, be_thrift_port, be_brpc_port, be_http_port,
|
||||
new DefaultHeartbeatServiceImpl(be_thrift_port, be_http_port, be_brpc_port),
|
||||
new DefaultBeThriftServiceImpl(), new DefaultPBackendServiceImpl());
|
||||
backend.setFeAddress(new TNetworkAddress("127.0.0.1", fe_rpc_port));
|
||||
backend.start();
|
||||
|
||||
// add be
|
||||
Backend be = new Backend(Catalog.getCurrentCatalog().getNextId(), backend.getHost(), backend.getHeartbeatPort());
|
||||
Map<String, DiskInfo> disks = Maps.newHashMap();
|
||||
DiskInfo diskInfo1 = new DiskInfo("/path" + be.getId());
|
||||
diskInfo1.setTotalCapacityB(1000000);
|
||||
diskInfo1.setAvailableCapacityB(500000);
|
||||
diskInfo1.setDataUsedCapacityB(480000);
|
||||
disks.put(diskInfo1.getRootPath(), diskInfo1);
|
||||
be.setDisks(ImmutableMap.copyOf(disks));
|
||||
be.setAlive(true);
|
||||
be.setOwnerClusterName(SystemInfoService.DEFAULT_CLUSTER);
|
||||
be.setBePort(be_thrift_port);
|
||||
be.setHttpPort(be_http_port);
|
||||
be.setBrpcPort(be_brpc_port);
|
||||
Catalog.getCurrentSystemInfo().addBackend(be);
|
||||
}
|
||||
|
||||
protected void cleanDorisFeDir(String baseDir) {
|
||||
try {
|
||||
FileUtils.deleteDirectory(new File(baseDir));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected int findValidPort() {
|
||||
int port = 0;
|
||||
while (true) {
|
||||
try (ServerSocket socket = new ServerSocket(0)) {
|
||||
socket.setReuseAddress(true);
|
||||
port = socket.getLocalPort();
|
||||
try (DatagramSocket datagramSocket = new DatagramSocket(port)) {
|
||||
datagramSocket.setReuseAddress(true);
|
||||
break;
|
||||
} catch (SocketException e) {
|
||||
System.out.println("The port " + port + " is invalid and try another port.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Could not find a free TCP/IP port to start HTTP Server on");
|
||||
}
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
protected String getSQLPlanOrErrorMsg(String sql) throws Exception {
|
||||
return getSQLPlanOrErrorMsg(sql, false);
|
||||
}
|
||||
|
||||
protected String getSQLPlanOrErrorMsg(String sql, boolean isVerbose) throws Exception {
|
||||
connectContext.getState().reset();
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, sql);
|
||||
connectContext.setExecutor(stmtExecutor);
|
||||
ConnectContext.get().setExecutor(stmtExecutor);
|
||||
stmtExecutor.execute();
|
||||
if (connectContext.getState().getStateType() != QueryState.MysqlStateType.ERR) {
|
||||
Planner planner = stmtExecutor.planner();
|
||||
return planner.getExplainString(planner.getFragments(), new ExplainOptions(isVerbose, false));
|
||||
} else {
|
||||
return connectContext.getState().getErrorMessage();
|
||||
}
|
||||
}
|
||||
|
||||
protected Planner getSQLPlanner(String queryStr) throws Exception {
|
||||
connectContext.getState().reset();
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, queryStr);
|
||||
stmtExecutor.execute();
|
||||
if (connectContext.getState().getStateType() != QueryState.MysqlStateType.ERR) {
|
||||
return stmtExecutor.planner();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected StmtExecutor getSqlStmtExecutor(String queryStr) throws Exception {
|
||||
connectContext.getState().reset();
|
||||
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, queryStr);
|
||||
stmtExecutor.execute();
|
||||
if (connectContext.getState().getStateType() != QueryState.MysqlStateType.ERR) {
|
||||
return stmtExecutor;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void createDatabase(String db) throws Exception {
|
||||
String createDbStmtStr = "CREATE DATABASE " + db;
|
||||
CreateDbStmt createDbStmt = (CreateDbStmt) parseAndAnalyzeStmt(createDbStmtStr);
|
||||
Catalog.getCurrentCatalog().createDb(createDbStmt);
|
||||
}
|
||||
|
||||
protected void createTable(String sql) throws Exception {
|
||||
createTables(sql);
|
||||
}
|
||||
|
||||
protected void createTables(String... sqls) throws Exception {
|
||||
for (String sql : sqls) {
|
||||
CreateTableStmt stmt = (CreateTableStmt) parseAndAnalyzeStmt(sql);
|
||||
Catalog.getCurrentCatalog().createTable(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createView(String sql) throws Exception {
|
||||
CreateViewStmt createViewStmt = (CreateViewStmt) parseAndAnalyzeStmt(sql);
|
||||
Catalog.getCurrentCatalog().createView(createViewStmt);
|
||||
}
|
||||
|
||||
protected void assertSQLPlanOrErrorMsgContains(String sql, String expect) throws Exception {
|
||||
// Note: adding `EXPLAIN` is necessary for non-query SQL, e.g., DDL, DML, etc.
|
||||
// TODO: Use a graceful way to get explain plan string, rather than modifying the SQL string.
|
||||
Assertions.assertTrue(getSQLPlanOrErrorMsg("EXPLAIN " + sql).contains(expect));
|
||||
}
|
||||
|
||||
protected void assertSQLPlanOrErrorMsgContains(String sql, String... expects) throws Exception {
|
||||
String str = getSQLPlanOrErrorMsg("EXPLAIN " + sql);
|
||||
for (String expect : expects) {
|
||||
Assertions.assertTrue(str.contains(expect));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/**
|
||||
* This class is deprecated.
|
||||
* If you want to start a FE server in unit test, please let your test
|
||||
* class extend {@link TestWithFeService}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class UtFrameUtils {
|
||||
|
||||
// Help to create a mocked ConnectContext.
|
||||
|
||||
Reference in New Issue
Block a user