Skip to content

Commit 6bbd12f

Browse files
vdoblernigeltao
authored andcommitted
exp/cookiejar: make cookie sorting deterministic.
Re-enable TestUpdateAndDelete, TestExpiration, TestChromiumDomain and TestChromiumDeletion on Windows. Sorting of cookies with same path length and same creation time is done by an additional seqNum field. This makes the order in which cookies are returned in Cookies deterministic, even if the system clock is manipulated or on systems with a low-resolution clock. The tests now use a synthetic time: This makes cookie testing reliable in case of bogus system clocks and speeds up the expiration tests. R=nigeltao, alex.brainman, dave CC=golang-dev https://golang.org/cl/7323063
1 parent 556dd0b commit 6bbd12f

File tree

2 files changed

+59
-36
lines changed

2 files changed

+59
-36
lines changed

src/pkg/exp/cookiejar/jar.go

+31-11
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ type Jar struct {
6363
// entries is a set of entries, keyed by their eTLD+1 and subkeyed by
6464
// their name/domain/path.
6565
entries map[string]map[string]entry
66+
67+
// nextSeqNum is the next sequence number assigned to a new cookie
68+
// created SetCookies.
69+
nextSeqNum uint64
6670
}
6771

6872
// New returns a new cookie jar. A nil *Options is equivalent to a zero
@@ -78,7 +82,9 @@ func New(o *Options) (*Jar, error) {
7882
}
7983

8084
// entry is the internal representation of a cookie.
81-
// The fields are those of RFC 6265.
85+
//
86+
// This struct type is not used outside of this package per se, but the exported
87+
// fields are those of RFC 6265.
8288
type entry struct {
8389
Name string
8490
Value string
@@ -91,6 +97,11 @@ type entry struct {
9197
Expires time.Time
9298
Creation time.Time
9399
LastAccess time.Time
100+
101+
// seqNum is a sequence number so that Cookies returns cookies in a
102+
// deterministic order, even for cookies that have equal Path length and
103+
// equal Creation time. This simplifies testing.
104+
seqNum uint64
94105
}
95106

96107
// Id returns the domain;path;name triple of e as an id.
@@ -135,11 +146,13 @@ type byPathLength []entry
135146
func (s byPathLength) Len() int { return len(s) }
136147

137148
func (s byPathLength) Less(i, j int) bool {
138-
in, jn := len(s[i].Path), len(s[j].Path)
139-
if in == jn {
149+
if len(s[i].Path) != len(s[j].Path) {
150+
return len(s[i].Path) > len(s[j].Path)
151+
}
152+
if !s[i].Creation.Equal(s[j].Creation) {
140153
return s[i].Creation.Before(s[j].Creation)
141154
}
142-
return in > jn
155+
return s[i].seqNum < s[j].seqNum
143156
}
144157

145158
func (s byPathLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
@@ -148,6 +161,11 @@ func (s byPathLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
148161
//
149162
// It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
150163
func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
164+
return j.cookies(u, time.Now())
165+
}
166+
167+
// cookies is like Cookies but takes the current time as a parameter.
168+
func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
151169
if u.Scheme != "http" && u.Scheme != "https" {
152170
return cookies
153171
}
@@ -165,7 +183,6 @@ func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
165183
return cookies
166184
}
167185

168-
now := time.Now()
169186
https := u.Scheme == "https"
170187
path := u.Path
171188
if path == "" {
@@ -208,6 +225,11 @@ func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
208225
//
209226
// It does nothing if the URL's scheme is not HTTP or HTTPS.
210227
func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
228+
j.setCookies(u, cookies, time.Now())
229+
}
230+
231+
// setCookies is like SetCookies but takes the current time as parameter.
232+
func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
211233
if len(cookies) == 0 {
212234
return
213235
}
@@ -225,7 +247,6 @@ func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
225247
defer j.mu.Unlock()
226248

227249
submap := j.entries[key]
228-
now := time.Now()
229250

230251
modified := false
231252
for _, cookie := range cookies {
@@ -249,16 +270,15 @@ func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
249270

250271
if old, ok := submap[id]; ok {
251272
e.Creation = old.Creation
273+
e.seqNum = old.seqNum
252274
} else {
253275
e.Creation = now
276+
e.seqNum = j.nextSeqNum
277+
j.nextSeqNum++
254278
}
255279
e.LastAccess = now
256280
submap[id] = e
257281
modified = true
258-
// Make Creation and LastAccess strictly monotonic forcing
259-
// deterministic behaviour during sorting.
260-
// TODO: check if this is conforming to RFC 6265.
261-
now = now.Add(1 * time.Nanosecond)
262282
}
263283

264284
if modified {
@@ -384,7 +404,7 @@ func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e e
384404
e.Expires = endOfTime
385405
e.Persistent = false
386406
} else {
387-
if c.Expires.Before(now) {
407+
if !c.Expires.After(now) {
388408
return e, true, nil
389409
}
390410
e.Expires = c.Expires

src/pkg/exp/cookiejar/jar_test.go

+28-25
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414
"time"
1515
)
1616

17+
// tNow is the synthetic current time used as now during testing.
18+
var tNow = time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC)
19+
1720
// testPSL implements PublicSuffixList with just two rules: "co.uk"
1821
// and the default rule "*".
1922
type testPSL struct{}
@@ -199,9 +202,9 @@ func TestDomainAndType(t *testing.T) {
199202
}
200203
}
201204

202-
// expiresIn creates an expires attribute delta seconds from now.
205+
// expiresIn creates an expires attribute delta seconds from tNow.
203206
func expiresIn(delta int) string {
204-
t := time.Now().Round(time.Second).Add(time.Duration(delta) * time.Second)
207+
t := tNow.Add(time.Duration(delta) * time.Second)
205208
return "expires=" + t.Format(time.RFC1123)
206209
}
207210

@@ -216,9 +219,12 @@ func mustParseURL(s string) *url.URL {
216219

217220
// jarTest encapsulates the following actions on a jar:
218221
// 1. Perform SetCookies with fromURL and the cookies from setCookies.
222+
// (Done at time tNow + 0 ms.)
219223
// 2. Check that the entries in the jar matches content.
224+
// (Done at time tNow + 1001 ms.)
220225
// 3. For each query in tests: Check that Cookies with toURL yields the
221226
// cookies in want.
227+
// (Query n done at tNow + (n+2)*1001 ms.)
222228
type jarTest struct {
223229
description string // The description of what this test is supposed to test
224230
fromURL string // The full URL of the request from which Set-Cookie headers where received
@@ -235,6 +241,8 @@ type query struct {
235241

236242
// run runs the jarTest.
237243
func (test jarTest) run(t *testing.T, jar *Jar) {
244+
now := tNow
245+
238246
// Populate jar with cookies.
239247
setCookies := make([]*http.Cookie, len(test.setCookies))
240248
for i, cs := range test.setCookies {
@@ -244,11 +252,11 @@ func (test jarTest) run(t *testing.T, jar *Jar) {
244252
}
245253
setCookies[i] = cookies[0]
246254
}
247-
jar.SetCookies(mustParseURL(test.fromURL), setCookies)
255+
jar.setCookies(mustParseURL(test.fromURL), setCookies, now)
256+
now = now.Add(1001 * time.Millisecond)
248257

249258
// Serialize non-expired entries in the form "name1=val1 name2=val2".
250259
var cs []string
251-
now := time.Now().UTC()
252260
for _, submap := range jar.entries {
253261
for _, cookie := range submap {
254262
if !cookie.Expires.After(now) {
@@ -268,8 +276,9 @@ func (test jarTest) run(t *testing.T, jar *Jar) {
268276

269277
// Test different calls to Cookies.
270278
for i, query := range test.queries {
279+
now = now.Add(1001 * time.Millisecond)
271280
var s []string
272-
for _, c := range jar.Cookies(mustParseURL(query.toURL)) {
281+
for _, c := range jar.cookies(mustParseURL(query.toURL), now) {
273282
s = append(s, c.Name+"="+c.Value)
274283
}
275284
if got := strings.Join(s, " "); got != query.want {
@@ -588,37 +597,33 @@ var updateAndDeleteTests = [...]jarTest{
588597
}
589598

590599
func TestUpdateAndDelete(t *testing.T) {
591-
t.Skip("test is broken on windows/386") // issue 4823
592600
jar := newTestJar()
593601
for _, test := range updateAndDeleteTests {
594602
test.run(t, jar)
595603
}
596604
}
597605

598606
func TestExpiration(t *testing.T) {
599-
t.Skip("test is broken on windows/386") // issue 4823
600607
jar := newTestJar()
601608
jarTest{
602-
"Fill jar.",
609+
"Expiration.",
603610
"http://www.host.test",
604611
[]string{
605612
"a=1",
606-
"b=2; max-age=1", // should expire in 1 second
607-
"c=3; " + expiresIn(1), // should expire in 1 second
608-
"d=4; max-age=100",
613+
"b=2; max-age=3",
614+
"c=3; " + expiresIn(3),
615+
"d=4; max-age=5",
616+
"e=5; " + expiresIn(5),
617+
"f=6; max-age=100",
618+
},
619+
"a=1 b=2 c=3 d=4 e=5 f=6", // executed at t0 + 1001 ms
620+
[]query{
621+
{"http://www.host.test", "a=1 b=2 c=3 d=4 e=5 f=6"}, // t0 + 2002 ms
622+
{"http://www.host.test", "a=1 d=4 e=5 f=6"}, // t0 + 3003 ms
623+
{"http://www.host.test", "a=1 d=4 e=5 f=6"}, // t0 + 4004 ms
624+
{"http://www.host.test", "a=1 f=6"}, // t0 + 5005 ms
625+
{"http://www.host.test", "a=1 f=6"}, // t0 + 6006 ms
609626
},
610-
"a=1 b=2 c=3 d=4",
611-
[]query{{"http://www.host.test", "a=1 b=2 c=3 d=4"}},
612-
}.run(t, jar)
613-
614-
time.Sleep(1500 * time.Millisecond)
615-
616-
jarTest{
617-
"Check jar.",
618-
"http://www.host.test",
619-
[]string{},
620-
"a=1 d=4",
621-
[]query{{"http://www.host.test", "a=1 d=4"}},
622627
}.run(t, jar)
623628
}
624629

@@ -885,7 +890,6 @@ var chromiumDomainTests = [...]jarTest{
885890
}
886891

887892
func TestChromiumDomain(t *testing.T) {
888-
t.Skip("test is broken on windows/amd64") // issue 4823
889893
jar := newTestJar()
890894
for _, test := range chromiumDomainTests {
891895
test.run(t, jar)
@@ -954,7 +958,6 @@ var chromiumDeletionTests = [...]jarTest{
954958
}
955959

956960
func TestChromiumDeletion(t *testing.T) {
957-
t.Skip("test is broken on windows/386") // issue 4823
958961
jar := newTestJar()
959962
for _, test := range chromiumDeletionTests {
960963
test.run(t, jar)

0 commit comments

Comments
 (0)