You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2021/02/03 04:51:49 UTC
[shardingsphere] branch master updated: Po/it parallel by scenario
(#9283)
This is an automated email from the ASF dual-hosted git repository.
zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new e163053 Po/it parallel by scenario (#9283)
e163053 is described below
commit e163053cf519a273096c74f1b1817f6e1a7a42cb
Author: AlphaPo <ju...@163.com>
AuthorDate: Wed Feb 3 12:51:28 2021 +0800
Po/it parallel by scenario (#9283)
* merge untracked file
* Support for parallel testing of integration tests by scenario
Fixed integration test BatchDMLIT case thread safety issue
---
.../test/integration/engine/it/BaseIT.java | 6 +-
.../test/integration/engine/it/BatchIT.java | 2 +-
.../ITBlockJUnit4ClassRunnerWithParameters.java | 159 +++++++++++++++++++++
.../engine/junit/ITRunnerScheduler.java | 122 ++++++++++++++++
.../junit/ITRunnerWithParametersFactory.java | 42 ++++++
.../engine/junit/ParallelParameterized.java | 38 +++++
6 files changed, 366 insertions(+), 3 deletions(-)
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BaseIT.java b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BaseIT.java
index 9196c00..b64b88e 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BaseIT.java
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BaseIT.java
@@ -22,6 +22,8 @@ import lombok.Getter;
import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
import org.apache.shardingsphere.infra.database.type.DatabaseType;
+import org.apache.shardingsphere.test.integration.engine.junit.ITRunnerWithParametersFactory;
+import org.apache.shardingsphere.test.integration.engine.junit.ParallelParameterized;
import org.apache.shardingsphere.test.integration.env.EnvironmentPath;
import org.apache.shardingsphere.test.integration.env.IntegrationTestEnvironment;
import org.apache.shardingsphere.test.integration.env.datasource.builder.ActualDataSourceBuilder;
@@ -30,7 +32,6 @@ import org.apache.shardingsphere.test.integration.env.database.DatabaseEnvironme
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import javax.sql.DataSource;
import javax.xml.bind.JAXBException;
@@ -40,7 +41,8 @@ import java.sql.SQLException;
import java.util.Map;
import java.util.TimeZone;
-@RunWith(Parameterized.class)
+@RunWith(ParallelParameterized.class)
+@ParallelParameterized.UseParametersRunnerFactory(ITRunnerWithParametersFactory.class)
@Getter(AccessLevel.PROTECTED)
public abstract class BaseIT {
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BatchIT.java b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BatchIT.java
index ea85d8e..1727129 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BatchIT.java
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/it/BatchIT.java
@@ -57,7 +57,7 @@ import static org.junit.Assert.assertThat;
@Getter(AccessLevel.PROTECTED)
public abstract class BatchIT extends BaseIT {
- private static DataSetEnvironmentManager dataSetEnvironmentManager;
+ private DataSetEnvironmentManager dataSetEnvironmentManager;
private final IntegrationTestCaseContext testCaseContext;
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITBlockJUnit4ClassRunnerWithParameters.java b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITBlockJUnit4ClassRunnerWithParameters.java
new file mode 100644
index 0000000..78c56e5
--- /dev/null
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITBlockJUnit4ClassRunnerWithParameters.java
@@ -0,0 +1,159 @@
+/*
+ * 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.shardingsphere.test.integration.engine.junit;
+
+import lombok.Getter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.junit.runners.parameterized.TestWithParameters;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.List;
+
+/**
+ * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be
+ * injected via constructor or into annotated fields.
+ */
+public final class ITBlockJUnit4ClassRunnerWithParameters extends BlockJUnit4ClassRunner {
+
+ @Getter
+ private final Object[] parameters;
+
+ private final String name;
+
+ public ITBlockJUnit4ClassRunnerWithParameters(final TestWithParameters test) throws InitializationError {
+ super(test.getTestClass().getJavaClass());
+ parameters = test.getParameters().toArray(new Object[test.getParameters().size()]);
+ name = test.getName();
+ }
+
+ @Override
+ public Object createTest() throws Exception {
+ if (fieldsAreAnnotated()) {
+ return createTestUsingFieldInjection();
+ } else {
+ return createTestUsingConstructorInjection();
+ }
+ }
+
+ private Object createTestUsingConstructorInjection() throws Exception {
+ return getTestClass().getOnlyConstructor().newInstance(parameters);
+ }
+
+ private Object createTestUsingFieldInjection() throws Exception {
+ List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
+ if (annotatedFieldsByParameter.size() != parameters.length) {
+ throw new Exception(
+ "Wrong number of parameters and @Parameter fields."
+ + " @Parameter fields counted: "
+ + annotatedFieldsByParameter.size()
+ + ", available parameters: " + parameters.length
+ + ".");
+ }
+ Object testClassInstance = getTestClass().getJavaClass().newInstance();
+ for (FrameworkField each : annotatedFieldsByParameter) {
+ Field field = each.getField();
+ Parameter annotation = field.getAnnotation(Parameter.class);
+ int index = annotation.value();
+ try {
+ field.set(testClassInstance, parameters[index]);
+ } catch (IllegalArgumentException iare) {
+ throw new Exception(getTestClass().getName()
+ + ": Trying to set " + field.getName()
+ + " with the value " + parameters[index]
+ + " that is not the right type ("
+ + parameters[index].getClass().getSimpleName()
+ + " instead of " + field.getType().getSimpleName()
+ + ").", iare);
+ }
+ }
+ return testClassInstance;
+ }
+
+ @Override
+ protected String getName() {
+ return name;
+ }
+
+ @Override
+ protected String testName(final FrameworkMethod method) {
+ return method.getName() + getName();
+ }
+
+ @Override
+ protected void validateConstructor(final List<Throwable> errors) {
+ validateOnlyOneConstructor(errors);
+ if (fieldsAreAnnotated()) {
+ validateZeroArgConstructor(errors);
+ }
+ }
+
+ @Override
+ protected void validateFields(final List<Throwable> errors) {
+ super.validateFields(errors);
+ if (fieldsAreAnnotated()) {
+ List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
+ int[] usedIndices = new int[annotatedFieldsByParameter.size()];
+ for (FrameworkField each : annotatedFieldsByParameter) {
+ int index = each.getField().getAnnotation(Parameter.class)
+ .value();
+ if (index < 0 || index > annotatedFieldsByParameter.size() - 1) {
+ errors.add(new Exception("Invalid @Parameter value: "
+ + index + ". @Parameter fields counted: "
+ + annotatedFieldsByParameter.size()
+ + ". Please use an index between 0 and "
+ + (annotatedFieldsByParameter.size() - 1) + "."));
+ } else {
+ usedIndices[index]++;
+ }
+ }
+ for (int index = 0; index < usedIndices.length; index++) {
+ int numberOfUse = usedIndices[index];
+ if (numberOfUse == 0) {
+ errors.add(new Exception("@Parameter(" + index + ") is never used."));
+ } else if (numberOfUse > 1) {
+ errors.add(new Exception("@Parameter(" + index + ") is used more than once (" + numberOfUse + ")."));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected Statement classBlock(final RunNotifier notifier) {
+ return childrenInvoker(notifier);
+ }
+
+ @Override
+ protected Annotation[] getRunnerAnnotations() {
+ return new Annotation[0];
+ }
+
+ private List<FrameworkField> getAnnotatedFieldsByParameter() {
+ return getTestClass().getAnnotatedFields(Parameter.class);
+ }
+
+ private boolean fieldsAreAnnotated() {
+ return !getAnnotatedFieldsByParameter().isEmpty();
+ }
+}
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITRunnerScheduler.java b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITRunnerScheduler.java
new file mode 100644
index 0000000..1327b3e
--- /dev/null
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITRunnerScheduler.java
@@ -0,0 +1,122 @@
+/*
+ * 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.shardingsphere.test.integration.engine.junit;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.infra.database.type.DatabaseType;
+import org.apache.shardingsphere.test.integration.env.IntegrationTestEnvironment;
+import org.junit.runners.model.RunnerScheduler;
+import org.junit.runners.model.TestClass;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Represents a strategy for scheduling when individual test methods
+ * should be run (in serial or parallel).
+ * <p>
+ * WARNING: still experimental, may go away.
+ * </p>
+ */
+@Slf4j
+public class ITRunnerScheduler implements RunnerScheduler {
+
+ private static final String DEFAULT_EXECUTOR_KEY = "default";
+
+ private final Map<String, ExecutorService> executors = new HashMap<>();
+
+ private final Map<String, AtomicInteger> executorCounter = new HashMap<>();
+
+ public ITRunnerScheduler() {
+ Collection<String> adapters = IntegrationTestEnvironment.getInstance().getAdapters();
+ Collection<String> scenarios = IntegrationTestEnvironment.getInstance().getScenarios();
+ Set<DatabaseType> databaseTypes = IntegrationTestEnvironment.getInstance().getDatabaseEnvironments().keySet();
+
+ executors.put(DEFAULT_EXECUTOR_KEY, getExecutorService(DEFAULT_EXECUTOR_KEY));
+ executorCounter.put(DEFAULT_EXECUTOR_KEY, new AtomicInteger(0));
+ for (String each : adapters) {
+ for (String scenario : scenarios) {
+ for (DatabaseType databaseType : databaseTypes) {
+ String executorServiceKey = getExecutorServiceKey(each, scenario, databaseType.getName());
+ executors.put(executorServiceKey, getExecutorService(executorServiceKey));
+ executorCounter.put(executorServiceKey, new AtomicInteger(0));
+ }
+ }
+ }
+ }
+
+ @SneakyThrows
+ @Override
+ public void schedule(final Runnable childStatement) {
+ // TODO Gets the parameters of the Runnable closure
+ Field field = childStatement.getClass().getDeclaredField("val$each");
+ field.setAccessible(true);
+ ITBlockJUnit4ClassRunnerWithParameters runnerWithParameters = (ITBlockJUnit4ClassRunnerWithParameters) field.get(childStatement);
+ String executorServiceKey = getExecutorServiceKey(runnerWithParameters.getParameters(), runnerWithParameters.getTestClass());
+ executorCounter.get(executorServiceKey).incrementAndGet();
+ executors.get(executorServiceKey).submit(childStatement);
+ }
+
+ private String getExecutorServiceKey(final String adapter, final String scenario, final String databaseTypeName) {
+ return String.join("_", adapter, scenario, databaseTypeName);
+ }
+
+ private String getExecutorServiceKey(final Object[] parameters, final TestClass testClass) {
+ Class<?> realTestClass = testClass.getJavaClass();
+ if (realTestClass == null) {
+ return DEFAULT_EXECUTOR_KEY;
+ }
+ int parametersLength = parameters.length;
+ if (parametersLength == 7) {
+ return String.join("_", String.valueOf(parameters[2]), String.valueOf(parameters[3]), String.valueOf(parameters[4]));
+ } else if (parametersLength == 5) {
+ return String.join("_", String.valueOf(parameters[1]), String.valueOf(parameters[2]), String.valueOf(parameters[3]));
+ }
+ return DEFAULT_EXECUTOR_KEY;
+ }
+
+ @Override
+ public void finished() {
+ for (Map.Entry<String, AtomicInteger> each : executorCounter.entrySet()) {
+ log.info(String.join(" -> ", each.getKey(), String.valueOf(each.getValue().get())));
+ }
+ executors.values().forEach(executorService -> {
+ try {
+ executorService.shutdown();
+ executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace(System.err);
+ }
+ });
+ }
+
+ private ExecutorService getExecutorService(final String executorServiceKey) {
+ return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setNameFormat("ITRunnerScheduler-" + executorServiceKey + "-pool-%d").build());
+ }
+}
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITRunnerWithParametersFactory.java b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITRunnerWithParametersFactory.java
new file mode 100644
index 0000000..740d7ef
--- /dev/null
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ITRunnerWithParametersFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.shardingsphere.test.integration.engine.junit;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.parameterized.ParametersRunnerFactory;
+import org.junit.runners.parameterized.TestWithParameters;
+
+/**
+ * A {@code ParameterizedRunnerFactory} creates a runner for a single
+ * {@link TestWithParameters}.
+ */
+public final class ITRunnerWithParametersFactory implements ParametersRunnerFactory {
+
+ /**
+ * Returns a runner for the specified {@link TestWithParameters}.
+ *
+ * @param test test with parameters
+ * @return runner runner
+ * @throws InitializationError
+ * if the runner could not be created.
+ */
+ public Runner createRunnerForTestWithParameters(final TestWithParameters test) throws InitializationError {
+ return new ITBlockJUnit4ClassRunnerWithParameters(test);
+ }
+}
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ParallelParameterized.java b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ParallelParameterized.java
new file mode 100644
index 0000000..de947d0
--- /dev/null
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/java/org/apache/shardingsphere/test/integration/engine/junit/ParallelParameterized.java
@@ -0,0 +1,38 @@
+/*
+ * 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.shardingsphere.test.integration.engine.junit;
+
+import org.junit.runners.Parameterized;
+
+/**
+ * Parallel parameterized by parameter.
+ */
+public final class ParallelParameterized extends Parameterized {
+
+ /**
+ * Only called reflectively. Do not use programmatically.
+ *
+ * @param klass root of the suite
+ */
+ //CHECKSTYLE:OFF
+ public ParallelParameterized(final Class<?> klass) throws Throwable {
+ super(klass);
+ setScheduler(new ITRunnerScheduler());
+ //CHECKSTYLE:ON
+ }
+}