diff --git a/shell/src/main/java/org/unigrid/janus/shell/Janus.java b/shell/src/main/java/org/unigrid/janus/shell/Janus.java index 2e836235..bdcd80a2 100644 --- a/shell/src/main/java/org/unigrid/janus/shell/Janus.java +++ b/shell/src/main/java/org/unigrid/janus/shell/Janus.java @@ -57,7 +57,16 @@ private Janus() { } public static void main(final String[] args) throws Exception { - final Optional options = options(args); + final CommandLine command = command(); + final Optional options; + + try { + options = options(command, args); + } catch (IllegalArgumentException | CommandLine.ParameterException e) { + refuse(command, e); + System.exit(USAGE_ERROR); + return; + } if (options.isPresent()) { start(application(), options.get()); @@ -66,28 +75,36 @@ public static void main(final String[] args) throws Exception { /* * The command line is Janus's: Oriel takes out the window system and Janus reads what is left, refusing what - * it does not know so that a mistyped option is never quietly ignored. Empty when the help was asked for. + * it does not know so that a mistyped option is never quietly ignored. Empty when the help was asked for, + * which is printed plainly on the command's own output; an option Janus or Oriel does not take is thrown, + * so that main alone decides how Janus ends. */ - private static Optional options(final String... args) { - final CommandLine command = new CommandLine(new Arguments()); + static Optional options(final CommandLine command, final String... args) { + final LaunchOptions options = LaunchOptions.of(args); - LaunchOptions.help().ifPresent(line -> command.getCommandSpec().usageMessage().footer("", "Window:", - " " + line)); + if (command.parseArgs(options.remaining().toArray(String[]::new)).isUsageHelpRequested()) { + command.usage(command.getOut()); + command.getOut().flush(); + return Optional.empty(); + } - try { - final LaunchOptions options = LaunchOptions.of(args); + return Optional.of(options); + } - if (command.parseArgs(options.remaining().toArray(String[]::new)).isUsageHelpRequested()) { - LOG.info("{}", command.getUsageMessage()); - return Optional.empty(); - } + /* A refused command line is answered on the command's error output, the way picocli answers it itself. */ + static void refuse(final CommandLine command, final RuntimeException refused) { + command.getErr().println(refused.getMessage()); + command.usage(command.getErr()); + command.getErr().flush(); + } - return Optional.of(options); - } catch (IllegalArgumentException | CommandLine.ParameterException e) { - LOG.error("{}{}{}", e.getMessage(), System.lineSeparator(), command.getUsageMessage()); - System.exit(USAGE_ERROR); - return Optional.empty(); - } + /* What Janus takes, with the window system Oriel takes listed in the help where it can be chosen. */ + static CommandLine command() { + final CommandLine command = new CommandLine(new Arguments()); + + LaunchOptions.help().ifPresent(line -> command.getCommandSpec().usageMessage().footer("", "Window:", + " " + line)); + return command; } /* The window and the message shown when it cannot open both go by this name. */ diff --git a/shell/src/test/java/org/unigrid/janus/shell/JanusTest.java b/shell/src/test/java/org/unigrid/janus/shell/JanusTest.java new file mode 100644 index 00000000..80a64351 --- /dev/null +++ b/shell/src/test/java/org/unigrid/janus/shell/JanusTest.java @@ -0,0 +1,74 @@ +/* + The Janus Wallet + Copyright © 2021-2026 Stiftelsen The Unigrid Foundation + + This program is free software: you can redistribute it and/or modify it under the terms of the + addended GNU Affero General Public License as published by the Free Software Foundation, version 3 + of the License (see COPYING and COPYING.addendum). + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received an addended copy of the GNU Affero General Public License with this program. + If not, see and . + */ + +package org.unigrid.janus.shell; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import picocli.CommandLine; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JanusTest { + /* What a log line starts with, so that plain output can be told from what went through the logger. */ + private static final String LOG_PREFIX = "^\\d{2}:\\d{2}:\\d{2}\\.\\d{3} .*"; + + private final StringWriter out = new StringWriter(); + private final StringWriter err = new StringWriter(); + private CommandLine command; + + /* picocli colours the help where it takes the console to understand colours, as on the Windows runners, which + puts escape codes inside the text, so the tests read it without them. */ + @BeforeEach + public void captureWhatJanusPrints() { + command = Janus.command(); + command.setColorScheme(CommandLine.Help.defaultColorScheme(CommandLine.Help.Ansi.OFF)); + command.setOut(new PrintWriter(out)); + command.setErr(new PrintWriter(err)); + } + + @Test + public void shouldPrintTheHelpPlainlyAndNotStart() { + assertTrue(Janus.options(command, "--help").isEmpty(), "Janus would start after showing the help"); + assertTrue(out.toString().startsWith("Usage: Unigrid"), out.toString()); + assertFalse(out.toString().lines().anyMatch(line -> line.matches(LOG_PREFIX)), out.toString()); + } + + @Test + public void shouldRefuseAnOptionItDoesNotTakeAndSayWhyPlainly() { + final CommandLine.ParameterException refused = assertThrows(CommandLine.ParameterException.class, + () -> Janus.options(command, "--bogus") + ); + + Janus.refuse(command, refused); + assertTrue(err.toString().startsWith(refused.getMessage()), err.toString()); + assertTrue(err.toString().contains("--bogus"), err.toString()); + assertTrue(err.toString().contains("Usage: Unigrid"), err.toString()); + } + + /* Oriel reads the window system on every desktop, even where it hides it from the help, so this holds anywhere. */ + @Test + public void shouldRefuseAWindowSystemOrielDoesNotKnow() { + final IllegalArgumentException refused = assertThrows(IllegalArgumentException.class, + () -> Janus.options(command, "--window-system=bogus") + ); + + assertTrue(refused.getMessage().contains("bogus"), refused.getMessage()); + } +}