Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Method arg values not iterated correctly in CommandSpec#commandMethodParamValues #1741

Closed
Onedy opened this issue Jul 13, 2022 · 9 comments
Closed
Labels
theme: parser An issue or change related to the parser type: bug 🐛
Milestone

Comments

@Onedy
Copy link

Onedy commented Jul 13, 2022

Hi,

I am getting an IllegalArgumentException: argument type mismatch when I input this command: sorteo enter compound tweeturl
Where sorteo and enter are nested subcommands that inherit mixinStandardHelpOptions = true from a parent command annotated with:

@CommandLine.Command(mixinStandardHelpOptions = true, scope = CommandLine.ScopeType.INHERIT)

and compound and tweeturl are parameters. The issue is that these two parameters are retrieved as booleans when they are defined as String:

@CommandLine.Command(name = "enter")
void enter(@CommandLine.Parameters(arity = "1") String sentenceType,
           @CommandLine.Parameters(arity = "1") String tweetUrl) {...}

This is happening because when the call stack gets to commandMethodParamValues
Captura de pantalla 2022-07-13 a las 23 01 01
Captura de pantalla 2022-07-13 a las 22 59 56

the line

int argIndex = autoHelpMixin == null || autoHelpMixin.inherited() ? 0 : 2;

makes this line from the else

values[i] = args.get(argIndex++).getValue();

to retrieve the values of the mixinStandardHelpOptions insetead of the actual parameters, because argIndex starts at 2 as you can see in the variables pane:
Captura de pantalla 2022-07-13 a las 23 00 46
Captura de pantalla 2022-07-13 a las 23 58 12

Am I doing something wrong?

@remkop
Copy link
Owner

remkop commented Jul 14, 2022

Hi @Onedy, thank you for raising this.
I tried but was unable to reproduce the issue. Maybe you can help me create a minimal test case that demonstrates the issue.

I had to guess and this is the test code I ended up with:

// in some JUnit test class

@Command(name = "app", mixinStandardHelpOptions = true, scope = INHERIT, subcommands = Sorteo.class)
static class TestIssue1741 implements Runnable {
    @Override public void run() {
        System.out.println("Executing top-level command");
    }
}
@Command(name = "sorteo")
static class Sorteo implements Runnable {
    @Command(name = "enter")
    void enter(@Parameters(arity = "1") String sentenceType,
               @Parameters(arity = "1") String tweetUrl) {
        System.out.printf("Subcmd 'enter' was called with %s and %s%n", sentenceType, tweetUrl);
    }

    @Override public void run() {
        System.out.println("Executing command 'sorteo'");
    }
}

@Test
public void testIssue1741() {
    new CommandLine(new TestIssue1741()).execute("sorteo", "enter", "compound");
}

@Test
public void testIssue1741b() {
    new CommandLine(new TestIssue1741()).execute("sorteo", "enter", "sentenceType1", "tweetURL2");
}

When I run testIssue1741, I see this output:

Missing required parameter: '<arg1>'
Usage: app sorteo enter [-hV] <arg0> <arg1>

When I run testIssue1741b, I see this output:

Subcmd 'enter' was called with sentenceType1 and tweetURL2

Can you help massage this test into something that demonstrates the issue?

@Onedy
Copy link
Author

Onedy commented Jul 14, 2022

I think the issue appears when the Spring factory is used. Here you have a minimal test:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import picocli.CommandLine;

import java.util.List;

import static picocli.CommandLine.ScopeType.INHERIT;

@Configuration
class Config {

    @Bean
    public CommandLine getTestCommandLine(ParentCommand parentCommand, TestCommand testCommand, CommandLine.IFactory factory) {
        CommandLine commandLine = new CommandLine(parentCommand, factory);
        commandLine.addSubcommand(testCommand);
        return commandLine;
    }
}

@Component
@CommandLine.Command(name = "parentTestCommand", mixinStandardHelpOptions = true, scope = INHERIT)
class ParentTestCommand {
}

@Component
@CommandLine.Command(name = "sorteotest")
class TestCommand {
    @CommandLine.Command(name = "entertest", description = "Start participating in a giveaway")
    void enter(@CommandLine.Parameters(arity = "1") String sentenceType,
               @CommandLine.Parameters(arity = "1") String tweetUrl) {
        System.out.println("entering giveaway");
    }
}

@SpringBootTest
class MyTest {

    @Autowired
    @Qualifier("getTestCommandLine")
    private CommandLine commandLine;

    @org.junit.jupiter.api.Test
    void testIssue1741() {
        commandLine.execute("sorteotest", "entertest", "sequenceType", "url");
    }
}

@Onedy
Copy link
Author

Onedy commented Jul 15, 2022

Hi @remkop, were you able to reproduce this issue?

@remkop
Copy link
Owner

remkop commented Jul 15, 2022

Hi @Onedy, no I haven't...
I got a NullpointerException in testIssue1741() method because the commandLine field is null.

Can you provide an example that doesn't use Spring, or alternatively an example that I can copy into the picocli-spring-boot-starter module (maybe under test) to reproduce the issue?

@Onedy
Copy link
Author

Onedy commented Jul 17, 2022

Hi @remkop, I couldn't build the picocli-spring-boot-starter module because I couldn't get resources from https://repo.spring.io/libs-snapshot, but I've created a minimal project where you can execute the test:

PicocliTest.zip

Just unzip and run with ./gradlew clean assemble :test --tests "test.MyTest" --info
And look at:

MyTest > testIssue1741() STANDARD_ERROR
    java.lang.IllegalArgumentException: argument type mismatch

@remkop
Copy link
Owner

remkop commented Jul 19, 2022

@Onedy Great thank you!
I was able to reproduce the issue now, even without Spring.
This demonstrates that the issue is in picocli. (UPDATE: new link to the renamed test)
It appears to have something to do with the subcommand being added later with commandLine.addSubcommand(testCommand). Still investigating...

@remkop
Copy link
Owner

remkop commented Jul 22, 2022

I found the cause:
When a subcommand with a nested sub-subcommand is added later with commandLine.addSubcommand, there is some logic that takes place that initializes the inherited state for the whole hierarchy and makes sure that the standard help options are mixed in by inheritance.
However, the method for mixing in the standard help options in the hierarchy itself also goes down the hierarchy.
As a result, the sorteotest CommandSpec has inherited=true, but the entertest subcommand still has inherited=false when the standard help option mixin is added. This makes a difference for the position of the arguments of the command method.

@remkop remkop closed this as completed in 4b9875f Jul 22, 2022
@remkop
Copy link
Owner

remkop commented Jul 22, 2022

I fixed the bug in the main branch and the fix will be included in the next release.
Thank you again for raising this!

@remkop remkop added this to the 4.7 milestone Jul 22, 2022
@remkop remkop added type: bug 🐛 theme: parser An issue or change related to the parser labels Jul 22, 2022
@Onedy
Copy link
Author

Onedy commented Aug 8, 2022

Thank you for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: parser An issue or change related to the parser type: bug 🐛
Projects
None yet
Development

No branches or pull requests

2 participants