diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 94f464df0..45d3a8570 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -3196,6 +3196,9 @@ void validate() { /** Sets the CommandLine constructed with this {@code CommandSpec} model. */ protected CommandSpec commandLine(CommandLine commandLine) { this.commandLine = commandLine; + for (CommandSpec mixedInSpec : mixins.values()) { + mixedInSpec.commandLine(commandLine); + } for (CommandLine sub : commands.values()) { sub.getCommandSpec().parent(this); } diff --git a/src/test/java/picocli/CommandLineMixinTest.java b/src/test/java/picocli/CommandLineMixinTest.java index 361f557fa..231c0416d 100644 --- a/src/test/java/picocli/CommandLineMixinTest.java +++ b/src/test/java/picocli/CommandLineMixinTest.java @@ -23,6 +23,7 @@ import java.io.File; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.util.concurrent.Callable; import static org.junit.Assert.*; import static picocli.HelpTestUtil.usageString; @@ -785,4 +786,68 @@ public void testMixinStandardHelpOptions_AreAddedLast() { assertEquals(expected, baos.toString()); } + static class Issue439Mixin { + @Spec CommandSpec spec; + + @Option(names = "--trex") + void setTRexFences(final String value) { + throw new ParameterException(spec.commandLine(), "TREX error"); + } + } + + static class Issue439Command { + @Mixin Issue439Mixin mixin; + @Spec CommandSpec spec; + + @Option(names = "--raptor") + void setRaptorFences(final String value) { + throw new ParameterException(spec.commandLine(), "RAPTOR error"); + } + } + + @Test + public void testIssue439InjectedSpecInMixinHasNullCommandLineAnnotations() { + CommandLine cmd = new CommandLine(new Issue439Command()); + assertExceptionThrownFromSetter(cmd); + } + + @Test + public void testIssue439InjectedSpecInMixinHasNullCommandLineProgrammatic() { + final CommandSpec mixinSpec = CommandSpec.create(); + ISetter trexSetter = new ISetter() { + public T set(T value) { + throw new ParameterException(mixinSpec.commandLine(), "TREX error"); + } + }; + mixinSpec.addOption(OptionSpec.builder("--trex"). + type(String.class).setter(trexSetter).build()); + + final CommandSpec commandSpec = CommandSpec.create(); + commandSpec.addMixin("mixin", mixinSpec); + ISetter raptorSetter = new ISetter() { + public T set(T value) { + throw new ParameterException(commandSpec.commandLine(), "RAPTOR error"); + } + }; + commandSpec.addOption(OptionSpec.builder("--raptor"). + type(String.class).setter(raptorSetter).build()); + + CommandLine cmd = new CommandLine(commandSpec); + assertExceptionThrownFromSetter(cmd); + } + + private void assertExceptionThrownFromSetter(CommandLine cmd) { + try { + cmd.parse("--trex", "abc"); + fail("expected ParameterException"); + } catch (ParameterException ex) { + assertEquals("TREX error", ex.getMessage()); + } + try { + cmd.parse("--raptor", "xyz"); + fail("expected ParameterException"); + } catch (ParameterException ex) { + assertEquals("RAPTOR error", ex.getMessage()); + } + } }