Reputation: 2798
After creating a CLI test in Java, I receive the checkstyle Error:
[ERROR] /src/src/test/java/com/doctestbot/cli/TestCliParser.java:53:23: 'supportedOptions' hides a field. [HiddenField]
[ERROR] /src/src/test/java/com/doctestbot/cli/TestCliParser.java:61:25: 'cliParser' hides a field. [HiddenField]
I assume this is because the private variable supportedOptions
of the test class TestCliParser
overshadows the same variable supportedOptions
in the test method testArgExists
after it is created in the @BeforeEach
setup()
method.
I understand that having a HiddenField that may be overwritten in some function of the class may be undesirable, hence, I understand Checkstyle throws the error. What I do not quite understand is whether I should be using a different approach/strategy to initialise the cliParser
and supportedOptions
objects, or whether should I pass them differently to the test functions.
This is the relevant test file:
package com.doctestbot;
import com.doctestbot.cli.CliParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.UnrecognizedOptionException;
import org.apache.commons.cli.ParseException;
/**
* Test class for the CliParser class.
* This class contains test methods to verify the functionality of the
* CliParser class.
*/
@SuppressWarnings({"PMD.AtLeastOneConstructor"})
class TestCliParser {
/** Object that is tested.
*/
private CliParser cliParser;
/** A custom set of supported options are created before each test for
* testing.
*/
private Options supportedOptions;
/**
This method sets up a dummy command-line parser with test options and
arguments before each test.
*/
@BeforeEach
public void setup() {
// Create the first option with the specified properties
final Option.Builder firstOptBuilder = Option.builder("f");
firstOptBuilder.required(true);
firstOptBuilder.desc("The first option");
firstOptBuilder.longOpt("first");
final Option firstOption = firstOptBuilder.build();
// Create the option for logging with the specified properties
final Option.Builder optionLogBuilder = Option.builder("l");
optionLogBuilder.required(true);
optionLogBuilder.desc(
"Log the full, unparsed ChatGPT output to the request"
);
optionLogBuilder.longOpt("log");
final Option optionLog = optionLogBuilder.build();
// Create the supported options set and add the options
final Options supportedOptions = new Options();
supportedOptions.addOption(firstOption);
supportedOptions.addOption(optionLog);
final String[] emptyArguments = {};
// Create and initialize the CliParser instance with dummy arguments
@SuppressWarnings({"PMD.UnusedLocalVariable"})
final CliParser cliParser = new CliParser(emptyArguments);
}
/**
* Test method to verify the CLI verification function works if it receives
* expected arguments.
*/
@Test
void testArgExists() {
// Dummy command-line arguments for testing
final String[] dummyArguments = {"-f", "theValueOfA", "--log"};
// Verify no error is thrown upon valid arguments.
assertDoesNotThrow(() -> cliParser.verifyArgsAreSupported(
supportedOptions, dummyArguments));
}
/**
* Test method to verify the verifyArgsAreSupported method throws an error
* if an unexpected argument is passed.
*/
@Test
@SuppressWarnings({"PMD.JUnitTestContainsTooManyAsserts",
"PMD.LawOfDemeter"})
void testUnknownArgThrowsError() {
// Dummy command-line arguments for testing
final String[] dummyArguments = {"-f", "theValueOfA", "--log",
"--someUnsupportedArg"};
// Verify that the provided arguments match the supported options
// Assert that the UnrecognizedOptionException is thrown and its cause
// is ParseException.
final Exception exception = assertThrows(
UnrecognizedOptionException.class, () -> {
cliParser.verifyArgsAreSupported(supportedOptions, dummyArguments);
});
// Assert the cause of the UnrecognizedOptionException
final Throwable cause = exception.getCause();
assertTrue(cause instanceof ParseException,
"The error was thrown due to a ParseException.");
}
}
How can I resolve the HiddenField error whilst still initialising the cliParser
and supportedOptions
in the @BeforeEach
setup()
function?
The error I made pointed out in the comments makes sense to me. However, when I change:
final Options supportedOptions = new Options();
to:
supportedOptions = new Options();
Checkstyle throws error:
checkstyle...............................................................Failed
- hook id: checkstyle
- exit code: 254
Starting audit...
com.puppycrawl.tools.checkstyle.api.CheckstyleException: Exception was thrown while processing src/test/java/com/doctestbot/cli/TestCliParser.java
at com.puppycrawl.tools.checkstyle.Checker.processFiles(Checker.java:306)
at com.puppycrawl.tools.checkstyle.Checker.process(Checker.java:223)
at com.puppycrawl.tools.checkstyle.Main.runCheckstyle(Main.java:415)
at com.puppycrawl.tools.checkstyle.Main.runCli(Main.java:338)
at com.puppycrawl.tools.checkstyle.Main.execute(Main.java:195)
at com.puppycrawl.tools.checkstyle.Main.main(Main.java:130)
Caused by: com.puppycrawl.tools.checkstyle.api.CheckstyleException: IllegalStateException occurred while parsing file /src/src/test/java/com/doctestbot/cli/TestCliParser.java.
at com.puppycrawl.tools.checkstyle.JavaParser.parse(JavaParser.java:105)
at com.puppycrawl.tools.checkstyle.TreeWalker.processFiltered(TreeWalker.java:152)
at com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.process(AbstractFileSetCheck.java:98)
at com.puppycrawl.tools.checkstyle.Checker.processFile(Checker.java:334)
at com.puppycrawl.tools.checkstyle.Checker.processFiles(Checker.java:293)
... 5 more
Caused by: java.lang.IllegalStateException: 37:61: mismatched input '(' expecting ';'
at com.puppycrawl.tools.checkstyle.JavaParser$CheckstyleErrorListener.syntaxError(JavaParser.java:255)
at org.antlr.v4.runtime.ProxyErrorListener.syntaxError(ProxyErrorListener.java:41)
at org.antlr.v4.runtime.Parser.notifyErrorListeners(Parser.java:544)
at org.antlr.v4.runtime.DefaultErrorStrategy.reportInputMismatch(DefaultErrorStrategy.java:327)
at org.antlr.v4.runtime.DefaultErrorStrategy.reportError(DefaultErrorStrategy.java:139)
at com.puppycrawl.tools.checkstyle.CheckstyleParserErrorStrategy.recoverInline(CheckstyleParserErrorStrategy.java:38)
at org.antlr.v4.runtime.Parser.match(Parser.java:208)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.blockStatement(JavaLanguageParser.java:6189)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.block(JavaLanguageParser.java:6100)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.methodBody(JavaLanguageParser.java:2939)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.methodDeclaration(JavaLanguageParser.java:2897)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.memberDeclaration(JavaLanguageParser.java:2744)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.classBodyDeclaration(JavaLanguageParser.java:2670)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.classBody(JavaLanguageParser.java:2476)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.classDeclaration(JavaLanguageParser.java:1095)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.types(JavaLanguageParser.java:752)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.typeDeclaration(JavaLanguageParser.java:666)
at com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser.compilationUnit(JavaLanguageParser.java:413)
at com.puppycrawl.tools.checkstyle.JavaParser.parse(JavaParser.java:99)
... 9 more
Caused by: org.antlr.v4.runtime.InputMismatchException
... 23 more
Checkstyle ends with 1 errors.
Upvotes: 0
Views: 368
Reputation: 2798
A solution was found by implementing the suggestion given in the comment:
package com.doctestbot.cli;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.ParseException;
/**
* The CLI parser for the documentation and test writing bot.
* This class provides functionality to parse and verify command-line arguments
* for the bot.
*/
public class CliParser {
/**
* Declare the CLI argument option objects.
*/
private final Options cliOptions;
/** Create a proper logger for error messages. */
private static final Logger LOGGER = LoggerFactory.getLogger(
CliParser.class
);
/**
* Constructs a new CliParser with the given command-line arguments.
*
* @param arguments The command-line arguments provided to the bot.
*/
public CliParser(final String... arguments) {
this.cliOptions = buildSupportedArgs();
verifyArgsAreSupported(this.cliOptions, arguments);
}
/**
* Adds logging options to the supported command-line options.
*
* @param supportedOptions The Options object to which the logging option
* will be added.
*/
@SuppressWarnings({"PMD.LawOfDemeter", "PMD.AvoidDuplicateLiterals"})
private static void addLogOptions(final Options supportedOptions) {
// Logging option
final Option.Builder logBldr = Option.builder("l");
logBldr.required(false);
logBldr.desc(
"Log the full, unparsed ChatGPT output to the request.");
logBldr.longOpt("log");
final Option logArg = logBldr.build();
supportedOptions.addOption(logArg);
}
/**
* Adds ChatGPT output verification CLI options to supportedOptions.
*
* @param supportedOptions The Options object to which the output
* verification options will be added.
*/
@SuppressWarnings({"PMD.LawOfDemeter", "PMD.AvoidDuplicateLiterals"})
private static void addOutputVerifOptions(final Options supportedOptions) {
final Option.Builder preCommitBldr = Option.builder("pc");
preCommitBldr.desc(
"Perform pre-commit checks before committing changes.");
preCommitBldr.longOpt("pre-commit-checks");
final Option preCommitArg = preCommitBldr.build();
supportedOptions.addOption(preCommitArg);
final Option.Builder compilabilityBldr = Option.builder("cc");
compilabilityBldr.desc(
"Perform compilability check.");
compilabilityBldr.longOpt("compilability");
final Option compilabilityArg = compilabilityBldr.build();
supportedOptions.addOption(compilabilityArg);
final Option.Builder runnabililtyBldr = Option.builder("rc");
runnabililtyBldr.desc(
"Enforce runnability compliance for generated code.");
runnabililtyBldr.longOpt("runnability-compliance");
final Option runnabililtyArg = runnabililtyBldr.build();
supportedOptions.addOption(runnabililtyArg);
final Option.Builder testsBldr = Option.builder("tc");
testsBldr.desc(
"Enforce the tests pass for generated code.");
testsBldr.longOpt("test-compliance");
final Option testsArg = testsBldr.build();
supportedOptions.addOption(testsArg);
final Option.Builder retriesBldr = Option.builder("n");
retriesBldr.desc(
"Specify the number of retries for certain actions (default: 1)."
);
retriesBldr.longOpt("retries");
retriesBldr.hasArg();
final Option retriesArg = retriesBldr.build();
supportedOptions.addOption(retriesArg);
}
/**
* Adds options for user to specify the path to source and test files of the
* target repository.
*
* @param supportedOptions The Options object to which the target paths
* options will be added.
*/
@SuppressWarnings({"PMD.LawOfDemeter", "PMD.AvoidDuplicateLiterals"})
private static void addTargetPathsOptions(final Options supportedOptions) {
final Option.Builder srcBldr = Option.builder("sd");
srcBldr.desc(
"The relative directory from the root dir to the dir with the "
+ "project code.");
srcBldr.longOpt("source-dir");
srcBldr.hasArg();
final Option srcArg = srcBldr.build();
supportedOptions.addOption(srcArg);
final Option.Builder testBldr = Option.builder("td");
testBldr.desc(
"The relative directory from the root dir to the dir with the "
+ "tests.");
testBldr.longOpt("test-dir");
testBldr.hasArg();
final Option testArg = testBldr.build();
supportedOptions.addOption(testArg);
}
/**
* Adds options to specify the context to feed to ChatGPT for the target
* repository.
*
* @param supportedOptions The Options object to which the context options
* will be added.
*/
@SuppressWarnings({"PMD.LawOfDemeter", "PMD.AvoidDuplicateLiterals",
"PMD.LongVariable"})
private static void addGptContextOptions(final Options supportedOptions) {
// What context to feed to ChatGPT (once) supportedOptions
final Option.Builder shareSrcBldr = Option.builder("ssd");
shareSrcBldr.desc(
"Share the source dir files of the specified language with"
+ " ChatGPT.");
shareSrcBldr.longOpt("share-source-dir");
final Option shareSrcArg = shareSrcBldr.build();
supportedOptions.addOption(shareSrcArg);
final Option.Builder shareTestBldr = Option.builder("std");
shareTestBldr.desc(
"Share the test dir files of the specified language with "
+ "ChatGPT.");
shareTestBldr.longOpt("share-test-dir");
final Option shareTestArg = shareTestBldr.build();
supportedOptions.addOption(shareTestArg);
final Option.Builder shareSrcStructBldr = Option.builder("ssds");
shareSrcStructBldr.desc(
"Share the source dir files of the specified language with "
+ "ChatGPT.");
shareSrcStructBldr.longOpt("share-source-dir-structure");
final Option shareSrcStructArg = shareSrcStructBldr.build();
supportedOptions.addOption(shareSrcStructArg);
final Option.Builder shareTestStructBldr = Option.builder(
"stds");
shareTestStructBldr.desc(
"Share the test dir files of the specified language with ChatGPT."
);
shareTestStructBldr.longOpt("share-test-dir-structure");
final Option shareTestStructArg = shareTestStructBldr.build();
supportedOptions.addOption(shareTestStructArg);
}
/**
* Adds options for specifying the which files will be processed.
*
* @param supportedOptions The Options object to which the processed code
* type options will be added.
*/
@SuppressWarnings({"PMD.LawOfDemeter", "PMD.AvoidDuplicateLiterals"})
private static void addTargetFileOptions(final Options supportedOptions) {
final Option.Builder languageBldr = Option.builder("l");
languageBldr.desc(
"Specify the programming language of the target project.");
languageBldr.longOpt("language");
languageBldr.hasArg();
final Option languageArg = languageBldr.build();
supportedOptions.addOption(languageArg);
final Option.Builder allFilesBldr = Option.builder("a");
allFilesBldr.desc(
"Perform the specified action on all source files in the"
+ " repository.");
allFilesBldr.longOpt("all-files");
final Option allFilesArg = allFilesBldr.build();
supportedOptions.addOption(allFilesArg);
final Option.Builder fileSubstrBldr = Option.builder("fs");
fileSubstrBldr.desc(
"Specify the substring that filenames should contain.");
fileSubstrBldr.longOpt("file-substring");
fileSubstrBldr.hasArg();
final Option fileSubstrArg = fileSubstrBldr.build();
supportedOptions.addOption(fileSubstrArg);
final Option.Builder funcBldr = Option.builder("ms");
funcBldr.desc(
"Specify the name of the method to run the bot on.");
funcBldr.longOpt("method");
funcBldr.hasArg();
final Option funcArg = funcBldr.build();
supportedOptions.addOption(funcArg);
final Option.Builder docstrBldr = Option.builder("ds");
docstrBldr.desc(
"Run on docstring of file.");
docstrBldr.longOpt("docstring");
final Option docstrArg = docstrBldr.build();
supportedOptions.addOption(docstrArg);
}
/**
* Adds options for specifying the type of processed code and the actions
* to perform.
*
* @param supportedOptions The Options object to which the processed code
* type options will be added.
*/
@SuppressWarnings({"PMD.LawOfDemeter", "PMD.AvoidDuplicateLiterals"})
private static void addCodeTypeOptions(final Options supportedOptions) {
final Option.Builder docuBldr = Option.builder("d");
docuBldr.desc(
"Perform the documentation action for identified functions.");
docuBldr.longOpt("documentation");
final Option docuArg = docuBldr.build();
supportedOptions.addOption(docuArg);
final Option.Builder testGenBldr = Option.builder("t");
testGenBldr.desc(
"Perform the test generation action for identified functions.");
testGenBldr.longOpt("test-generation");
final Option testGenArg = testGenBldr.build();
supportedOptions.addOption(testGenArg);
final Option.Builder commentsBldr = Option.builder("c");
commentsBldr.desc(
"Improve comments in the function.");
commentsBldr.longOpt("comments");
final Option commentsArg = commentsBldr.build();
supportedOptions.addOption(commentsArg);
final Option.Builder overrideBldr = Option.builder("o");
overrideBldr.desc(
"Override pre-existing (DOCUMENTATION, COMMENTS, FUNCTIONS,"
+ " TESTS).");
overrideBldr.longOpt("override");
final Option overrideArg = overrideBldr.build();
supportedOptions.addOption(overrideArg);
}
/**
* Builds and returns the supported command-line options.
*
* @return The Options object containing the supported command-line options.
*/
@SuppressWarnings({"PMD.LawOfDemeter",
"PMD.NcssCount", "PMD.LongVariable"})
private Options buildSupportedArgs() {
final Options supportedOptions = new Options();
addLogOptions(supportedOptions);
addOutputVerifOptions(supportedOptions);
addTargetPathsOptions(supportedOptions);
addGptContextOptions(supportedOptions);
addTargetFileOptions(supportedOptions);
addCodeTypeOptions(supportedOptions);
return supportedOptions;
}
/**
* Prints the usage instructions for running the bot from the command line.
* It provides information about the available command-line options and
* their usage.
*/
private void printUsage() {
final HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("java -jar YourJarName.jar", cliOptions);
}
/**
* Verifies whether the provided command-line arguments are supported by
* the bot. Prints usage for invalid commands.
*
* @param options The Options object containing the supported command-line
* options.
* @param args The command-line arguments provided to the bot.
*/
public final void verifyArgsAreSupported(
final Options options,
final String... args
) {
final CommandLineParser parser = new DefaultParser();
try {
parser.parse(options, args);
} catch (ParseException e) {
if (LOGGER.isErrorEnabled()) { // Check if ERROR level is enabled
LOGGER.error(
"Error parsing command-line arguments: {}", e.getMessage()
);
}
printUsage();
throw new CommandLineParseException(
"Error parsing command-line arguments", e);
}
}
}
The additional error, mentioned at the end of the question, was resolved by moving the initialisation of the two private fields into separate setter and getter methods, and calling those methods from the @BeforeEach
setup()
method. I do not yet know why this was necessary/why the error was thrown.
It was not necessary to create an object/constructor of the test class inside the test to call the private fields with this.supportedOptions
. Instead I could just keep them as globals of the test.
Upvotes: 0