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
+    }
+}