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

Fixes #587 Support negative numbers as command option values #732

Conversation

jouniheikniemi
Copy link
Contributor

@jouniheikniemi jouniheikniemi commented Feb 16, 2022

Decided to create some unit tests for the tokenizer, as it would've been too much of a mess to test all of this through CommandApp. That's obviously up for debate, but at least the tokenizer tests helped me with this.

@jouniheikniemi jouniheikniemi changed the title Fix #587: Support negative numbers as command option values Fixes #587 Support negative numbers as command option values Feb 16, 2022
@jouniheikniemi jouniheikniemi marked this pull request as draft February 16, 2022 20:01
Copy link
Contributor

@phil-scott-78 phil-scott-78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had some time to look through this. At a glance it looks good. But think we could get some tests that cover the invalid cases as described in the comments of the tokenizer for invalid items?

@jouniheikniemi
Copy link
Contributor Author

Hey @phil-scott-78, thanks for reviewing. Actually, CommandTreeTokenizer does have (logical) 100 % test coverage already. It is not immediately obvious, as this PR is the first stage that actually adds tests specifically for the Tokenizer; previously, the tokenizer has only been tested through CommandAppTests which test the entire parsing and execution pipeline. However, some of the test scenarios there (say, CommandAppTests.Parsing.InvalidShortOptionName.Should_Return_Correct_Text) do exercise these failure modes.

I would personally prefer to have 100 % coverage specifically with the tokenizer test suite, but writing all that is slightly beyond the scope of this issue. Would you expect to see (tokenizer level) tests for the failure scenarios of the ScanShortOptions tokenizer branch or what? TBH, the tokenizer test set in this PR doesn't cover all short option tokenization features (even the happy paths) either, grouped properties probably being the most important missing part. Given that there's no precedent for tokenizer tests in the project, I'm happy to do extra work to set a good example, let's just agree on the goal first.

Copy link
Contributor

@FrankRay78 FrankRay78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for submitting this PR, my review comments are largely stylistic to ensure the changes fit stylistically with the rest of the codebase.

@@ -235,13 +235,34 @@ private static IEnumerable<CommandTreeToken> ScanShortOptions(CommandTreeTokeniz
? new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position, value, $"-{value}")
: new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position + result.Count, value, value));
}
else if (result.Count == 0 && char.IsDigit(current))
{
/* We require short options to be named with letters. Short options that start with a number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everywhere else in the codebase, multi-line comments have simply used the // convention at the beginning of each line. I suggest this comment be updated accordingly to ensure consistency (for better or worse) with the rest of the codebase.

var represntation = current.ToString(CultureInfo.InvariantCulture);
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, represntation, represntation);
throw CommandParseException.InvalidShortOptionName(reader.Original, token);
ThrowInvalidShortOptionName(current.ToString(CultureInfo.InvariantCulture));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see the benefit of putting the local three-line logic into a separate private method, which also still references reader.Original from the calling function without passing this in as an explicit argument. I suggest this private method be removed and the logic relocated inline here, instead.

@@ -0,0 +1,90 @@
namespace Spectre.Console.Tests.Unit.Cli;

public static class CommandTreeTokenizerTests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic that you have introduced explicit unit tests for the tokenizer! A positive move away from the general approach of 'unit tests' being located at the CommandApp level (nb. more like integration tests really).

Does this class need to be static? Unit test classes aren't generally made static - suggest this is also removed to ensure consistency with the rest of the codebase.

{
[Fact]
public void Valueless()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// When

var result = CommandTreeTokenizer.Tokenize(new[] { "-a" });
Assert.Empty(result.Remaining);
var t = Assert.Single(result.Tokens);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Then

[InlineData("-a=-1.5", null)]
[InlineData("-a", "-1.5")]
public void With_Negative_Numeric_Value(string firstArg, string secondArg)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Given

{
args.Add(secondArg);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// When

var result = CommandTreeTokenizer.Tokenize(args);
Assert.Empty(result.Remaining);
Assert.Equal(2, result.Tokens.Count);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Then


[Fact]
public void With_Negative_Numeric_Prefixed_String_Value()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// When

public void With_Negative_Numeric_Prefixed_String_Value()
{
var result = CommandTreeTokenizer.Tokenize(new[] { "-6..2 " });
Assert.Empty(result.Remaining);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Then

@FrankRay78
Copy link
Contributor

FrankRay78 commented Oct 28, 2022

this PR is the first stage that actually adds tests specifically for the Tokenizer; previously, the tokenizer has only been tested through CommandAppTests which test the entire parsing and execution pipeline

This is a significant improvement that @jouniheikniemi has introduced and I would be keen to see this PR, once review comments are addressed 😊, merged into the codebase. Particularly since I have a raft of CommandTreeTokenizer tests I would like to add myself, ideally into the same class being introduced in this PR.

(given this PR has been languishing since Feb, I'm happy to fork Jouni's branch, bring it up to date, and make the review comments myself to get this over the line, if asked...)

@FrankRay78
Copy link
Contributor

This should be closed as it's been fully included in #1048 [I don't have permission to close another person's PR]

@jouniheikniemi
Copy link
Contributor Author

I was about to get back to your review comments, but it seems you'd rather pick up the ball yourself; fine with me. Closing the PR. Thanks!

@FrankRay78
Copy link
Contributor

I was keen to merge your excellent work @jouniheikniemi - thanks for your contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants