mirror of
https://github.com/golang/go.git
synced 2026-04-03 01:40:30 +09:00
net/url: add Values.Clone
This change implements a method Clone on Values that creates a deep copy of all of the subject's consistent values. CL 746800 added URL.Clone and this one therefore closes out the feature. Fixes #73450 Change-Id: I6fb95091c856e43063ab641c03034e1faaff8ed6 Reviewed-on: https://go-review.googlesource.com/c/go/+/746801 Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Sean Liao <sean@liao.dev> Auto-Submit: Emmanuel Odeke <emmanuel@orijtech.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Nicholas Husin <nsh@golang.org>
This commit is contained in:
committed by
Gopher Robot
parent
6c083034f8
commit
0359353574
@@ -1 +1,2 @@
|
||||
pkg net/url, method (*URL) Clone() *URL #73450
|
||||
pkg net/url, method (Values) Clone() Values #73450
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
The new [URL.Clone] method creates a deep copy of a URL.
|
||||
The new [Values.Clone] method creates a deep copy of Values.
|
||||
|
||||
@@ -915,6 +915,19 @@ func (v Values) Has(key string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the subject [Values].
|
||||
func (vs Values) Clone() Values {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
newVals := make(Values, len(vs))
|
||||
for k, v := range vs {
|
||||
newVals[k] = slices.Clone(v)
|
||||
}
|
||||
return newVals
|
||||
}
|
||||
|
||||
// ParseQuery parses the URL-encoded query string and returns
|
||||
// a map listing the values specified for each key.
|
||||
// ParseQuery always returns a non-nil map containing all the
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"fmt"
|
||||
"internal/diff"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -2443,3 +2445,93 @@ func TestURLClone(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValuesClone(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in Values
|
||||
}{
|
||||
{"nil", nil},
|
||||
{"empty", Values{}},
|
||||
{"1 key, nil values", Values{"1": nil}},
|
||||
{"1 key, no values", Values{"1": {}}},
|
||||
{"1 key, some values", Values{"1": {"a", "b"}}},
|
||||
{"multiple keys, diverse values", Values{"1": {"a", "b"}, "X": nil, "B": {"abcdefghi"}}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// The cloned map must always deep equal the input.
|
||||
cloned1 := tt.in.Clone()
|
||||
if !reflect.DeepEqual(tt.in, cloned1) {
|
||||
t.Fatal("reflect.DeepEqual failed")
|
||||
}
|
||||
|
||||
if cloned1 == nil && tt.in == nil {
|
||||
return
|
||||
}
|
||||
if len(cloned1) == 0 && len(tt.in) == 0 && (cloned1 == nil || tt.in == nil) {
|
||||
t.Fatalf("Inconsistency: both have len=0, yet not both nil\nCloned: %#v\nOriginal: %#v\n", cloned1, tt.in)
|
||||
}
|
||||
// Test out malleability of values.
|
||||
cloned1["XXXXXXXXXXX"] = []string{"a", "b"}
|
||||
if reflect.DeepEqual(tt.in, cloned1) {
|
||||
t.Fatal("Inconsistent state: cloned and input are somehow the same")
|
||||
}
|
||||
|
||||
// Ensure that we can correctly invoke some methods like .Add
|
||||
cloned2 := tt.in.Clone()
|
||||
if !reflect.DeepEqual(tt.in, cloned2) {
|
||||
t.Fatal("reflect.DeepEqual failed")
|
||||
}
|
||||
cloned2.Add("a", "A")
|
||||
if !cloned2.Has("a") {
|
||||
t.Error("Cloned doesn't have the desired key: a")
|
||||
}
|
||||
if !cloned2.Has("a") {
|
||||
t.Error("Cloned doesn't have the desired key: a")
|
||||
}
|
||||
// Assert that any changes to the clone did not change the original.
|
||||
if reflect.DeepEqual(tt.in, cloned2) {
|
||||
t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
|
||||
}
|
||||
cloned2.Del("a")
|
||||
// Assert that reverting the clone's changes bring it back to original state.
|
||||
if !reflect.DeepEqual(tt.in, cloned2) {
|
||||
t.Fatal("reflect.DeepEqual failed")
|
||||
}
|
||||
|
||||
cloned3 := tt.in.Clone()
|
||||
clonedKeys := slices.Collect(maps.Keys(cloned3))
|
||||
if len(clonedKeys) == 0 {
|
||||
return
|
||||
}
|
||||
key0 := clonedKeys[0]
|
||||
// Test modifying the actual slice.
|
||||
if len(cloned3[key0]) == 0 {
|
||||
cloned3[key0] = append(cloned3[key0], "golang")
|
||||
} else {
|
||||
cloned3[key0][0] = "directly modified"
|
||||
if got, want := cloned3.Get(key0), "directly modified"; got != want {
|
||||
t.Errorf("Get failed:\n\tGot: %q\n\tWant: %q", got, want)
|
||||
}
|
||||
}
|
||||
if reflect.DeepEqual(tt.in, cloned3) {
|
||||
t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
|
||||
}
|
||||
|
||||
// Try out also with .Set.
|
||||
cloned4 := tt.in.Clone()
|
||||
if !reflect.DeepEqual(tt.in, cloned4) {
|
||||
t.Fatal("reflect.DeepEqual failed")
|
||||
}
|
||||
cloned4.Set(key0, "good night")
|
||||
if reflect.DeepEqual(tt.in, cloned4) {
|
||||
t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
|
||||
}
|
||||
if got, want := cloned4.Get(key0), "good night"; got != want {
|
||||
t.Errorf("Get failed:\n\tGot: %q\n\tWant: %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user