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>