Skip to content
This repository was archived by the owner on Jul 6, 2023. It is now read-only.

Commit b5866bb

Browse files
authored
Merge pull request #57 from spacecowboy/fix-windows-interactive
Fixed lack of interactive detection on Windows
2 parents 6eb8ffc + 919c83b commit b5866bb

File tree

6 files changed

+116
-10
lines changed

6 files changed

+116
-10
lines changed

cypher-shell/src/main/java/org/neo4j/shell/ShellRunner.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package org.neo4j.shell;
22

3-
import org.neo4j.shell.cli.*;
3+
import org.neo4j.shell.cli.CliArgs;
4+
import org.neo4j.shell.cli.FileHistorian;
5+
import org.neo4j.shell.cli.InteractiveShellRunner;
6+
import org.neo4j.shell.cli.NonInteractiveShellRunner;
7+
import org.neo4j.shell.cli.StringShellRunner;
48
import org.neo4j.shell.log.Logger;
59
import org.neo4j.shell.parser.ShellStatementParser;
610

711
import javax.annotation.Nonnull;
12+
import javax.annotation.Nullable;
813
import java.io.IOException;
14+
import java.util.Optional;
915

1016
import static org.fusesource.jansi.internal.CLibrary.STDIN_FILENO;
1117
import static org.fusesource.jansi.internal.CLibrary.isatty;
@@ -19,7 +25,6 @@ public interface ShellRunner {
1925
int runUntilEnd();
2026

2127
/**
22-
*
2328
* @return an object which can provide the history of commands executed
2429
*/
2530
@Nonnull
@@ -40,7 +45,7 @@ static ShellRunner getShellRunner(@Nonnull CliArgs cliArgs,
4045
@Nonnull Logger logger) throws IOException {
4146
if (cliArgs.getCypher().isPresent()) {
4247
return new StringShellRunner(cliArgs, cypherShell, logger);
43-
} else if (isInputInteractive()) {
48+
} else if (shouldBeInteractive(cliArgs)) {
4449
return new InteractiveShellRunner(cypherShell, cypherShell, logger, new ShellStatementParser(),
4550
System.in, FileHistorian.getDefaultHistoryFile());
4651
} else {
@@ -49,19 +54,36 @@ static ShellRunner getShellRunner(@Nonnull CliArgs cliArgs,
4954
}
5055
}
5156

57+
/**
58+
* @param cliArgs
59+
* @return true if an interactive shellrunner should be used, false otherwise
60+
*/
61+
static boolean shouldBeInteractive(@Nonnull CliArgs cliArgs) {
62+
if (cliArgs.getNonInteractive()) {
63+
return false;
64+
}
65+
66+
return isInputInteractive(System.getProperty("os.name")).orElse(true);
67+
}
68+
5269
/**
5370
* Checks if STDIN is a TTY. In case TTY checking is not possible (lack of libc), then the check falls back to
5471
* the built in Java {@link System#console()} which checks if EITHER STDIN or STDOUT has been redirected.
5572
*
56-
* @return true if the shell reading from a TTY, false otherwise (e.g., we are reading from a file)
73+
* @return true if the shell reading from a TTY, false otherwise (e.g., we are reading from a file). If on windows,
74+
* no result is returned.
5775
*/
58-
static boolean isInputInteractive() {
76+
static Optional<Boolean> isInputInteractive(@Nullable final String osName) {
77+
if (osName != null && osName.toLowerCase().contains("windows")) {
78+
// System.console is always null on windows
79+
return Optional.empty();
80+
}
5981
try {
60-
return 1 == isatty(STDIN_FILENO);
82+
return Optional.of(1 == isatty(STDIN_FILENO));
6183
} catch (NoClassDefFoundError e) {
6284
// system is not using libc (like Alpine Linux)
6385
// Fallback to checking stdin OR stdout
64-
return System.console() != null;
86+
return Optional.of(System.console() != null);
6587
}
6688
}
6789
}

cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgHelper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public static CliArgs parse(@Nonnull String... args) {
8484

8585
cliArgs.setDebugMode(ns.getBoolean("debug"));
8686

87+
cliArgs.setNonInteractive(ns.getBoolean("force-non-interactive"));
88+
8789
return cliArgs;
8890
}
8991

@@ -146,6 +148,11 @@ private static ArgumentParser setupParser() {
146148
.help("print additional debug information")
147149
.action(new StoreTrueArgumentAction());
148150

151+
parser.addArgument("--non-interactive")
152+
.help("force non-interactive mode, only useful if auto-detection fails (like on Windows)")
153+
.dest("force-non-interactive")
154+
.action(new StoreTrueArgumentAction());
155+
149156
parser.addArgument("cypher")
150157
.nargs("?")
151158
.help("an optional string of cypher to execute and then exit");

cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgs.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class CliArgs {
1414
private Optional<String> cypher = Optional.empty();
1515
private boolean encryption;
1616
private boolean debugMode;
17+
private boolean nonInteractive = false;
1718

1819
/**
1920
* Set the host to the primary value, or if null, the fallback value.
@@ -71,6 +72,13 @@ public void setEncryption(boolean encryption) {
7172
this.encryption = encryption;
7273
}
7374

75+
/**
76+
* Force the shell to use non-interactive mode. Only useful on systems where auto-detection fails, such as Windows.
77+
*/
78+
public void setNonInteractive(boolean nonInteractive) {
79+
this.nonInteractive = nonInteractive;
80+
}
81+
7482
/**
7583
* Enable/disable debug mode
7684
*/
@@ -120,4 +128,8 @@ public boolean getEncryption() {
120128
public boolean getDebugMode() {
121129
return debugMode;
122130
}
131+
132+
public boolean getNonInteractive() {
133+
return nonInteractive;
134+
}
123135
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.neo4j.shell;
2+
3+
import org.junit.Rule;
4+
import org.junit.Test;
5+
import org.junit.rules.ExpectedException;
6+
import org.neo4j.shell.cli.CliArgs;
7+
import org.neo4j.shell.cli.InteractiveShellRunner;
8+
import org.neo4j.shell.cli.NonInteractiveShellRunner;
9+
import org.neo4j.shell.log.Logger;
10+
11+
import static org.junit.Assert.assertFalse;
12+
import static org.junit.Assert.assertTrue;
13+
import static org.junit.Assume.assumeTrue;
14+
import static org.mockito.Mockito.mock;
15+
import static org.neo4j.shell.ShellRunner.getShellRunner;
16+
import static org.neo4j.shell.ShellRunner.isInputInteractive;
17+
18+
public class ShellRunnerTest {
19+
@Rule
20+
public final ExpectedException thrown = ExpectedException.none();
21+
22+
@Test
23+
public void isInputInteractiveThrowsOnWindows() throws Exception {
24+
assertFalse("No result should be returned on Windows",
25+
isInputInteractive("Windows 7").isPresent());
26+
}
27+
28+
@Test
29+
public void isInputInteractiveDoesNotThrowOnNonWindows() throws Exception {
30+
assertTrue(isInputInteractive(null).isPresent());
31+
assertTrue(isInputInteractive("").isPresent());
32+
assertTrue(isInputInteractive("Linux").isPresent());
33+
assertTrue(isInputInteractive("BeOS").isPresent());
34+
}
35+
36+
@Test
37+
public void inputIsInteractiveByDefaultOnWindows() throws Exception {
38+
assumeTrue(System.getProperty("os.name", "").toLowerCase().contains("windows"));
39+
ShellRunner runner = getShellRunner(new CliArgs(), mock(CypherShell.class), mock(Logger.class));
40+
assertTrue("Should be interactive shell runner by default on windows",
41+
runner instanceof InteractiveShellRunner);
42+
}
43+
44+
@Test
45+
public void inputIsNonInteractiveIfForced() throws Exception {
46+
CliArgs args = new CliArgs();
47+
args.setNonInteractive(true);
48+
ShellRunner runner = getShellRunner(args, mock(CypherShell.class), mock(Logger.class));
49+
assertTrue("Should be non-interactive shell runner when forced",
50+
runner instanceof NonInteractiveShellRunner);
51+
}
52+
}

cypher-shell/src/test/java/org/neo4j/shell/cli/CliArgHelperTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ public void setup() {
2626
mockedStdErr = mock(PrintStream.class);
2727
}
2828

29+
@Test
30+
public void testForceNonInteractiveIsNotDefault() {
31+
assertFalse("Force non-interactive should not be the default mode",
32+
CliArgHelper.parse(asArray()).getNonInteractive());
33+
}
34+
35+
@Test
36+
public void testForceNonInteractiveIsParsed() {
37+
assertTrue("Force non-interactive should have been parsed to true",
38+
CliArgHelper.parse(asArray("--non-interactive")).getNonInteractive());
39+
}
40+
2941
@Test
3042
public void testDebugIsNotDefault() {
3143
assertFalse("Debug should not be the default mode",

cypher-shell/src/test/java/org/neo4j/shell/cli/FileHistorianTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static java.lang.System.getProperty;
1818
import static org.junit.Assert.assertEquals;
1919
import static org.junit.Assert.assertNotNull;
20+
import static org.mockito.Matchers.contains;
2021
import static org.mockito.Mockito.doReturn;
2122
import static org.mockito.Mockito.mock;
2223
import static org.mockito.Mockito.verify;
@@ -46,11 +47,11 @@ public void defaultHistoryFile() throws Exception {
4647
@Test
4748
public void noHistoryFileGivesMemoryHistory() throws Exception {
4849
Historian historian = FileHistorian.setupHistory(reader, logger,
49-
new File("/temp/aasbzs/asfaz/asdfasvzx/asfdasdf/asdfasd"));
50+
Paths.get("temp", "aasbzs", "asfaz").toFile());
5051

5152
assertNotNull(historian);
5253

53-
verify(logger).printError("Could not load history file. Falling back to session-based history.\n" +
54-
"Failed to create directory for history: /temp/aasbzs/asfaz/asdfasvzx/asfdasdf");
54+
verify(logger).printError(contains("Could not load history file. Falling back to session-based history.\n" +
55+
"Failed to create directory for history"));
5556
}
5657
}

0 commit comments

Comments
 (0)