From 38ce78434af19cf0455109b8fa4622e97358650a Mon Sep 17 00:00:00 2001 From: Yubi Lee Date: Wed, 8 Jun 2022 02:20:52 +0900 Subject: [PATCH] Capturing CTRL+D EOF to exit the shell * Capturing CTRL+D (EOF) to exit the shell * replace junit to assertj * fix hang on testing --- .../shell/jline/InteractiveShellRunner.java | 4 + .../jline/InteractiveShellRunnerTests.java | 132 ++++++++++++++++++ 2 files changed, 136 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 c292f5a9..cabdfaff 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 class InteractiveShellRunner implements ShellRunner { 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 00000000..59d3963b --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/jline/InteractiveShellRunnerTests.java @@ -0,0 +1,132 @@ +package org.springframework.shell.jline; + +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +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; +import org.springframework.shell.ExitRequest; + +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; + + + private PromptProvider dummyPromptProvider() { + return () -> new AttributedString("dummy-shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)); + } + + private void initForShortcutKeyTest() throws Exception { + PipedInputStream in = new PipedInputStream(); + 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 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(); + + 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 { + + initForShortcutKeyTest(); + + 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(); + + readThread.join(); + writeThread.join(); + } +}