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

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


The following commit(s) were added to refs/heads/main by this push:
     new 10cfeea5a9 IGNITE-18220 Implement integration tests for completers. 
Fixes #1473
10cfeea5a9 is described below

commit 10cfeea5a914a8e81a2bb221e44e0b99f3be2015
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Thu Jan 5 11:25:46 2023 +0200

    IGNITE-18220 Implement integration tests for completers. Fixes #1473
    
    Signed-off-by: Slava Koptilin <[email protected]>
---
 gradle/libs.versions.toml                          |   3 +
 modules/cli/build.gradle                           |   1 +
 modules/cli/pom.xml                                |   6 +
 .../ignite/internal/cli/IntegrationTestBase.java   |  89 +++--
 .../CliCommandTestInitializedIntegrationBase.java  |   9 +-
 ...liCommandTestNotInitializedIntegrationBase.java |   4 +
 .../repl/executor/ItIgnitePicocliCommandsTest.java | 403 +++++++++++++++++++++
 .../completer/filter/DynamicCompleterFilter.java   |  11 +-
 .../filter/NonRepeatableOptionsFilter.java         |   2 +-
 .../core/repl/executor/IgnitePicocliCommands.java  |   2 +-
 .../completer/DynamicCompleterRegistryTest.java    |   5 +-
 .../filter/DynamicCompleterFilterTest.java         |  32 +-
 parent/pom.xml                                     |   8 +
 13 files changed, 525 insertions(+), 50 deletions(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f96312bd6f..abe5b32978 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -71,6 +71,7 @@ archunit = "0.23.1"
 testkit = "1.8.1"
 openapi = "3.2.0"
 autoService = "1.0.1"
+awaitility = "4.2.0"
 
 #Tools
 pmdTool = "6.28.0"
@@ -225,3 +226,5 @@ checker-qual = { module = 
"org.checkerframework:checker-qual", version.ref = "ch
 
 auto-service = { module = "com.google.auto.service:auto-service", version.ref 
= "autoService" }
 auto-service-annotations = { module = 
"com.google.auto.service:auto-service-annotations", version.ref = "autoService" 
}
+
+awaitility = { module = "org.awaitility:awaitility", version.ref = 
"awaitility" }
diff --git a/modules/cli/build.gradle b/modules/cli/build.gradle
index 4d646194bf..d3a04e4430 100644
--- a/modules/cli/build.gradle
+++ b/modules/cli/build.gradle
@@ -89,6 +89,7 @@ dependencies {
     integrationTestImplementation libs.micronaut.junit5
     integrationTestImplementation libs.micronaut.test
     integrationTestImplementation libs.assertj.core
+    integrationTestImplementation libs.awaitility
 }
 
 ext.generatedClientDir = layout.buildDirectory.dir("swagger/client")
diff --git a/modules/cli/pom.xml b/modules/cli/pom.xml
index 9b819b1280..6c5561fe4d 100644
--- a/modules/cli/pom.xml
+++ b/modules/cli/pom.xml
@@ -232,6 +232,12 @@
             <artifactId>mockserver-junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
index c050e29ca1..f14617ae0e 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
@@ -28,6 +28,7 @@ import java.io.PrintWriter;
 import java.io.Writer;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -65,23 +66,28 @@ import org.junit.jupiter.api.extension.ExtendWith;
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 @MicronautTest(rebuildContext = true)
 public class IntegrationTestBase extends BaseIgniteAbstractTest {
-    /** Timeout should be big enough to prevent premature session expiration. 
*/
-    private static final long SESSION_IDLE_TIMEOUT = 
TimeUnit.SECONDS.toMillis(60);
-
-    private static final int DEFAULT_NODES_COUNT = 3;
-
     /** Correct ignite cluster url. */
     protected static final String NODE_URL = "http://localhost:10300";;
 
     /** Cluster nodes. */
     protected static final List<Ignite> CLUSTER_NODES = new ArrayList<>();
 
-    /** Futures that are going to be completed when all nodes are started and 
the cluster is initialized. */
-    private static List<CompletableFuture<Ignite>> futures = new ArrayList<>();
+    /** Cluster node names. */
+    protected static final List<String> CLUSTER_NODE_NAMES = new ArrayList<>();
+
+    /** Node name to its configuration map.*/
+    protected static final Map<String, String> NODE_CONFIGS = new HashMap<>();
+
+    /** Timeout should be big enough to prevent premature session expiration. 
*/
+
+    private static final long SESSION_IDLE_TIMEOUT = 
TimeUnit.SECONDS.toMillis(60);
+
+    private static final int DEFAULT_NODES_COUNT = 3;
 
     private static final IgniteLogger LOG = 
Loggers.forClass(IntegrationTestBase.class);
 
     /** Base port number. */
+
     private static final int BASE_PORT = 3344;
 
     /** Nodes bootstrap configuration pattern. */
@@ -95,7 +101,10 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
             + "  }\n"
             + "}";
 
+    /** Futures that are going to be completed when all nodes are started and 
the cluster is initialized. */
+    private static List<CompletableFuture<Ignite>> futures = new ArrayList<>();
     /** Work directory. */
+
     @WorkDirectory
     private static Path WORK_DIR;
 
@@ -105,11 +114,11 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
         int idx = 0;
 
         for (Object[] args : new Object[][]{
-            {idx++, "Igor", 10d},
-            {idx++, null, 15d},
-            {idx++, "Ilya", 15d},
-            {idx++, "Roma", 10d},
-            {idx, "Roma", 10d}
+                {idx++, "Igor", 10d},
+                {idx++, null, 15d},
+                {idx++, "Ilya", 15d},
+                {idx++, "Roma", 10d},
+                {idx, "Roma", 10d}
         }) {
             sql("INSERT INTO person(id, name, salary) VALUES (?, ?, ?)", args);
         }
@@ -158,6 +167,27 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
         return res;
     }
 
+    protected static PrintWriter output(List<Character> buffer) {
+        return new PrintWriter(new Writer() {
+            @Override
+            public void write(char[] cbuf, int off, int len) {
+                for (int i = off; i < off + len; i++) {
+                    buffer.add(cbuf[i]);
+                }
+            }
+
+            @Override
+            public void flush() {
+
+            }
+
+            @Override
+            public void close() {
+
+            }
+        });
+    }
+
     /**
      * Before all.
      *
@@ -169,9 +199,12 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
         futures = IntStream.range(0, nodes())
                 .mapToObj(i -> {
                     String nodeName = testNodeName(testInfo, i);
+                    CLUSTER_NODE_NAMES.add(nodeName);
 
                     String config = 
IgniteStringFormatter.format(NODE_BOOTSTRAP_CFG, BASE_PORT + i, 
connectNodeAddr);
 
+                    NODE_CONFIGS.put(nodeName, config);
+
                     return IgnitionManager.start(nodeName, config, 
WORK_DIR.resolve(nodeName));
                 })
                 .collect(toList());
@@ -203,6 +236,7 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
         LOG.info("Start tearDown()");
 
         CLUSTER_NODES.clear();
+        CLUSTER_NODE_NAMES.clear();
 
         List<AutoCloseable> closeables = IntStream.range(0, nodes())
                 .mapToObj(i -> testNodeName(testInfo, i))
@@ -214,6 +248,16 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
         LOG.info("End tearDown()");
     }
 
+    protected void stopNode(String nodeName) {
+        IgnitionManager.stop(nodeName);
+        CLUSTER_NODE_NAMES.remove(nodeName);
+    }
+
+    protected void startNode(String nodeName) {
+        IgnitionManager.start(nodeName, NODE_CONFIGS.get(nodeName), 
WORK_DIR.resolve(nodeName));
+        CLUSTER_NODE_NAMES.add(nodeName);
+    }
+
     /** Drops all visible tables. */
     protected void dropAllTables() {
         for (Table t : CLUSTER_NODES.get(0).tables().tables()) {
@@ -221,27 +265,6 @@ public class IntegrationTestBase extends 
BaseIgniteAbstractTest {
         }
     }
 
-    protected static PrintWriter output(List<Character> buffer) {
-        return new PrintWriter(new Writer() {
-            @Override
-            public void write(char[] cbuf, int off, int len) {
-                for (int i = off; i < off + len; i++) {
-                    buffer.add(cbuf[i]);
-                }
-            }
-
-            @Override
-            public void flush() {
-
-            }
-
-            @Override
-            public void close() {
-
-            }
-        });
-    }
-
     /**
      * Invokes before the test will start.
      *
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestInitializedIntegrationBase.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestInitializedIntegrationBase.java
index 8baceea2ec..e91423a46e 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestInitializedIntegrationBase.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestInitializedIntegrationBase.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.cli.commands;
 
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 
+import java.util.List;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.TestInfo;
 
@@ -31,10 +32,14 @@ public class CliCommandTestInitializedIntegrationBase 
extends CliCommandTestNotI
     @Override
     void beforeAll(TestInfo testInfo) {
         startNodes(testInfo);
-        super.initializeCluster(metaStorageNodeName(testInfo));
+        initializeCluster(metaStorageNodeName(testInfo));
     }
 
-    protected String metaStorageNodeName(TestInfo testInfo) {
+    protected static String metaStorageNodeName(TestInfo testInfo) {
         return testNodeName(testInfo, 0);
     }
+
+    protected static List<String> allNodeNames() {
+        return CLUSTER_NODE_NAMES;
+    }
 }
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
index 85fce2958b..b69776cd85 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
@@ -110,6 +110,10 @@ public class CliCommandTestNotInitializedIntegrationBase 
extends IntegrationTest
         exitCode = cmd.execute(args);
     }
 
+    protected CommandLine commandLine() {
+        return cmd;
+    }
+
     protected void assertExitCodeIs(int expectedExitCode) {
         assertThat(exitCode)
                 .as("Expected exit code to be: " + expectedExitCode + " but 
was " + exitCode)
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
new file mode 100644
index 0000000000..86489fb4e5
--- /dev/null
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/core/repl/executor/ItIgnitePicocliCommandsTest.java
@@ -0,0 +1,403 @@
+/*
+ * 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.cli.core.repl.executor;
+
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.not;
+import static org.mockito.Mockito.when;
+
+import jakarta.inject.Inject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import 
org.apache.ignite.internal.cli.commands.CliCommandTestInitializedIntegrationBase;
+import org.apache.ignite.internal.cli.commands.TopLevelCliReplCommand;
+import org.apache.ignite.internal.cli.core.repl.Session;
+import org.apache.ignite.internal.cli.core.repl.SessionInfo;
+import 
org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterActivationPoint;
+import 
org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterRegistry;
+import 
org.apache.ignite.internal.cli.core.repl.completer.filter.CompleterFilter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.filter.DynamicCompleterFilter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.filter.NonRepeatableOptionsFilter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.filter.ShortOptionsFilter;
+import org.jline.reader.Candidate;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+import org.jline.reader.impl.DefaultParser;
+import org.jline.reader.impl.completer.SystemCompleter;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+
+/** Integration test for all completions in interactive mode. */
+public class ItIgnitePicocliCommandsTest extends 
CliCommandTestInitializedIntegrationBase {
+
+    private static final String DEFAULT_REST_URL = "http://localhost:10300";;
+
+    @Inject
+    DynamicCompleterRegistry dynamicCompleterRegistry;
+
+    @Inject
+    DynamicCompleterActivationPoint dynamicCompleterActivationPoint;
+
+    @Inject
+    DynamicCompleterFilter dynamicCompleterFilter;
+
+    @Inject
+    Session session;
+
+    SystemCompleter completer;
+
+    LineReader lineReader;
+
+    @Override
+    protected Class<?> getCommandClass() {
+        return TopLevelCliReplCommand.class;
+    }
+
+    @BeforeEach
+    @Override
+    public void setUp(TestInfo testInfo) throws Exception {
+        super.setUp(testInfo);
+        setupSystemCompleter();
+    }
+
+    private void setupSystemCompleter() {
+        
dynamicCompleterActivationPoint.activateDynamicCompleter(dynamicCompleterRegistry);
+
+        List<CompleterFilter> filters = List.of(
+                dynamicCompleterFilter,
+                new ShortOptionsFilter(),
+                new NonRepeatableOptionsFilter(commandLine().getCommandSpec())
+        );
+
+        IgnitePicocliCommands commandRegistry = new 
IgnitePicocliCommands(commandLine(), dynamicCompleterRegistry, filters);
+
+        // This completer is used by jline to suggest all completions
+        completer = commandRegistry.compileCompleters();
+        completer.compile();
+
+        lineReader = Mockito.mock(LineReader.class);
+        when(lineReader.getParser()).thenReturn(new DefaultParser());
+    }
+
+    private Stream<Arguments> helpAndVerboseAreNotCompletedSource() {
+        return Stream.of(
+                words(""), words("-"), words(" -"),
+                words("node"),
+                words("node", ""),
+                words("node", "config"),
+                words("node", "config", ""),
+                words("node", "config", "show"),
+                words("node", "config", "show", ""),
+                words("node", "config", "show", "--node-name", "name"),
+                words("node", "config", "show", "--node-name", "name", "")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("helpAndVerboseAreNotCompletedSource")
+    @DisplayName("--help and --verbose are not suggested")
+    void helpAndVerboseAreNotCompleted(ParsedLine givenParsedLine) {
+        // When
+        List<String> suggestions = complete(givenParsedLine);
+
+        // Then
+        assertThat(
+                "For given parsed words: " + givenParsedLine.words(),
+                suggestions,
+                not(hasItems("--help", "--verbose"))
+        );
+    }
+
+    private Stream<Arguments> helpAndVerboseCompletedSource() {
+        return Stream.of(
+                words("node", "config", "show", "-"),
+                words("node", "config", "show", "--"),
+                words("node", "status", "-"),
+                words("node", "status", "--"),
+                words("node", "config", "show", "--node-name", "name", "-")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("helpAndVerboseCompletedSource")
+    @DisplayName("--help and --verbose are suggested if '-' or '--' typed")
+    void helpAndVerboseAreCompleted(ParsedLine givenParsedLine) {
+        // When
+        List<String> suggestions = complete(givenParsedLine);
+
+        // Then
+        assertThat(
+                "For given parsed words: " + givenParsedLine.words(),
+                suggestions,
+                hasItems("--verbose", "--help")
+        );
+    }
+
+    private Stream<Arguments> helpCompletedSource() {
+        return Stream.of(
+                words("node", "-"),
+                words("node", "", "-"),
+                words("node", "config", "-"),
+                words("node", "config", "--"),
+                words("node", "config", " ", "-")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("helpCompletedSource")
+    @DisplayName("--help suggested if '-' or '--' typed for keywords that is 
not complete commands")
+    void helpSuggested(ParsedLine givenParsedLine) {
+        // When
+        List<String> suggestions = complete(givenParsedLine);
+
+        // Then
+        assertThat(
+                "For given parsed words: " + givenParsedLine.words(),
+                suggestions,
+                hasItem("--help")
+        );
+    }
+
+    private Stream<Arguments> nodeConfigShowSuggestedSource() {
+        return Stream.of(
+                words("node", "config", "show", ""),
+                words("node", "config", "show", " --node-name", "nodeName", 
""),
+                words("node", "config", "show", " --verbose", ""),
+                words("node", "config", "show", " -v", "")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("nodeConfigShowSuggestedSource")
+    @DisplayName("node config show selector parameters suggested")
+    void nodeConfigShowSuggested(ParsedLine givenParsedLine) {
+        // Given
+        connected();
+
+        // wait for lazy init of node config completer
+        await("For given parsed words: " + givenParsedLine.words()).until(
+                () -> complete(givenParsedLine),
+                containsInAnyOrder("rest", "compute", "clientConnector", 
"raft", "network")
+        );
+    }
+
+    private void connected() {
+        session.connect(new SessionInfo(DEFAULT_REST_URL, null, null));
+    }
+
+    private Stream<Arguments> nodeConfigUpdateSuggestedSource() {
+        return Stream.of(
+                words("node", "config", "update", ""),
+                words("node", "config", "update", " --node-name", "nodeName", 
""),
+                words("node", "config", "update", " --verbose", ""),
+                words("node", "config", "update", " -v", "")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("nodeConfigUpdateSuggestedSource")
+    @DisplayName("node config update selector parameters suggested")
+    void nodeConfigUpdateSuggested(ParsedLine givenParsedLine) {
+        // Given
+        connected();
+
+        // wait for lazy init of node config completer
+        await("For given parsed words: " + givenParsedLine.words()).until(
+                () -> complete(givenParsedLine),
+                containsInAnyOrder("rest", "clientConnector", "network")
+        );
+    }
+
+    private Stream<Arguments> clusterConfigShowSuggestedSource() {
+        return Stream.of(
+                words("cluster", "config", "show", ""),
+                words("cluster", "config", "show", " --node-name", "nodeName", 
""),
+                words("cluster", "config", "show", " --verbose", ""),
+                words("cluster", "config", "show", " -v", "")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("clusterConfigShowSuggestedSource")
+    @DisplayName("cluster config selector parameters suggested")
+    void clusterConfigShowSuggested(ParsedLine givenParsedLine) {
+        // Given
+        connected();
+
+        // wait for lazy init of cluster config completer
+        await("For given parsed words: " + givenParsedLine.words()).until(
+                () -> complete(givenParsedLine),
+                containsInAnyOrder("aimem", "aipersist", "metrics", "rocksDb", 
"table", "zone")
+        );
+    }
+
+    private Stream<Arguments> clusterConfigUpdateSuggestedSource() {
+        return Stream.of(
+                words("cluster", "config", "update", ""),
+                words("cluster", "config", "update", " --node-name", 
"nodeName", ""),
+                words("cluster", "config", "update", " --verbose", ""),
+                words("cluster", "config", "update", " -v", "")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("clusterConfigUpdateSuggestedSource")
+    @DisplayName("cluster config selector parameters suggested")
+    void clusterConfigUpdateSuggested(ParsedLine givenParsedLine) {
+        // Given
+        connected();
+
+        // wait for lazy init of cluster config completer
+        await("For given parsed words: " + givenParsedLine.words()).until(
+                () -> complete(givenParsedLine),
+                containsInAnyOrder("aimem", "aipersist", "metrics", "rocksDb", 
"table", "zone")
+        );
+    }
+
+
+    private Stream<Arguments> nodeNamesSource() {
+        return Stream.of(
+                words("cluster", "config", "show", "--node-name", ""),
+                words("cluster", "config", "update", "--node-name", ""),
+                words("cluster", "status", "--node-name", ""),
+                words("cluster", "init", "--node-name", ""),
+                words("cluster", "init", "--cmg-node", ""),
+                words("cluster", "init", "--meta-storage-node", ""),
+                words("node", "config", "show", "--node-name", ""),
+                words("node", "config", "show", "--verbose", "--node-name", 
""),
+                words("node", "config", "update", "--node-name", ""),
+                words("node", "status", "--node-name", ""),
+                words("node", "version", "--node-name", ""),
+                words("node", "metric", "list", "--node-name", "")
+        ).map(this::named).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("nodeNamesSource")
+    @DisplayName("node names suggested after --node-name option")
+    void nodeNameSuggested(ParsedLine givenParsedLine) {
+        // Given
+        connected();
+        // And the first update is fetched
+        await().until(() -> nodeNameRegistry.names(), not(empty()));
+
+        // Then
+        assertThat(
+                "For given parsed words: " + givenParsedLine.words(),
+                complete(givenParsedLine),
+                containsInAnyOrder(allNodeNames().toArray())
+        );
+    }
+
+    @Test
+    @DisplayName("start/stop node affects --node-name suggestions")
+    void startStopNodeWhenCompleteNodeName() {
+        // Given
+        var igniteNodeName = allNodeNames().get(1);
+        // And
+        var givenParsedLine = words("node", "status", "--node-name", "");
+        // And
+        assertThat(nodeNameRegistry.names(), empty());
+
+        // Then
+        assertThat(complete(givenParsedLine), not(contains(igniteNodeName)));
+
+        // When
+        connected();
+        // And the first update is fetched
+        await().until(() -> nodeNameRegistry.names(), not(empty()));
+
+        // Then
+        assertThat(complete(givenParsedLine), 
containsInAnyOrder(allNodeNames().toArray()));
+
+        // When stop one node
+        stopNode(igniteNodeName);
+        var actualNodeNames = allNodeNames();
+        actualNodeNames.remove(igniteNodeName);
+
+        // Then node name suggestions does not contain the stopped node
+        await().until(() -> complete(givenParsedLine), 
containsInAnyOrder(actualNodeNames.toArray()));
+
+        // When start the node again
+        startNode(igniteNodeName);
+
+        // Then node name comes back to suggestions
+        await().until(() -> complete(givenParsedLine), 
containsInAnyOrder(allNodeNames().toArray()));
+    }
+
+    List<String> complete(ParsedLine typedWords) {
+        List<Candidate> candidates = new ArrayList<>();
+        completer.complete(lineReader, typedWords, candidates);
+
+        return 
candidates.stream().map(Candidate::displ).collect(Collectors.toList());
+    }
+
+    Named<ParsedLine> named(ParsedLine parsedLine) {
+        return Named.of("cmd: " + String.join(" ", parsedLine.words()), 
parsedLine);
+    }
+
+    ParsedLine words(String... words) {
+        return new ParsedLine() {
+            @Override
+            public String word() {
+                return null;
+            }
+
+            @Override
+            public int wordCursor() {
+                return words.length - 1;
+            }
+
+            @Override
+            public int wordIndex() {
+                return words.length;
+            }
+
+            @Override
+            public List<String> words() {
+                return Arrays.stream(words).collect(Collectors.toList());
+            }
+
+            @Override
+            public String line() {
+                return null;
+            }
+
+            @Override
+            public int cursor() {
+                return 0;
+            }
+        };
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilter.java
index 4674529e2a..410b9ae5e6 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilter.java
@@ -49,6 +49,10 @@ public class DynamicCompleterFilter implements 
CompleterFilter {
 
     @Override
     public String[] filter(String[] words, String[] candidates) {
+        if (optionTyped(words)) {
+            return candidates;
+        }
+
         List<String> notOptionsCandidates = Arrays.stream(candidates)
                 .filter(candidate -> !candidate.startsWith("-"))
                 .collect(Collectors.toList());
@@ -59,13 +63,12 @@ public class DynamicCompleterFilter implements 
CompleterFilter {
 
         return Arrays.stream(candidates)
                 .filter(candidate -> filterClusterUrl(words, candidate))
-                .filter(candidate -> filterCommonOptions(words, candidate))
+                .filter(this::filterCommonOptions)
                 .toArray(String[]::new);
     }
 
-    private boolean filterCommonOptions(String[] words, String candidate) {
-        return optionTyped(words)
-                || !(HELP_OPTION.equals(candidate)
+    private boolean filterCommonOptions(String candidate) {
+        return !(HELP_OPTION.equals(candidate)
                 || HELP_OPTION_SHORT.equals(candidate)
                 || VERBOSE_OPTION_SHORT.equals(candidate)
                 || VERBOSE_OPTION.equals(candidate));
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/NonRepeatableOptionsFilter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/NonRepeatableOptionsFilter.java
index a5011cbf97..fd0648d11c 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/NonRepeatableOptionsFilter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/filter/NonRepeatableOptionsFilter.java
@@ -52,7 +52,7 @@ public class NonRepeatableOptionsFilter implements 
CompleterFilter {
     private CommandSpec findCommandSpec(String[] words) {
         int cursor = 0;
         CommandSpec commandSpec = topCommandSpec;
-        while (commandSpec.subcommands().containsKey(words[cursor])) {
+        while (cursor < words.length && 
commandSpec.subcommands().containsKey(words[cursor])) {
             commandSpec = 
commandSpec.subcommands().get(words[cursor]).getCommandSpec();
             cursor++;
         }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
index e7097097ec..d31bd5d23e 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
@@ -192,7 +192,7 @@ public class IgnitePicocliCommands implements 
CommandRegistry {
 
             // let picocli generate completion candidates for the token where 
the cursor is at
             String[] words = commandLine.words().toArray(new String[0]);
-            List<CharSequence> cs = new ArrayList<CharSequence>();
+            List<CharSequence> cs = new ArrayList<>();
             AutoComplete.complete(cmd.getCommandSpec(),
                     words,
                     commandLine.wordIndex(),
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
index a25c60f055..84659f2a99 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
@@ -268,7 +268,6 @@ class DynamicCompleterRegistryTest {
         // And exclusiveEnableOption is registered last
         registry.register(
                 CompleterConf.builder()
-                        .command("node", "config", "show")
                         .enableOptions("-n")
                         .exclusiveEnableOptions().build(),
                 words -> completer1
@@ -279,6 +278,10 @@ class DynamicCompleterRegistryTest {
                 registry.findCompleters(words("node", "config", "show", "-n", 
"nod")),
                 both(hasSize(1)).and(containsInAnyOrder(completer1))
         );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
         assertThat(
                 registry.findCompleters(words("node", "config", "show", "-n")),
                 both(hasSize(1)).and(containsInAnyOrder(completer1))
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilterTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilterTest.java
index c6e879a900..39a4fc3469 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilterTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/filter/DynamicCompleterFilterTest.java
@@ -33,9 +33,9 @@ class DynamicCompleterFilterTest {
     @Test
     void filtersHelp() {
         // Given
-        String[] words = new String[]{"cluster", "config", "show", ""};
+        String[] words = {"cluster", "config", "show", ""};
         // And completion candidates
-        String[] candidates = new String[]{"--cluster-endpoint-url", "--help", 
"-h"};
+        String[] candidates = {"--cluster-endpoint-url", "--help", "-h"};
         // And user is not connected to the cluster
         Session session = notConnected();
 
@@ -61,9 +61,9 @@ class DynamicCompleterFilterTest {
     @Test
     void doesNotFilterHelpIfOptionIsTyped() {
         // Given typed words that end with "-"
-        String[] words = new String[]{"cluster", "config", "show", "-"};
+        String[] words = {"cluster", "config", "show", "-"};
         // And completion candidates
-        String[] candidates = new String[]{"--cluster-endpoint-url", "--help", 
"-h"};
+        String[] candidates = {"--cluster-endpoint-url", "--help", "-h"};
         // And user is not connected to the cluster
         Session session = notConnected();
 
@@ -77,9 +77,9 @@ class DynamicCompleterFilterTest {
     @Test
     void filtersClusterUrlWhenConnected() {
         // Given typed words that end with "-"
-        String[] words = new String[]{"cluster", "config", "show", ""};
+        String[] words = {"cluster", "config", "show", ""};
         // And completion candidates
-        String[] candidates = new String[]{"--cluster-endpoint-url", "--help", 
"-h"};
+        String[] candidates = {"--cluster-endpoint-url", "--help", "-h"};
         // And
         Session session = connected();
 
@@ -93,9 +93,9 @@ class DynamicCompleterFilterTest {
     @Test
     void doesNotFilterHelpIfOptionIsTypedAndConnected() {
         // Given typed words that end with "-"
-        String[] words = new String[]{"cluster", "config", "show", "-"};
+        String[] words = {"cluster", "config", "show", "-"};
         // And completion candidates
-        String[] candidates = new String[]{"--cluster-endpoint-url", "--help", 
"-h"};
+        String[] candidates = {"--cluster-endpoint-url", "--help", "-h"};
         // And
         Session session = connected();
 
@@ -105,4 +105,20 @@ class DynamicCompleterFilterTest {
         // Then help is NOT filtered out
         assertThat(asList(filtered), 
containsInAnyOrder("--cluster-endpoint-url", "--help", "-h"));
     }
+
+    @Test
+    void doesNotFilterHelpForPartialCommands() {
+        // Given
+        String[] words = {"cluster", "-"};
+        // And completion candidates that contains not only option candidates 
but subcommands too
+        String[] candidates = {"--help", "-h", "--verbose", "-v", "config", 
"init"};
+        // And
+        Session session = connected();
+
+        // When
+        String[] filtered = new DynamicCompleterFilter(session).filter(words, 
candidates);
+
+        // Then help is NOT filtered out
+        assertThat(asList(filtered), containsInAnyOrder("--help", "-h", 
"--verbose", "-v",  "config", "init"));
+    }
 }
diff --git a/parent/pom.xml b/parent/pom.xml
index 9c36dc78ea..27260a905c 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -53,6 +53,7 @@
         <!-- Dependencies versions -->
         <assertj-core.version>3.22.0</assertj-core.version>
         <asm.framework.version>9.0</asm.framework.version>
+        <awaitility.version>4.2.0</awaitility.version>
         <compile.testing.library.version>0.19</compile.testing.library.version>
         <fliptables.version>1.1.0</fliptables.version>
         <jackson.version>2.13.1</jackson.version>
@@ -800,6 +801,13 @@
                 <scope>test</scope>
             </dependency>
 
+            <dependency>
+                <groupId>org.awaitility</groupId>
+                <artifactId>awaitility</artifactId>
+                <version>${awaitility.version}</version>
+                <scope>test</scope>
+            </dependency>
+
             <dependency>
                 <groupId>com.google.testing.compile</groupId>
                 <artifactId>compile-testing</artifactId>


Reply via email to