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

String slice issue v1 #981

Merged
merged 8 commits into from
Feb 26, 2020
Merged

String slice issue v1 #981

merged 8 commits into from
Feb 26, 2020

Conversation

asahasrabuddhe
Copy link
Member

Motivation

Fixes #790

The issue describes a bug where the default values of a Slice type flag persist even after the user explicitly sets the flag. Consider the following code:

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/urfave/cli"
)

func main() {
	app := cli.NewApp()
	app.Flags = []cli.Flag{
		cli.StringSliceFlag{
			Name: "Hostnames, n",
			Usage: "`HOSTNAME`s to listen to.",
			Value: &cli.StringSlice{"localhost"},
		},
	}
	app.Action = func(c *cli.Context) error {
		if c.IsSet("Hostnames") {
			// do something
		}

		if c.IsSet("n") {
			// do something else
		}

		return nil
	}

	err := app.Run(os.Args)
	if err != nil {
		log.Fatal()
	}
}

Suppose, we execute this code using --Hostnames a.b.c --Hostnames e.f.g, we would expect the c.StringSlice("Hostnames") function to return a StringSlice with length equal to 2 and containing a.b.c and e.f.g. As of writing this PR, the call to c.StringSlice("Hostnames") returns a StringSlice with length equal to 3 and containing localhost, a.b.c and e.f.g. This behavior is incorrect.

The same behavior can be replicated in the IntSliceFlag and Int64SliceFlag also. This PR aims to fix this inconsistency.

Release Notes

[BUG FIX] String flag no longer persists the default value if the flag is explicitly initialized.

Changes

  • flag_int64_slice.go: Update lookupInt64Slice function to extract default values of the flag and then subsequently remove them.
  • flag_int_slice.go: Update lookupIntSlice function to extract default values of the flag and then subsequently remove them.
  • flag_string_slice.go: Update lookupStringSlice function to extract default values of the flag and then subsequently remove them.
  • flag_test.go: Add unit tests.

Testing

Unit tests are added to cover the following scenarios:

  1. Flags are initialized with default values and are not explicitly initialized by the user.
  2. Flags are initialized with default values and are explicitly initialized by the user.
  3. Flags are initialized with default values and are explicitly initialized by the user with values the same as the default values.

@codecov
Copy link

codecov bot commented Dec 8, 2019

Codecov Report

Merging #981 into v1 will increase coverage by 0.58%.
The diff coverage is 90.36%.

Impacted file tree graph

@@            Coverage Diff             @@
##               v1     #981      +/-   ##
==========================================
+ Coverage   71.65%   72.24%   +0.58%     
==========================================
  Files          30       30              
  Lines        2410     2490      +80     
==========================================
+ Hits         1727     1799      +72     
- Misses        577      581       +4     
- Partials      106      110       +4
Impacted Files Coverage Δ
flag_int64_slice.go 73.25% <86.2%> (+6.01%) ⬆️
flag_string_slice.go 75.94% <92%> (+6.85%) ⬆️
flag_int_slice.go 77.9% <93.1%> (+7.21%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ebf9c3e...0f5ca21. Read the comment docs.

@asahasrabuddhe asahasrabuddhe marked this pull request as ready for review December 8, 2019 08:10
@asahasrabuddhe asahasrabuddhe requested a review from a team as a code owner December 8, 2019 08:10
@asahasrabuddhe
Copy link
Member Author

bump

Copy link
Member

@rliebz rliebz left a comment

Choose a reason for hiding this comment

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

I'm pausing review here because I think this PR might not be approaching the fix in the ideal way. Right now, we'd be accepting that the new values are getting appended incorrectly, and then removing the default based on their string representation after the fact, rather than preventing them from getting incorrectly joined in the first place.

The bigger issue I see is that there's already code to handle removing the defaults—this is in master:

cli/flag_string_slice.go

Lines 22 to 26 in cae7b0c

func (s *StringSlice) Set(value string) error {
if !s.hasBeenSet {
s.slice = []string{}
s.hasBeenSet = true
}

This means on the first setting of a slice flag, we should in theory be zeroing out the value. I'm not sure why that's not working (or no longer working, if it used to), but I think the better fix would be to address that issue at the root rather than patch it downstream on flag lookup.

flag_int64_slice.go Show resolved Hide resolved
flag_string_slice.go Outdated Show resolved Hide resolved
flag_int64_slice.go Outdated Show resolved Hide resolved
return parsed
}
return nil
}

func removeFromInt64Slice(slice []int64, val int64) (newVal []int64) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: We shouldn't be using named returns unless the function signature is ambiguous, or we absolutely have to

There are caveats with the scope of a named variable that aren't always intuitive, and naked returns pretty much always obfuscate what code should be doing. It also encourages us to mutate an existing variable rather than returning directly.

I'd probably write the function like this:

func removeFromInt64Slice(slice []int64, val int64) ([]int64) {
	for i, v := range slice {
		if v == val {
			return slice[count+1:]
		}
	}
	return slice
}

That said, there's an issue here, which I'm not sure if is intentional or not. This function removes all items up to and including the first occurrence of val, but the name implies that we would just be removing val. I don't think it manifests because of how this function is called, but we should either fix the implementation or rename the function to avoid future misuse of this helper.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have updated the function to behave like it's named

func removeFromInt64Slice(slice []int64, val int64) []int64 {
	for i, v := range slice {
		if v == val {
			return append(slice[:i], slice[i+1:]...)
		}
	}
	return slice
}

See it in action: https://play.golang.org/p/ZjJxp38tKNQ

@asahasrabuddhe
Copy link
Member Author

I'm pausing review here because I think this PR might not be approaching the fix in the ideal way. Right now, we'd be accepting that the new values are getting appended incorrectly, and then removing the default based on their string representation after the fact, rather than preventing them from getting incorrectly joined in the first place.

The bigger issue I see is that there's already code to handle removing the defaults—this is in master:

cli/flag_string_slice.go

Lines 22 to 26 in cae7b0c

func (s *StringSlice) Set(value string) error {
if !s.hasBeenSet {
s.slice = []string{}
s.hasBeenSet = true
}

This means on the first setting of a slice flag, we should in theory be zeroing out the value. I'm not sure why that's not working (or no longer working, if it used to), but I think the better fix would be to address that issue at the root rather than patch it downstream on flag lookup.

I agree with your comment but unfortunately, this is right now not possible in the v1 branch because the StringSliceFlag is simply an aliased string slice. To make it work like you say, I would need to rewrite the Slice Flags to make them into structs and then include the has been set.

Copy link
Member

@rliebz rliebz left a comment

Choose a reason for hiding this comment

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

Sorry for missing it earlier, I was definitely comparing this change to what we had in master rather than v1.

Looks pretty good, just a couple small notes.

flag_int_slice.go Outdated Show resolved Hide resolved
flag_int64_slice.go Outdated Show resolved Hide resolved
flag_int_slice.go Outdated Show resolved Hide resolved
flag_test.go Show resolved Hide resolved
rliebz
rliebz previously approved these changes Jan 26, 2020
flag_int64_slice.go Outdated Show resolved Hide resolved
- Add check for blank default value
- Reduce indentation of code
Copy link
Member

@saschagrunert saschagrunert left a comment

Choose a reason for hiding this comment

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

LGTM


func isInt64SliceEqual(newValue, defaultValue []int64) bool {
// If one is nil, the other must also be nil.
if (newValue == nil) != (defaultValue == nil) {
Copy link
Member

Choose a reason for hiding this comment

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

This is the first time I every see an expression like this in golang. 👀

Copy link
Member

Choose a reason for hiding this comment

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

Woooooooooah, love it!

@coilysiren coilysiren merged commit c354cec into v1 Feb 26, 2020
@coilysiren coilysiren deleted the string-slice-issue-v1 branch February 26, 2020 05:38
@coilysiren coilysiren mentioned this pull request Feb 26, 2020
derekbit added a commit to derekbit/longhorn-engine that referenced this pull request Mar 26, 2023
Since urfave/cli v1.22.3, string flag no longer persists the default value if the flag is explicitly initialized.
Please refer to urfave/cli#981.

Signed-off-by: Derek Su <derek.su@suse.com>
derekbit added a commit to derekbit/longhorn-engine that referenced this pull request Mar 26, 2023
Since urfave/cli v1.22.3, string flag no longer persists the default value if the flag is explicitly initialized.
Please refer to urfave/cli#981.

Signed-off-by: Derek Su <derek.su@suse.com>
innobead pushed a commit to longhorn/longhorn-engine that referenced this pull request Apr 5, 2023
Since urfave/cli v1.22.3, string flag no longer persists the default value if the flag is explicitly initialized.
Please refer to urfave/cli#981.

Signed-off-by: Derek Su <derek.su@suse.com>
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.

4 participants