From e0681feac155509301f3c5c6423bc7e496cac388 Mon Sep 17 00:00:00 2001 From: Yubi Lee Date: Sun, 29 May 2022 22:23:52 +0900 Subject: [PATCH 1/3] Capturing CTRL+D (EOF) to exit the shell --- .../shell/jline/InteractiveShellRunner.java | 4 ++ .../jline/InteractiveShellRunnerTests.java | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellRunner.java index c292f5a95..cabdfaff5 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellRunner.java @@ -16,6 +16,7 @@ package org.springframework.shell.jline; +import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; import org.jline.reader.UserInterruptException; import org.jline.utils.AttributedString; @@ -101,6 +102,9 @@ public Input readInput() { return Input.EMPTY; } } + catch (EndOfFileException e) { + throw new ExitRequest(1); + } return new ParsedLineInput(lineReader.getParsedLine()); } } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java new file mode 100644 index 000000000..4911156ec --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java @@ -0,0 +1,52 @@ +package org.springframework.shell.jline; + +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.AttributedString; +import org.jline.utils.AttributedStyle; +import org.junit.jupiter.api.Test; +import org.springframework.shell.ExitRequest; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +public class InteractiveShellRunnerTests { + + + public PromptProvider dummyPromptProvider() { + return () -> new AttributedString("dummy-shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)); + } + + @Test + public void testExitShortcuts() throws Exception { + + PipedInputStream in = new PipedInputStream(); + final PipedOutputStream outIn = new PipedOutputStream(in); + Terminal terminal = TerminalBuilder.builder() + .streams(in, System.out) + .nativeSignals(true) + .build(); + LineReaderBuilder builder = + LineReaderBuilder.builder() + .terminal(terminal); + + LineReader reader = builder.build(); + InteractiveShellRunner.JLineInputProvider jLineInputProvider = new InteractiveShellRunner.JLineInputProvider(reader, dummyPromptProvider()); + + // clear with ctrl + c + outIn.write('a'); + outIn.write(3); // ctrl + c + assertDoesNotThrow(jLineInputProvider::readInput); + + // exit with ctrl + c + outIn.write(3); + assertThrows(ExitRequest.class, jLineInputProvider::readInput); + + // exit with ctrl + d + outIn.write(4); // ctrl + d + assertThrows(ExitRequest.class, jLineInputProvider::readInput); + } +} From 9d6d1856680097a3f34f54d666b14946ab4e1c06 Mon Sep 17 00:00:00 2001 From: Yubi Lee Date: Wed, 1 Jun 2022 12:45:19 +0900 Subject: [PATCH 2/3] replace junit to assertj --- .../shell/jline/InteractiveShellRunnerTests.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java index 4911156ec..684c92656 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java @@ -8,7 +8,8 @@ import org.jline.utils.AttributedStyle; import org.junit.jupiter.api.Test; import org.springframework.shell.ExitRequest; -import static org.junit.jupiter.api.Assertions.*; + +import static org.assertj.core.api.Assertions.*; import java.io.PipedInputStream; import java.io.PipedOutputStream; @@ -39,14 +40,15 @@ public void testExitShortcuts() throws Exception { // clear with ctrl + c outIn.write('a'); outIn.write(3); // ctrl + c - assertDoesNotThrow(jLineInputProvider::readInput); + + assertThatNoException().isThrownBy(jLineInputProvider::readInput); // exit with ctrl + c outIn.write(3); - assertThrows(ExitRequest.class, jLineInputProvider::readInput); + assertThatThrownBy(jLineInputProvider::readInput).isInstanceOf(ExitRequest.class); // exit with ctrl + d outIn.write(4); // ctrl + d - assertThrows(ExitRequest.class, jLineInputProvider::readInput); + assertThatThrownBy(jLineInputProvider::readInput).isInstanceOf(ExitRequest.class); } } From b6aeee9a8684ff58812bd8dad87ab66508e132a5 Mon Sep 17 00:00:00 2001 From: Yubi Lee Date: Thu, 2 Jun 2022 01:33:13 +0900 Subject: [PATCH 3/3] fix hang on testing --- .../jline/InteractiveShellRunnerTests.java | 124 ++++++++++++++---- 1 file changed, 101 insertions(+), 23 deletions(-) diff --git a/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java index 684c92656..59d3963b1 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java @@ -2,8 +2,8 @@ import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; -import org.jline.terminal.Terminal; -import org.jline.terminal.TerminalBuilder; +import org.jline.terminal.Attributes; +import org.jline.terminal.impl.ExternalTerminal; import org.jline.utils.AttributedString; import org.jline.utils.AttributedStyle; import org.junit.jupiter.api.Test; @@ -11,44 +11,122 @@ import static org.assertj.core.api.Assertions.*; +import java.io.ByteArrayOutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; public class InteractiveShellRunnerTests { + private PipedOutputStream outIn; + private InteractiveShellRunner.JLineInputProvider jLineInputProvider; - public PromptProvider dummyPromptProvider() { + + private PromptProvider dummyPromptProvider() { return () -> new AttributedString("dummy-shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)); } - @Test - public void testExitShortcuts() throws Exception { - + private void initForShortcutKeyTest() throws Exception { PipedInputStream in = new PipedInputStream(); - final PipedOutputStream outIn = new PipedOutputStream(in); - Terminal terminal = TerminalBuilder.builder() - .streams(in, System.out) - .nativeSignals(true) - .build(); + outIn = new PipedOutputStream(in); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ExternalTerminal terminal = new ExternalTerminal("foo", "ansi", in, out, StandardCharsets.UTF_8); + Attributes attributes = terminal.getAttributes(); + attributes.setLocalFlag(Attributes.LocalFlag.ISIG, true); + attributes.setControlChar(Attributes.ControlChar.VINTR, 3); + terminal.setAttributes(attributes); LineReaderBuilder builder = LineReaderBuilder.builder() .terminal(terminal); - LineReader reader = builder.build(); - InteractiveShellRunner.JLineInputProvider jLineInputProvider = new InteractiveShellRunner.JLineInputProvider(reader, dummyPromptProvider()); + LineReader lineReader = builder.build(); + jLineInputProvider = new InteractiveShellRunner.JLineInputProvider(lineReader, dummyPromptProvider()); + } + + @Test + public void testClearWithCtrlC() throws Exception { + + initForShortcutKeyTest(); + + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch endLatch = new CountDownLatch(1); + Thread writeThread = new Thread(() -> { + try { + startLatch.await(); + outIn.write('a'); + outIn.write(3); + endLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + Thread readThread = new Thread(() -> { + assertThatNoException().isThrownBy(() -> assertThat(jLineInputProvider.readInput().rawText()).isEqualTo("")); + endLatch.countDown(); + }); + readThread.start(); + startLatch.countDown(); + writeThread.start(); + + readThread.join(); + writeThread.join(); + } + + + @Test + public void testExitWithCtrlC() throws Exception { + + initForShortcutKeyTest(); - // clear with ctrl + c - outIn.write('a'); - outIn.write(3); // ctrl + c + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch endLatch = new CountDownLatch(1); + Thread writeThread = new Thread(() -> { + try { + startLatch.await(); + outIn.write(3); + endLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + Thread readThread = new Thread(() -> { + assertThatThrownBy(jLineInputProvider::readInput).isInstanceOf(ExitRequest.class); + endLatch.countDown(); + }); + readThread.start(); + startLatch.countDown(); + writeThread.start(); + + readThread.join(); + writeThread.join(); + } + + @Test + public void testExitWithCtrlD() throws Exception { - assertThatNoException().isThrownBy(jLineInputProvider::readInput); + initForShortcutKeyTest(); - // exit with ctrl + c - outIn.write(3); - assertThatThrownBy(jLineInputProvider::readInput).isInstanceOf(ExitRequest.class); + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch endLatch = new CountDownLatch(1); + Thread writeThread = new Thread(() -> { + try { + startLatch.await(); + outIn.write(4); + endLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + Thread readThread = new Thread(() -> { + assertThatThrownBy(jLineInputProvider::readInput).isInstanceOf(ExitRequest.class); + endLatch.countDown(); + }); + readThread.start(); + startLatch.countDown(); + writeThread.start(); - // exit with ctrl + d - outIn.write(4); // ctrl + d - assertThatThrownBy(jLineInputProvider::readInput).isInstanceOf(ExitRequest.class); + readThread.join(); + writeThread.join(); } }