This is an automated email from the ASF dual-hosted git repository.

amashenkov pushed a commit to branch ignite-18171
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit a1bb57677bbec17fd7ddfa4e0cee016c34fc60dc
Author: amashenkov <[email protected]>
AuthorDate: Tue Nov 22 15:24:57 2022 +0300

    Added test template.
---
 .../ignite/internal/ItNodeStartStopTest.java       | 274 +++++++++++++++++++++
 1 file changed, 274 insertions(+)

diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ItNodeStartStopTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ItNodeStartStopTest.java
new file mode 100644
index 0000000000..9bfdd00d12
--- /dev/null
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ItNodeStartStopTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.ignite.internal;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.function.Executable;
+
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItNodeStartStopTest extends BaseIgniteAbstractTest {
+    /** Work directory. */
+    @WorkDirectory
+    private static Path WORK_DIR;
+
+    /** Nodes configurations. */
+    private final Map<String, String> nodesCfg = Map.of(
+            "C", "",
+            "M", "",
+            "D", "",
+            "D2", ""
+    ); // Node name -> config.
+
+    /** Cluster nodes. */
+    private final List<String> clusterNodes = new ArrayList<>(); // TODO: 
replace with Map<String, Ignite> (nodeName->node) ?
+
+    /** Runs after each sequence. */
+    @AfterEach
+    public void after() {
+//        clusterNodes.forEach(node -> IgnitionManager.stop(node.name()));
+        clusterNodes.clear();
+    }
+
+    /**
+     * Test factory.
+     *
+     * @return Tests.
+     */
+    @TestFactory
+    public Stream<DynamicTest> factory() {
+        return new NodesStartStopGenerator(
+                this::startNode,
+                this::stopNode,
+                testExecutor(this::testStartStopNodes), // Wrap test method 
with logging executor.
+                nodesCfg.keySet(),
+                ItNodeStartStopTest::isValidNode
+        ).build();
+    }
+
+    private static boolean isValidNode(String nodeName, Set<String> grid) {
+        return (!grid.isEmpty() || "C".equals(nodeName)) // CMG node always 
starts first.
+                && (!"D2".equals(nodeName) || grid.contains("D")); // Data 
nodes are interchangeable.
+    }
+
+    private void testStartStopNodes() {
+        System.out.println("Grid state: " + String.join("", String.join(", ", 
clusterNodes)));
+    }
+
+    private void startNode(String nodeName) {
+        System.out.println("Starting node: " + nodeName);
+//        CompletableFuture<Ignite> node = IgnitionManager.start(nodeName, 
nodesCfg.get(nodeName), WORK_DIR.resolve(nodeName));
+//
+//        clusterNodes.add(node.join());
+        clusterNodes.add(nodeName);
+    }
+
+    private void stopNode(String nodeName) {
+        System.out.println("Stopping node: " + nodeName);
+//        Node node = clusterNodes.stream().filter(n -> 
nodeName.equals(n.name())).findFirst().orElseThrow();
+//        IgnitionManager.stop(nodeName);
+
+        clusterNodes.remove(nodeName);
+    }
+
+    /**
+     * Test sequence generator.
+     */
+    private Consumer<TestInfo> testExecutor(Runnable testRunnable) {
+        return (info) -> {
+            try {
+                setupBase(info, WORK_DIR);
+
+                testRunnable.run();
+
+            } finally {
+                tearDownBase(info);
+            }
+        };
+    }
+
+    /**
+     * Filter out grids that where already seen before.
+     */
+    static class VisitedFilter implements Predicate<Set<String>> {
+
+        private final Set<Set<String>> visitedGrids = new HashSet<>();
+
+        @Override
+        public boolean test(Set<String> g) {
+            return visitedGrids.add(new HashSet<>(g));
+        }
+    }
+
+    public static class NodesStartStopGenerator {
+
+        private final Consumer<String> nodeStarter;
+        private final Consumer<String> nodeStopper;
+        private final Consumer<TestInfo> testMethodBody;
+
+        private final Set<String> currentGrid = new TreeSet<>();
+        private final Predicate<Set<String>> gridFilter = new VisitedFilter(); 
// Duplicates filter.
+        private final BiPredicate<String, Set<String>> nodeFilter;
+
+        private final Set<String> gridNodes;
+
+        /**
+         * Creates test sequence generator.
+         *
+         * @param nodeStarter Function, starts node by node name.
+         * @param nodeStopper Function, stops node by node name.
+         * @param testMethodBody Test method body executor.
+         * @param nodes Node names.
+         * @param nodeFilter Node filter accepts node name to start and 
current grid state.
+         */
+        NodesStartStopGenerator(
+                Consumer<String> nodeStarter,
+                Consumer<String> nodeStopper,
+                Consumer<TestInfo> testMethodBody,
+                Set<String> nodes,
+                BiPredicate<String, Set<String>> nodeFilter
+        ) {
+            this.nodeStarter = nodeStarter;
+            this.nodeStopper = nodeStopper;
+            this.testMethodBody = testMethodBody;
+            this.nodeFilter = nodeFilter;
+            this.gridNodes = nodes;
+        }
+
+        /**
+         * Generates test sequence for all valid grid configurations that can 
be created with given nodes.
+         *
+         * @return Tests sequence.
+         */
+        public Stream<DynamicTest> build() {
+            List<Executable> actionSequence = new ArrayList<>();
+
+            generate(actionSequence::add, gridNodes);
+
+            return actionSequence.stream()
+                    .map(action -> {
+                        if (action instanceof TestExecutable) {
+                            TestExecutable namedAction = (TestExecutable) 
action;
+
+                            return 
DynamicTest.dynamicTest(namedAction.getDisplayName(), namedAction);
+                        } else {
+                            try { // Execute instantly and proceed with the 
next.
+                                action.execute();
+                            } catch (Throwable e) {
+                                fail(e);
+                            }
+
+                            return null;
+                        }
+                    })
+                    .filter(Objects::nonNull);
+        }
+
+        /** Generates tests execution sequence recursively. */
+        private void generate(Consumer<Executable> actionCollector, 
Set<String> availableNodes) {
+            for (String nodeName : availableNodes) {
+                if (!nodeFilter.test(nodeName, currentGrid)) {
+                    continue; // Skip node from adding to the current grid.
+                }
+
+                String gridStateString = 
currentGrid.stream().map(Objects::toString).collect(Collectors.joining(", "));
+                String prevGridState = '[' + gridStateString + "]";
+                String nextGridState = currentGrid.isEmpty() ? '[' + nodeName 
+ ']' : '[' + gridStateString + ", " + nodeName + ']';
+
+                actionCollector.accept(() -> nodeStarter.accept(nodeName));
+                currentGrid.add(nodeName);
+
+                actionCollector.accept(new TestExecutable(prevGridState + " -> 
" + nextGridState, testMethodBody));
+
+                if (availableNodes.size() > 1) {
+                    Set<String> otherNodes = new HashSet<>(availableNodes);
+                    otherNodes.remove(nodeName);
+                    if (gridFilter.test(otherNodes)) { // Avoid generating 
duplicate subsequences.
+                        generate(actionCollector, otherNodes);
+                    }
+                }
+
+                currentGrid.remove(nodeName);
+                actionCollector.accept(() -> nodeStopper.accept(nodeName));
+
+                if (!currentGrid.isEmpty()) {
+                    actionCollector.accept(new TestExecutable(nextGridState + 
" -> " + prevGridState, testMethodBody));
+                }
+            }
+        }
+    }
+
+    static class TestExecutable implements TestInfo, Executable {
+        private final Consumer<TestInfo> delegate;
+
+        private final String name;
+
+        TestExecutable(String name, Consumer<TestInfo> delegate) {
+            this.name = name;
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void execute() throws Throwable {
+            delegate.accept(this);
+        }
+
+        @Override
+        public String getDisplayName() {
+            return name;
+        }
+
+        @Override
+        public Set<String> getTags() {
+            return Set.of();
+        }
+
+        @Override
+        public Optional<Class<?>> getTestClass() {
+            return Optional.of(ItNodeStartStopTest.class);
+        }
+
+        @Override
+        public Optional<Method> getTestMethod() {
+            return Optional.empty();
+        }
+    }
+}

Reply via email to