Skip to content

Commit d27ba3d

Browse files
authored
Prevent duplicate entries in board_manager.additional_urls (#1902)
* uniquify configuration arrays in `config add` * Add tests for unique board manager URLs * fix linting * lint: update function name * update config set to uniquify slices
1 parent c8ff042 commit d27ba3d

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

Diff for: cli/config/add.go

+43
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,48 @@ import (
2626
"github.com/spf13/cobra"
2727
)
2828

29+
// // TODO: When update to go 1.18 or later, convert to generic
30+
// // to allow uniquify() on any slice that supports
31+
// // `comparable`
32+
// // See https://gosamples.dev/generics-remove-duplicates-slice/
33+
// func uniquify[T comparable](s []T) []T {
34+
// // use a map, which enforces unique keys
35+
// inResult := make(map[T]bool)
36+
// var result []T
37+
// // loop through input slice **in order**,
38+
// // to ensure output retains that order
39+
// // (except that it removes duplicates)
40+
// for i := 0; i < len(s); i++ {
41+
// // attempt to use the element as a key
42+
// if _, ok := inResult[s[i]]; !ok {
43+
// // if key didn't exist in map,
44+
// // add to map and append to result
45+
// inResult[s[i]] = true
46+
// result = append(result, s[i])
47+
// }
48+
// }
49+
// return result
50+
// }
51+
52+
func uniquifyStringSlice(s []string) []string {
53+
// use a map, which enforces unique keys
54+
inResult := make(map[string]bool)
55+
var result []string
56+
// loop through input slice **in order**,
57+
// to ensure output retains that order
58+
// (except that it removes duplicates)
59+
for i := 0; i < len(s); i++ {
60+
// attempt to use the element as a key
61+
if _, ok := inResult[s[i]]; !ok {
62+
// if key didn't exist in map,
63+
// add to map and append to result
64+
inResult[s[i]] = true
65+
result = append(result, s[i])
66+
}
67+
}
68+
return result
69+
}
70+
2971
func initAddCommand() *cobra.Command {
3072
addCommand := &cobra.Command{
3173
Use: "add",
@@ -55,6 +97,7 @@ func runAddCommand(cmd *cobra.Command, args []string) {
5597

5698
v := configuration.Settings.GetStringSlice(key)
5799
v = append(v, args[1:]...)
100+
v = uniquifyStringSlice(v)
58101
configuration.Settings.Set(key, v)
59102

60103
if err := configuration.Settings.WriteConfig(); err != nil {

Diff for: cli/config/set.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func runSetCommand(cmd *cobra.Command, args []string) {
5959
var value interface{}
6060
switch kind {
6161
case reflect.Slice:
62-
value = args[1:]
62+
value = uniquifyStringSlice(args[1:])
6363
case reflect.String:
6464
value = args[1]
6565
case reflect.Bool:

Diff for: test/test_config.py

+63
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,16 @@ def test_add_single_argument(run_command):
277277
settings_json = json.loads(result.stdout)
278278
assert ["https://example.com"] == settings_json["board_manager"]["additional_urls"]
279279

280+
# Adds the same URL (should not error)
281+
url = "https://example.com"
282+
assert run_command(["config", "add", "board_manager.additional_urls", url])
283+
284+
# Verifies a second copy has NOT been added
285+
result = run_command(["config", "dump", "--format", "json"])
286+
assert result.ok
287+
settings_json = json.loads(result.stdout)
288+
assert ["https://example.com"] == settings_json["board_manager"]["additional_urls"]
289+
280290

281291
def test_add_multiple_arguments(run_command):
282292
# Create a config file
@@ -303,6 +313,34 @@ def test_add_multiple_arguments(run_command):
303313
assert urls[0] in settings_json["board_manager"]["additional_urls"]
304314
assert urls[1] in settings_json["board_manager"]["additional_urls"]
305315

316+
# Adds both the same URLs a second time
317+
assert run_command(["config", "add", "board_manager.additional_urls"] + urls)
318+
319+
# Verifies no change in result array
320+
result = run_command(["config", "dump", "--format", "json"])
321+
assert result.ok
322+
settings_json = json.loads(result.stdout)
323+
assert 2 == len(settings_json["board_manager"]["additional_urls"])
324+
assert urls[0] in settings_json["board_manager"]["additional_urls"]
325+
assert urls[1] in settings_json["board_manager"]["additional_urls"]
326+
327+
# Adds multiple URLs ... the middle one is the only new URL
328+
urls = [
329+
"https://example.com/package_example_index.json",
330+
"https://example.com/a_third_package_example_index.json",
331+
"https://example.com/yet_another_package_example_index.json",
332+
]
333+
assert run_command(["config", "add", "board_manager.additional_urls"] + urls)
334+
335+
# Verifies URL has been saved
336+
result = run_command(["config", "dump", "--format", "json"])
337+
assert result.ok
338+
settings_json = json.loads(result.stdout)
339+
assert 3 == len(settings_json["board_manager"]["additional_urls"])
340+
assert urls[0] in settings_json["board_manager"]["additional_urls"]
341+
assert urls[1] in settings_json["board_manager"]["additional_urls"]
342+
assert urls[2] in settings_json["board_manager"]["additional_urls"]
343+
306344

307345
def test_add_on_unsupported_key(run_command):
308346
# Create a config file
@@ -482,6 +520,31 @@ def test_set_slice_with_multiple_arguments(run_command):
482520
assert urls[0] in settings_json["board_manager"]["additional_urls"]
483521
assert urls[1] in settings_json["board_manager"]["additional_urls"]
484522

523+
# Sets a third set of 7 URLs (with only 4 unique values)
524+
urls = [
525+
"https://example.com/first_package_index.json",
526+
"https://example.com/second_package_index.json",
527+
"https://example.com/first_package_index.json",
528+
"https://example.com/fifth_package_index.json",
529+
"https://example.com/second_package_index.json",
530+
"https://example.com/sixth_package_index.json",
531+
"https://example.com/first_package_index.json",
532+
]
533+
assert run_command(["config", "set", "board_manager.additional_urls"] + urls)
534+
535+
# Verifies all unique values exist in config
536+
result = run_command(["config", "dump", "--format", "json"])
537+
assert result.ok
538+
settings_json = json.loads(result.stdout)
539+
assert 4 == len(settings_json["board_manager"]["additional_urls"])
540+
assert urls[0] in settings_json["board_manager"]["additional_urls"]
541+
assert urls[1] in settings_json["board_manager"]["additional_urls"]
542+
assert urls[2] in settings_json["board_manager"]["additional_urls"]
543+
assert urls[3] in settings_json["board_manager"]["additional_urls"]
544+
assert urls[4] in settings_json["board_manager"]["additional_urls"]
545+
assert urls[5] in settings_json["board_manager"]["additional_urls"]
546+
assert urls[6] in settings_json["board_manager"]["additional_urls"]
547+
485548

486549
def test_set_string_with_single_argument(run_command):
487550
# Create a config file

0 commit comments

Comments
 (0)