parent
5721d85c9a
commit
988d39a5b6
@ -1,81 +1,81 @@ |
||||
{ |
||||
"comment": "", |
||||
"ignore": "test", |
||||
"ignore": "", |
||||
"package": [ |
||||
{ |
||||
"canonical": "github.com/dustin/go-humanize", |
||||
"comment": "", |
||||
"local": "github.com/dustin/go-humanize", |
||||
"local": "vendor/github.com/dustin/go-humanize", |
||||
"revision": "1c212aae1d02984808182b98b0da7a3e07e4c770", |
||||
"revisionTime": "2015-08-09T13:14:05-07:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/facebookgo/clock", |
||||
"comment": "", |
||||
"local": "github.com/facebookgo/clock", |
||||
"local": "vendor/github.com/facebookgo/clock", |
||||
"revision": "600d898af40aa09a7a93ecb9265d87b0504b6f03", |
||||
"revisionTime": "2015-04-09T18:09:13-07:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/facebookgo/httpdown", |
||||
"comment": "", |
||||
"local": "github.com/facebookgo/httpdown", |
||||
"local": "vendor/github.com/facebookgo/httpdown", |
||||
"revision": "9229879964ff32fc4e42c7ba6b4745efce39023c", |
||||
"revisionTime": "2015-08-07T22:21:07Z" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/facebookgo/stats", |
||||
"comment": "", |
||||
"local": "github.com/facebookgo/stats", |
||||
"local": "vendor/github.com/facebookgo/stats", |
||||
"revision": "31fb71caf5a4f04c9f8bb3fa8e7c2597ba6eb50a", |
||||
"revisionTime": "2015-06-12T18:29:15Z" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/fatih/structs", |
||||
"comment": "", |
||||
"local": "github.com/fatih/structs", |
||||
"local": "vendor/github.com/fatih/structs", |
||||
"revision": "a9f7daa9c2729e97450c2da2feda19130a367d8f", |
||||
"revisionTime": "2015-05-26T09:43:52+03:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/gorilla/context", |
||||
"comment": "", |
||||
"local": "github.com/gorilla/context", |
||||
"local": "vendor/github.com/gorilla/context", |
||||
"revision": "215affda49addc4c8ef7e2534915df2c8c35c6cd", |
||||
"revisionTime": "2014-12-17T08:02:51-08:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/gorilla/mux", |
||||
"comment": "", |
||||
"local": "github.com/gorilla/mux", |
||||
"local": "vendor/github.com/gorilla/mux", |
||||
"revision": "5112c33f3a6ef694c1e5784b68981f08b3f0327c", |
||||
"revisionTime": "2015-08-11T22:16:22-07:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/gorilla/rpc/v2", |
||||
"comment": "", |
||||
"local": "github.com/gorilla/rpc/v2", |
||||
"local": "vendor/github.com/gorilla/rpc/v2", |
||||
"revision": "74aa4b5cceca1188df2c7128f7ede4c92893701e", |
||||
"revisionTime": "2015-08-09T21:43:58-07:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/gorilla/rpc/v2/json", |
||||
"comment": "", |
||||
"local": "github.com/gorilla/rpc/v2/json", |
||||
"local": "vendor/github.com/gorilla/rpc/v2/json", |
||||
"revision": "74aa4b5cceca1188df2c7128f7ede4c92893701e", |
||||
"revisionTime": "2015-08-09T21:43:58-07:00" |
||||
}, |
||||
{ |
||||
"canonical": "github.com/minio/cli", |
||||
"comment": "", |
||||
"local": "github.com/minio/cli", |
||||
"revision": "9280cbaadcdd26d50b5ae85123682e37944701de", |
||||
"revisionTime": "2015-07-24T23:32:06-07:00" |
||||
"local": "vendor/github.com/minio/cli", |
||||
"revision": "ee386baecc113eef2b8945df429120a5aec319ef", |
||||
"revisionTime": "2015-08-19T11:23:55-07:00" |
||||
}, |
||||
{ |
||||
"canonical": "gopkg.in/check.v1", |
||||
"comment": "", |
||||
"local": "gopkg.in/check.v1", |
||||
"local": "vendor/gopkg.in/check.v1", |
||||
"revision": "11d3bc7aa68e238947792f30573146a3231fc0f1", |
||||
"revisionTime": "2015-07-29T10:04:31+02:00" |
||||
} |
@ -0,0 +1,219 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"math/big" |
||||
"testing" |
||||
) |
||||
|
||||
func TestBigByteParsing(t *testing.T) { |
||||
tests := []struct { |
||||
in string |
||||
exp uint64 |
||||
}{ |
||||
{"42", 42}, |
||||
{"42MB", 42000000}, |
||||
{"42MiB", 44040192}, |
||||
{"42mb", 42000000}, |
||||
{"42mib", 44040192}, |
||||
{"42MIB", 44040192}, |
||||
{"42 MB", 42000000}, |
||||
{"42 MiB", 44040192}, |
||||
{"42 mb", 42000000}, |
||||
{"42 mib", 44040192}, |
||||
{"42 MIB", 44040192}, |
||||
{"42.5MB", 42500000}, |
||||
{"42.5MiB", 44564480}, |
||||
{"42.5 MB", 42500000}, |
||||
{"42.5 MiB", 44564480}, |
||||
// No need to say B
|
||||
{"42M", 42000000}, |
||||
{"42Mi", 44040192}, |
||||
{"42m", 42000000}, |
||||
{"42mi", 44040192}, |
||||
{"42MI", 44040192}, |
||||
{"42 M", 42000000}, |
||||
{"42 Mi", 44040192}, |
||||
{"42 m", 42000000}, |
||||
{"42 mi", 44040192}, |
||||
{"42 MI", 44040192}, |
||||
{"42.5M", 42500000}, |
||||
{"42.5Mi", 44564480}, |
||||
{"42.5 M", 42500000}, |
||||
{"42.5 Mi", 44564480}, |
||||
// Large testing, breaks when too much larger than
|
||||
// this.
|
||||
{"12.5 EB", uint64(12.5 * float64(EByte))}, |
||||
{"12.5 E", uint64(12.5 * float64(EByte))}, |
||||
{"12.5 EiB", uint64(12.5 * float64(EiByte))}, |
||||
} |
||||
|
||||
for _, p := range tests { |
||||
got, err := ParseBigBytes(p.in) |
||||
if err != nil { |
||||
t.Errorf("Couldn't parse %v: %v", p.in, err) |
||||
} else { |
||||
if got.Uint64() != p.exp { |
||||
t.Errorf("Expected %v for %v, got %v", |
||||
p.exp, p.in, got) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestBigByteErrors(t *testing.T) { |
||||
got, err := ParseBigBytes("84 JB") |
||||
if err == nil { |
||||
t.Errorf("Expected error, got %v", got) |
||||
} |
||||
got, err = ParseBigBytes("") |
||||
if err == nil { |
||||
t.Errorf("Expected error parsing nothing") |
||||
} |
||||
} |
||||
|
||||
func bbyte(in uint64) string { |
||||
return BigBytes((&big.Int{}).SetUint64(in)) |
||||
} |
||||
|
||||
func bibyte(in uint64) string { |
||||
return BigIBytes((&big.Int{}).SetUint64(in)) |
||||
} |
||||
|
||||
func TestBigBytes(t *testing.T) { |
||||
testList{ |
||||
{"bytes(0)", bbyte(0), "0B"}, |
||||
{"bytes(1)", bbyte(1), "1B"}, |
||||
{"bytes(803)", bbyte(803), "803B"}, |
||||
{"bytes(999)", bbyte(999), "999B"}, |
||||
|
||||
{"bytes(1024)", bbyte(1024), "1.0kB"}, |
||||
{"bytes(1MB - 1)", bbyte(MByte - Byte), "1000kB"}, |
||||
|
||||
{"bytes(1MB)", bbyte(1024 * 1024), "1.0MB"}, |
||||
{"bytes(1GB - 1K)", bbyte(GByte - KByte), "1000MB"}, |
||||
|
||||
{"bytes(1GB)", bbyte(GByte), "1.0GB"}, |
||||
{"bytes(1TB - 1M)", bbyte(TByte - MByte), "1000GB"}, |
||||
|
||||
{"bytes(1TB)", bbyte(TByte), "1.0TB"}, |
||||
{"bytes(1PB - 1T)", bbyte(PByte - TByte), "999TB"}, |
||||
|
||||
{"bytes(1PB)", bbyte(PByte), "1.0PB"}, |
||||
{"bytes(1PB - 1T)", bbyte(EByte - PByte), "999PB"}, |
||||
|
||||
{"bytes(1EB)", bbyte(EByte), "1.0EB"}, |
||||
// Overflows.
|
||||
// {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"},
|
||||
|
||||
{"bytes(0)", bibyte(0), "0B"}, |
||||
{"bytes(1)", bibyte(1), "1B"}, |
||||
{"bytes(803)", bibyte(803), "803B"}, |
||||
{"bytes(1023)", bibyte(1023), "1023B"}, |
||||
|
||||
{"bytes(1024)", bibyte(1024), "1.0KiB"}, |
||||
{"bytes(1MB - 1)", bibyte(MiByte - IByte), "1024KiB"}, |
||||
|
||||
{"bytes(1MB)", bibyte(1024 * 1024), "1.0MiB"}, |
||||
{"bytes(1GB - 1K)", bibyte(GiByte - KiByte), "1024MiB"}, |
||||
|
||||
{"bytes(1GB)", bibyte(GiByte), "1.0GiB"}, |
||||
{"bytes(1TB - 1M)", bibyte(TiByte - MiByte), "1024GiB"}, |
||||
|
||||
{"bytes(1TB)", bibyte(TiByte), "1.0TiB"}, |
||||
{"bytes(1PB - 1T)", bibyte(PiByte - TiByte), "1023TiB"}, |
||||
|
||||
{"bytes(1PB)", bibyte(PiByte), "1.0PiB"}, |
||||
{"bytes(1PB - 1T)", bibyte(EiByte - PiByte), "1023PiB"}, |
||||
|
||||
{"bytes(1EiB)", bibyte(EiByte), "1.0EiB"}, |
||||
// Overflows.
|
||||
// {"bytes(1EB - 1P)", bibyte((KIByte*EIByte)-PiByte), "1023EB"},
|
||||
|
||||
{"bytes(5.5GiB)", bibyte(5.5 * GiByte), "5.5GiB"}, |
||||
|
||||
{"bytes(5.5GB)", bbyte(5.5 * GByte), "5.5GB"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func TestVeryBigBytes(t *testing.T) { |
||||
b, _ := (&big.Int{}).SetString("15347691069326346944512", 10) |
||||
s := BigBytes(b) |
||||
if s != "15ZB" { |
||||
t.Errorf("Expected 15ZB, got %v", s) |
||||
} |
||||
s = BigIBytes(b) |
||||
if s != "13ZiB" { |
||||
t.Errorf("Expected 13ZiB, got %v", s) |
||||
} |
||||
|
||||
b, _ = (&big.Int{}).SetString("15716035654990179271180288", 10) |
||||
s = BigBytes(b) |
||||
if s != "16YB" { |
||||
t.Errorf("Expected 16YB, got %v", s) |
||||
} |
||||
s = BigIBytes(b) |
||||
if s != "13YiB" { |
||||
t.Errorf("Expected 13YiB, got %v", s) |
||||
} |
||||
} |
||||
|
||||
func TestVeryVeryBigBytes(t *testing.T) { |
||||
b, _ := (&big.Int{}).SetString("16093220510709943573688614912", 10) |
||||
s := BigBytes(b) |
||||
if s != "16093YB" { |
||||
t.Errorf("Expected 16093YB, got %v", s) |
||||
} |
||||
s = BigIBytes(b) |
||||
if s != "13312YiB" { |
||||
t.Errorf("Expected 13312YiB, got %v", s) |
||||
} |
||||
} |
||||
|
||||
func TestParseVeryBig(t *testing.T) { |
||||
tests := []struct { |
||||
in string |
||||
out string |
||||
}{ |
||||
{"16ZB", "16000000000000000000000"}, |
||||
{"16ZiB", "18889465931478580854784"}, |
||||
{"16.5ZB", "16500000000000000000000"}, |
||||
{"16.5ZiB", "19479761741837286506496"}, |
||||
{"16Z", "16000000000000000000000"}, |
||||
{"16Zi", "18889465931478580854784"}, |
||||
{"16.5Z", "16500000000000000000000"}, |
||||
{"16.5Zi", "19479761741837286506496"}, |
||||
|
||||
{"16YB", "16000000000000000000000000"}, |
||||
{"16YiB", "19342813113834066795298816"}, |
||||
{"16.5YB", "16500000000000000000000000"}, |
||||
{"16.5YiB", "19947276023641381382651904"}, |
||||
{"16Y", "16000000000000000000000000"}, |
||||
{"16Yi", "19342813113834066795298816"}, |
||||
{"16.5Y", "16500000000000000000000000"}, |
||||
{"16.5Yi", "19947276023641381382651904"}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
x, err := ParseBigBytes(test.in) |
||||
if err != nil { |
||||
t.Errorf("Error parsing %q: %v", test.in, err) |
||||
continue |
||||
} |
||||
|
||||
if x.String() != test.out { |
||||
t.Errorf("Expected %q for %q, got %v", test.out, test.in, x) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func BenchmarkParseBigBytes(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
ParseBigBytes("16.5Z") |
||||
} |
||||
} |
||||
|
||||
func BenchmarkBigBytes(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
bibyte(16.5 * GByte) |
||||
} |
||||
} |
@ -0,0 +1,144 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func TestByteParsing(t *testing.T) { |
||||
tests := []struct { |
||||
in string |
||||
exp uint64 |
||||
}{ |
||||
{"42", 42}, |
||||
{"42MB", 42000000}, |
||||
{"42MiB", 44040192}, |
||||
{"42mb", 42000000}, |
||||
{"42mib", 44040192}, |
||||
{"42MIB", 44040192}, |
||||
{"42 MB", 42000000}, |
||||
{"42 MiB", 44040192}, |
||||
{"42 mb", 42000000}, |
||||
{"42 mib", 44040192}, |
||||
{"42 MIB", 44040192}, |
||||
{"42.5MB", 42500000}, |
||||
{"42.5MiB", 44564480}, |
||||
{"42.5 MB", 42500000}, |
||||
{"42.5 MiB", 44564480}, |
||||
// No need to say B
|
||||
{"42M", 42000000}, |
||||
{"42Mi", 44040192}, |
||||
{"42m", 42000000}, |
||||
{"42mi", 44040192}, |
||||
{"42MI", 44040192}, |
||||
{"42 M", 42000000}, |
||||
{"42 Mi", 44040192}, |
||||
{"42 m", 42000000}, |
||||
{"42 mi", 44040192}, |
||||
{"42 MI", 44040192}, |
||||
{"42.5M", 42500000}, |
||||
{"42.5Mi", 44564480}, |
||||
{"42.5 M", 42500000}, |
||||
{"42.5 Mi", 44564480}, |
||||
// Large testing, breaks when too much larger than
|
||||
// this.
|
||||
{"12.5 EB", uint64(12.5 * float64(EByte))}, |
||||
{"12.5 E", uint64(12.5 * float64(EByte))}, |
||||
{"12.5 EiB", uint64(12.5 * float64(EiByte))}, |
||||
} |
||||
|
||||
for _, p := range tests { |
||||
got, err := ParseBytes(p.in) |
||||
if err != nil { |
||||
t.Errorf("Couldn't parse %v: %v", p.in, err) |
||||
} |
||||
if got != p.exp { |
||||
t.Errorf("Expected %v for %v, got %v", |
||||
p.exp, p.in, got) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestByteErrors(t *testing.T) { |
||||
got, err := ParseBytes("84 JB") |
||||
if err == nil { |
||||
t.Errorf("Expected error, got %v", got) |
||||
} |
||||
got, err = ParseBytes("") |
||||
if err == nil { |
||||
t.Errorf("Expected error parsing nothing") |
||||
} |
||||
got, err = ParseBytes("16 EiB") |
||||
if err == nil { |
||||
t.Errorf("Expected error, got %v", got) |
||||
} |
||||
} |
||||
|
||||
func TestBytes(t *testing.T) { |
||||
testList{ |
||||
{"bytes(0)", Bytes(0), "0B"}, |
||||
{"bytes(1)", Bytes(1), "1B"}, |
||||
{"bytes(803)", Bytes(803), "803B"}, |
||||
{"bytes(999)", Bytes(999), "999B"}, |
||||
|
||||
{"bytes(1024)", Bytes(1024), "1.0kB"}, |
||||
{"bytes(9999)", Bytes(9999), "10kB"}, |
||||
{"bytes(1MB - 1)", Bytes(MByte - Byte), "1000kB"}, |
||||
|
||||
{"bytes(1MB)", Bytes(1024 * 1024), "1.0MB"}, |
||||
{"bytes(1GB - 1K)", Bytes(GByte - KByte), "1000MB"}, |
||||
|
||||
{"bytes(1GB)", Bytes(GByte), "1.0GB"}, |
||||
{"bytes(1TB - 1M)", Bytes(TByte - MByte), "1000GB"}, |
||||
{"bytes(10MB)", Bytes(9999 * 1000), "10MB"}, |
||||
|
||||
{"bytes(1TB)", Bytes(TByte), "1.0TB"}, |
||||
{"bytes(1PB - 1T)", Bytes(PByte - TByte), "999TB"}, |
||||
|
||||
{"bytes(1PB)", Bytes(PByte), "1.0PB"}, |
||||
{"bytes(1PB - 1T)", Bytes(EByte - PByte), "999PB"}, |
||||
|
||||
{"bytes(1EB)", Bytes(EByte), "1.0EB"}, |
||||
// Overflows.
|
||||
// {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"},
|
||||
|
||||
{"bytes(0)", IBytes(0), "0B"}, |
||||
{"bytes(1)", IBytes(1), "1B"}, |
||||
{"bytes(803)", IBytes(803), "803B"}, |
||||
{"bytes(1023)", IBytes(1023), "1023B"}, |
||||
|
||||
{"bytes(1024)", IBytes(1024), "1.0KiB"}, |
||||
{"bytes(1MB - 1)", IBytes(MiByte - IByte), "1024KiB"}, |
||||
|
||||
{"bytes(1MB)", IBytes(1024 * 1024), "1.0MiB"}, |
||||
{"bytes(1GB - 1K)", IBytes(GiByte - KiByte), "1024MiB"}, |
||||
|
||||
{"bytes(1GB)", IBytes(GiByte), "1.0GiB"}, |
||||
{"bytes(1TB - 1M)", IBytes(TiByte - MiByte), "1024GiB"}, |
||||
|
||||
{"bytes(1TB)", IBytes(TiByte), "1.0TiB"}, |
||||
{"bytes(1PB - 1T)", IBytes(PiByte - TiByte), "1023TiB"}, |
||||
|
||||
{"bytes(1PB)", IBytes(PiByte), "1.0PiB"}, |
||||
{"bytes(1PB - 1T)", IBytes(EiByte - PiByte), "1023PiB"}, |
||||
|
||||
{"bytes(1EiB)", IBytes(EiByte), "1.0EiB"}, |
||||
// Overflows.
|
||||
// {"bytes(1EB - 1P)", IBytes((KIByte*EIByte)-PiByte), "1023EB"},
|
||||
|
||||
{"bytes(5.5GiB)", IBytes(5.5 * GiByte), "5.5GiB"}, |
||||
|
||||
{"bytes(5.5GB)", Bytes(5.5 * GByte), "5.5GB"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func BenchmarkParseBytes(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
ParseBytes("16.5GB") |
||||
} |
||||
} |
||||
|
||||
func BenchmarkBytes(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
Bytes(16.5 * GByte) |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"math" |
||||
"math/big" |
||||
"testing" |
||||
) |
||||
|
||||
func TestCommas(t *testing.T) { |
||||
testList{ |
||||
{"0", Comma(0), "0"}, |
||||
{"10", Comma(10), "10"}, |
||||
{"100", Comma(100), "100"}, |
||||
{"1,000", Comma(1000), "1,000"}, |
||||
{"10,000", Comma(10000), "10,000"}, |
||||
{"100,000", Comma(100000), "100,000"}, |
||||
{"10,000,000", Comma(10000000), "10,000,000"}, |
||||
{"10,100,000", Comma(10100000), "10,100,000"}, |
||||
{"10,010,000", Comma(10010000), "10,010,000"}, |
||||
{"10,001,000", Comma(10001000), "10,001,000"}, |
||||
{"123,456,789", Comma(123456789), "123,456,789"}, |
||||
{"maxint", Comma(9.223372e+18), "9,223,372,000,000,000,000"}, |
||||
{"minint", Comma(-9.223372e+18), "-9,223,372,000,000,000,000"}, |
||||
{"-123,456,789", Comma(-123456789), "-123,456,789"}, |
||||
{"-10,100,000", Comma(-10100000), "-10,100,000"}, |
||||
{"-10,010,000", Comma(-10010000), "-10,010,000"}, |
||||
{"-10,001,000", Comma(-10001000), "-10,001,000"}, |
||||
{"-10,000,000", Comma(-10000000), "-10,000,000"}, |
||||
{"-100,000", Comma(-100000), "-100,000"}, |
||||
{"-10,000", Comma(-10000), "-10,000"}, |
||||
{"-1,000", Comma(-1000), "-1,000"}, |
||||
{"-100", Comma(-100), "-100"}, |
||||
{"-10", Comma(-10), "-10"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func TestCommafs(t *testing.T) { |
||||
testList{ |
||||
{"0", Commaf(0), "0"}, |
||||
{"10.11", Commaf(10.11), "10.11"}, |
||||
{"100", Commaf(100), "100"}, |
||||
{"1,000", Commaf(1000), "1,000"}, |
||||
{"10,000", Commaf(10000), "10,000"}, |
||||
{"100,000", Commaf(100000), "100,000"}, |
||||
{"834,142.32", Commaf(834142.32), "834,142.32"}, |
||||
{"10,000,000", Commaf(10000000), "10,000,000"}, |
||||
{"10,100,000", Commaf(10100000), "10,100,000"}, |
||||
{"10,010,000", Commaf(10010000), "10,010,000"}, |
||||
{"10,001,000", Commaf(10001000), "10,001,000"}, |
||||
{"123,456,789", Commaf(123456789), "123,456,789"}, |
||||
{"maxf64", Commaf(math.MaxFloat64), "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000"}, |
||||
{"minf64", Commaf(math.SmallestNonzeroFloat64), "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"}, |
||||
{"-123,456,789", Commaf(-123456789), "-123,456,789"}, |
||||
{"-10,100,000", Commaf(-10100000), "-10,100,000"}, |
||||
{"-10,010,000", Commaf(-10010000), "-10,010,000"}, |
||||
{"-10,001,000", Commaf(-10001000), "-10,001,000"}, |
||||
{"-10,000,000", Commaf(-10000000), "-10,000,000"}, |
||||
{"-100,000", Commaf(-100000), "-100,000"}, |
||||
{"-10,000", Commaf(-10000), "-10,000"}, |
||||
{"-1,000", Commaf(-1000), "-1,000"}, |
||||
{"-100.11", Commaf(-100.11), "-100.11"}, |
||||
{"-10", Commaf(-10), "-10"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func BenchmarkCommas(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
Comma(1234567890) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkCommaf(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
Commaf(1234567890.83584) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkBigCommas(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
BigComma(big.NewInt(1234567890)) |
||||
} |
||||
} |
||||
|
||||
func bigComma(i int64) string { |
||||
return BigComma(big.NewInt(i)) |
||||
} |
||||
|
||||
func TestBigCommas(t *testing.T) { |
||||
testList{ |
||||
{"0", bigComma(0), "0"}, |
||||
{"10", bigComma(10), "10"}, |
||||
{"100", bigComma(100), "100"}, |
||||
{"1,000", bigComma(1000), "1,000"}, |
||||
{"10,000", bigComma(10000), "10,000"}, |
||||
{"100,000", bigComma(100000), "100,000"}, |
||||
{"10,000,000", bigComma(10000000), "10,000,000"}, |
||||
{"10,100,000", bigComma(10100000), "10,100,000"}, |
||||
{"10,010,000", bigComma(10010000), "10,010,000"}, |
||||
{"10,001,000", bigComma(10001000), "10,001,000"}, |
||||
{"123,456,789", bigComma(123456789), "123,456,789"}, |
||||
{"maxint", bigComma(9.223372e+18), "9,223,372,000,000,000,000"}, |
||||
{"minint", bigComma(-9.223372e+18), "-9,223,372,000,000,000,000"}, |
||||
{"-123,456,789", bigComma(-123456789), "-123,456,789"}, |
||||
{"-10,100,000", bigComma(-10100000), "-10,100,000"}, |
||||
{"-10,010,000", bigComma(-10010000), "-10,010,000"}, |
||||
{"-10,001,000", bigComma(-10001000), "-10,001,000"}, |
||||
{"-10,000,000", bigComma(-10000000), "-10,000,000"}, |
||||
{"-100,000", bigComma(-100000), "-100,000"}, |
||||
{"-10,000", bigComma(-10000), "-10,000"}, |
||||
{"-1,000", bigComma(-1000), "-1,000"}, |
||||
{"-100", bigComma(-100), "-100"}, |
||||
{"-10", bigComma(-10), "-10"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func TestVeryBigCommas(t *testing.T) { |
||||
tests := []struct{ in, exp string }{ |
||||
{ |
||||
"84889279597249724975972597249849757294578485", |
||||
"84,889,279,597,249,724,975,972,597,249,849,757,294,578,485", |
||||
}, |
||||
{ |
||||
"-84889279597249724975972597249849757294578485", |
||||
"-84,889,279,597,249,724,975,972,597,249,849,757,294,578,485", |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
n, _ := (&big.Int{}).SetString(test.in, 10) |
||||
got := BigComma(n) |
||||
if test.exp != got { |
||||
t.Errorf("Expected %q, got %q", test.exp, got) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
type testList []struct { |
||||
name, got, exp string |
||||
} |
||||
|
||||
func (tl testList) validate(t *testing.T) { |
||||
for _, test := range tl { |
||||
if test.got != test.exp { |
||||
t.Errorf("On %v, expected '%v', but got '%v'", |
||||
test.name, test.exp, test.got) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"fmt" |
||||
"regexp" |
||||
"strconv" |
||||
"testing" |
||||
) |
||||
|
||||
func TestFtoa(t *testing.T) { |
||||
testList{ |
||||
{"200", Ftoa(200), "200"}, |
||||
{"2", Ftoa(2), "2"}, |
||||
{"2.2", Ftoa(2.2), "2.2"}, |
||||
{"2.02", Ftoa(2.02), "2.02"}, |
||||
{"200.02", Ftoa(200.02), "200.02"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func BenchmarkFtoaRegexTrailing(b *testing.B) { |
||||
trailingZerosRegex := regexp.MustCompile(`\.?0+$`) |
||||
|
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
trailingZerosRegex.ReplaceAllString("2.00000", "") |
||||
trailingZerosRegex.ReplaceAllString("2.0000", "") |
||||
trailingZerosRegex.ReplaceAllString("2.000", "") |
||||
trailingZerosRegex.ReplaceAllString("2.00", "") |
||||
trailingZerosRegex.ReplaceAllString("2.0", "") |
||||
trailingZerosRegex.ReplaceAllString("2", "") |
||||
} |
||||
} |
||||
|
||||
func BenchmarkFtoaFunc(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
stripTrailingZeros("2.00000") |
||||
stripTrailingZeros("2.0000") |
||||
stripTrailingZeros("2.000") |
||||
stripTrailingZeros("2.00") |
||||
stripTrailingZeros("2.0") |
||||
stripTrailingZeros("2") |
||||
} |
||||
} |
||||
|
||||
func BenchmarkFmtF(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
fmt.Sprintf("%f", 2.03584) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkStrconvF(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
strconv.FormatFloat(2.03584, 'f', 6, 64) |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func TestOrdinals(t *testing.T) { |
||||
testList{ |
||||
{"0", Ordinal(0), "0th"}, |
||||
{"1", Ordinal(1), "1st"}, |
||||
{"2", Ordinal(2), "2nd"}, |
||||
{"3", Ordinal(3), "3rd"}, |
||||
{"4", Ordinal(4), "4th"}, |
||||
{"10", Ordinal(10), "10th"}, |
||||
{"11", Ordinal(11), "11th"}, |
||||
{"12", Ordinal(12), "12th"}, |
||||
{"13", Ordinal(13), "13th"}, |
||||
{"101", Ordinal(101), "101st"}, |
||||
{"102", Ordinal(102), "102nd"}, |
||||
{"103", Ordinal(103), "103rd"}, |
||||
}.validate(t) |
||||
} |
@ -0,0 +1,98 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"math" |
||||
"testing" |
||||
) |
||||
|
||||
func TestSI(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
num float64 |
||||
formatted string |
||||
}{ |
||||
{"e-24", 1e-24, "1yF"}, |
||||
{"e-21", 1e-21, "1zF"}, |
||||
{"e-18", 1e-18, "1aF"}, |
||||
{"e-15", 1e-15, "1fF"}, |
||||
{"e-12", 1e-12, "1pF"}, |
||||
{"e-12", 2.2345e-12, "2.2345pF"}, |
||||
{"e-12", 2.23e-12, "2.23pF"}, |
||||
{"e-11", 2.23e-11, "22.3pF"}, |
||||
{"e-10", 2.2e-10, "220pF"}, |
||||
{"e-9", 2.2e-9, "2.2nF"}, |
||||
{"e-8", 2.2e-8, "22nF"}, |
||||
{"e-7", 2.2e-7, "220nF"}, |
||||
{"e-6", 2.2e-6, "2.2µF"}, |
||||
{"e-6", 1e-6, "1µF"}, |
||||
{"e-5", 2.2e-5, "22µF"}, |
||||
{"e-4", 2.2e-4, "220µF"}, |
||||
{"e-3", 2.2e-3, "2.2mF"}, |
||||
{"e-2", 2.2e-2, "22mF"}, |
||||
{"e-1", 2.2e-1, "220mF"}, |
||||
{"e+0", 2.2e-0, "2.2F"}, |
||||
{"e+0", 2.2, "2.2F"}, |
||||
{"e+1", 2.2e+1, "22F"}, |
||||
{"0", 0, "0F"}, |
||||
{"e+1", 22, "22F"}, |
||||
{"e+2", 2.2e+2, "220F"}, |
||||
{"e+2", 220, "220F"}, |
||||
{"e+3", 2.2e+3, "2.2kF"}, |
||||
{"e+3", 2200, "2.2kF"}, |
||||
{"e+4", 2.2e+4, "22kF"}, |
||||
{"e+4", 22000, "22kF"}, |
||||
{"e+5", 2.2e+5, "220kF"}, |
||||
{"e+6", 2.2e+6, "2.2MF"}, |
||||
{"e+6", 1e+6, "1MF"}, |
||||
{"e+7", 2.2e+7, "22MF"}, |
||||
{"e+8", 2.2e+8, "220MF"}, |
||||
{"e+9", 2.2e+9, "2.2GF"}, |
||||
{"e+10", 2.2e+10, "22GF"}, |
||||
{"e+11", 2.2e+11, "220GF"}, |
||||
{"e+12", 2.2e+12, "2.2TF"}, |
||||
{"e+15", 2.2e+15, "2.2PF"}, |
||||
{"e+18", 2.2e+18, "2.2EF"}, |
||||
{"e+21", 2.2e+21, "2.2ZF"}, |
||||
{"e+24", 2.2e+24, "2.2YF"}, |
||||
|
||||
// special case
|
||||
{"1F", 1000 * 1000, "1MF"}, |
||||
{"1F", 1e6, "1MF"}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
got := SI(test.num, "F") |
||||
if got != test.formatted { |
||||
t.Errorf("On %v (%v), got %v, wanted %v", |
||||
test.name, test.num, got, test.formatted) |
||||
} |
||||
|
||||
gotf, gotu, err := ParseSI(test.formatted) |
||||
if err != nil { |
||||
t.Errorf("Error parsing %v (%v): %v", test.name, test.formatted, err) |
||||
continue |
||||
} |
||||
|
||||
if math.Abs(1-(gotf/test.num)) > 0.01 { |
||||
t.Errorf("On %v (%v), got %v, wanted %v (±%v)", |
||||
test.name, test.formatted, gotf, test.num, |
||||
math.Abs(1-(gotf/test.num))) |
||||
} |
||||
if gotu != "F" { |
||||
t.Errorf("On %v (%v), expected unit F, got %v", |
||||
test.name, test.formatted, gotu) |
||||
} |
||||
} |
||||
|
||||
// Parse error
|
||||
gotf, gotu, err := ParseSI("x1.21JW") // 1.21 jigga whats
|
||||
if err == nil { |
||||
t.Errorf("Expected error on x1.21JW, got %v %v", gotf, gotu) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkParseSI(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
ParseSI("2.2346ZB") |
||||
} |
||||
} |
@ -0,0 +1,71 @@ |
||||
package humanize |
||||
|
||||
import ( |
||||
"math" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestPast(t *testing.T) { |
||||
now := time.Now().Unix() |
||||
testList{ |
||||
{"now", Time(time.Unix(now, 0)), "now"}, |
||||
{"1 second ago", Time(time.Unix(now-1, 0)), "1 second ago"}, |
||||
{"12 seconds ago", Time(time.Unix(now-12, 0)), "12 seconds ago"}, |
||||
{"30 seconds ago", Time(time.Unix(now-30, 0)), "30 seconds ago"}, |
||||
{"45 seconds ago", Time(time.Unix(now-45, 0)), "45 seconds ago"}, |
||||
{"1 minute ago", Time(time.Unix(now-63, 0)), "1 minute ago"}, |
||||
{"15 minutes ago", Time(time.Unix(now-15*Minute, 0)), "15 minutes ago"}, |
||||
{"1 hour ago", Time(time.Unix(now-63*Minute, 0)), "1 hour ago"}, |
||||
{"2 hours ago", Time(time.Unix(now-2*Hour, 0)), "2 hours ago"}, |
||||
{"21 hours ago", Time(time.Unix(now-21*Hour, 0)), "21 hours ago"}, |
||||
{"1 day ago", Time(time.Unix(now-26*Hour, 0)), "1 day ago"}, |
||||
{"2 days ago", Time(time.Unix(now-49*Hour, 0)), "2 days ago"}, |
||||
{"3 days ago", Time(time.Unix(now-3*Day, 0)), "3 days ago"}, |
||||
{"1 week ago (1)", Time(time.Unix(now-7*Day, 0)), "1 week ago"}, |
||||
{"1 week ago (2)", Time(time.Unix(now-12*Day, 0)), "1 week ago"}, |
||||
{"2 weeks ago", Time(time.Unix(now-15*Day, 0)), "2 weeks ago"}, |
||||
{"1 month ago", Time(time.Unix(now-39*Day, 0)), "1 month ago"}, |
||||
{"3 months ago", Time(time.Unix(now-99*Day, 0)), "3 months ago"}, |
||||
{"1 year ago (1)", Time(time.Unix(now-365*Day, 0)), "1 year ago"}, |
||||
{"1 year ago (1)", Time(time.Unix(now-400*Day, 0)), "1 year ago"}, |
||||
{"2 years ago (1)", Time(time.Unix(now-548*Day, 0)), "2 years ago"}, |
||||
{"2 years ago (2)", Time(time.Unix(now-725*Day, 0)), "2 years ago"}, |
||||
{"2 years ago (3)", Time(time.Unix(now-800*Day, 0)), "2 years ago"}, |
||||
{"3 years ago", Time(time.Unix(now-3*Year, 0)), "3 years ago"}, |
||||
{"long ago", Time(time.Unix(now-LongTime, 0)), "a long while ago"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func TestFuture(t *testing.T) { |
||||
now := time.Now().Unix() |
||||
testList{ |
||||
{"now", Time(time.Unix(now, 0)), "now"}, |
||||
{"1 second from now", Time(time.Unix(now+1, 0)), "1 second from now"}, |
||||
{"12 seconds from now", Time(time.Unix(now+12, 0)), "12 seconds from now"}, |
||||
{"30 seconds from now", Time(time.Unix(now+30, 0)), "30 seconds from now"}, |
||||
{"45 seconds from now", Time(time.Unix(now+45, 0)), "45 seconds from now"}, |
||||
{"15 minutes from now", Time(time.Unix(now+15*Minute, 0)), "15 minutes from now"}, |
||||
{"2 hours from now", Time(time.Unix(now+2*Hour, 0)), "2 hours from now"}, |
||||
{"21 hours from now", Time(time.Unix(now+21*Hour, 0)), "21 hours from now"}, |
||||
{"1 day from now", Time(time.Unix(now+26*Hour, 0)), "1 day from now"}, |
||||
{"2 days from now", Time(time.Unix(now+49*Hour, 0)), "2 days from now"}, |
||||
{"3 days from now", Time(time.Unix(now+3*Day, 0)), "3 days from now"}, |
||||
{"1 week from now (1)", Time(time.Unix(now+7*Day, 0)), "1 week from now"}, |
||||
{"1 week from now (2)", Time(time.Unix(now+12*Day, 0)), "1 week from now"}, |
||||
{"2 weeks from now", Time(time.Unix(now+15*Day, 0)), "2 weeks from now"}, |
||||
{"1 month from now", Time(time.Unix(now+30*Day, 0)), "1 month from now"}, |
||||
{"1 year from now", Time(time.Unix(now+365*Day, 0)), "1 year from now"}, |
||||
{"2 years from now", Time(time.Unix(now+2*Year, 0)), "2 years from now"}, |
||||
{"a while from now", Time(time.Unix(now+LongTime, 0)), "a long while from now"}, |
||||
}.validate(t) |
||||
} |
||||
|
||||
func TestRange(t *testing.T) { |
||||
start := time.Time{} |
||||
end := time.Unix(math.MaxInt64, math.MaxInt64) |
||||
x := RelTime(start, end, "ago", "from now") |
||||
if x != "a long while from now" { |
||||
t.Errorf("Expected a long while from now, got %q", x) |
||||
} |
||||
} |
@ -0,0 +1,536 @@ |
||||
package clock_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"runtime" |
||||
"sync" |
||||
"sync/atomic" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/facebookgo/clock" |
||||
) |
||||
|
||||
// Ensure that the clock's After channel sends at the correct time.
|
||||
func TestClock_After(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
go func() { |
||||
time.Sleep(30 * time.Millisecond) |
||||
t.Fatal("too late") |
||||
}() |
||||
gosched() |
||||
|
||||
<-clock.New().After(20 * time.Millisecond) |
||||
if !ok { |
||||
t.Fatal("too early") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock's AfterFunc executes at the correct time.
|
||||
func TestClock_AfterFunc(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
go func() { |
||||
time.Sleep(30 * time.Millisecond) |
||||
t.Fatal("too late") |
||||
}() |
||||
gosched() |
||||
|
||||
var wg sync.WaitGroup |
||||
wg.Add(1) |
||||
clock.New().AfterFunc(20*time.Millisecond, func() { |
||||
wg.Done() |
||||
}) |
||||
wg.Wait() |
||||
if !ok { |
||||
t.Fatal("too early") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock's time matches the standary library.
|
||||
func TestClock_Now(t *testing.T) { |
||||
a := time.Now().Round(time.Second) |
||||
b := clock.New().Now().Round(time.Second) |
||||
if !a.Equal(b) { |
||||
t.Errorf("not equal: %s != %s", a, b) |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock sleeps for the appropriate amount of time.
|
||||
func TestClock_Sleep(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
go func() { |
||||
time.Sleep(30 * time.Millisecond) |
||||
t.Fatal("too late") |
||||
}() |
||||
gosched() |
||||
|
||||
clock.New().Sleep(20 * time.Millisecond) |
||||
if !ok { |
||||
t.Fatal("too early") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock ticks correctly.
|
||||
func TestClock_Tick(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
go func() { |
||||
time.Sleep(50 * time.Millisecond) |
||||
t.Fatal("too late") |
||||
}() |
||||
gosched() |
||||
|
||||
c := clock.New().Tick(20 * time.Millisecond) |
||||
<-c |
||||
<-c |
||||
if !ok { |
||||
t.Fatal("too early") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock's ticker ticks correctly.
|
||||
func TestClock_Ticker(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(100 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
go func() { |
||||
time.Sleep(200 * time.Millisecond) |
||||
t.Fatal("too late") |
||||
}() |
||||
gosched() |
||||
|
||||
ticker := clock.New().Ticker(50 * time.Millisecond) |
||||
<-ticker.C |
||||
<-ticker.C |
||||
if !ok { |
||||
t.Fatal("too early") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock's ticker can stop correctly.
|
||||
func TestClock_Ticker_Stp(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
gosched() |
||||
|
||||
ticker := clock.New().Ticker(20 * time.Millisecond) |
||||
<-ticker.C |
||||
ticker.Stop() |
||||
select { |
||||
case <-ticker.C: |
||||
t.Fatal("unexpected send") |
||||
case <-time.After(30 * time.Millisecond): |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock's timer waits correctly.
|
||||
func TestClock_Timer(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
go func() { |
||||
time.Sleep(30 * time.Millisecond) |
||||
t.Fatal("too late") |
||||
}() |
||||
gosched() |
||||
|
||||
timer := clock.New().Timer(20 * time.Millisecond) |
||||
<-timer.C |
||||
if !ok { |
||||
t.Fatal("too early") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the clock's timer can be stopped.
|
||||
func TestClock_Timer_Stop(t *testing.T) { |
||||
var ok bool |
||||
go func() { |
||||
time.Sleep(10 * time.Millisecond) |
||||
ok = true |
||||
}() |
||||
|
||||
timer := clock.New().Timer(20 * time.Millisecond) |
||||
timer.Stop() |
||||
select { |
||||
case <-timer.C: |
||||
t.Fatal("unexpected send") |
||||
case <-time.After(30 * time.Millisecond): |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock's After channel sends at the correct time.
|
||||
func TestMock_After(t *testing.T) { |
||||
var ok int32 |
||||
clock := clock.NewMock() |
||||
|
||||
// Create a channel to execute after 10 mock seconds.
|
||||
ch := clock.After(10 * time.Second) |
||||
go func(ch <-chan time.Time) { |
||||
<-ch |
||||
atomic.StoreInt32(&ok, 1) |
||||
}(ch) |
||||
|
||||
// Move clock forward to just before the time.
|
||||
clock.Add(9 * time.Second) |
||||
if atomic.LoadInt32(&ok) == 1 { |
||||
t.Fatal("too early") |
||||
} |
||||
|
||||
// Move clock forward to the after channel's time.
|
||||
clock.Add(1 * time.Second) |
||||
if atomic.LoadInt32(&ok) == 0 { |
||||
t.Fatal("too late") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock's AfterFunc executes at the correct time.
|
||||
func TestMock_AfterFunc(t *testing.T) { |
||||
var ok int32 |
||||
clock := clock.NewMock() |
||||
|
||||
// Execute function after duration.
|
||||
clock.AfterFunc(10*time.Second, func() { |
||||
atomic.StoreInt32(&ok, 1) |
||||
}) |
||||
|
||||
// Move clock forward to just before the time.
|
||||
clock.Add(9 * time.Second) |
||||
if atomic.LoadInt32(&ok) == 1 { |
||||
t.Fatal("too early") |
||||
} |
||||
|
||||
// Move clock forward to the after channel's time.
|
||||
clock.Add(1 * time.Second) |
||||
if atomic.LoadInt32(&ok) == 0 { |
||||
t.Fatal("too late") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock's AfterFunc doesn't execute if stopped.
|
||||
func TestMock_AfterFunc_Stop(t *testing.T) { |
||||
// Execute function after duration.
|
||||
clock := clock.NewMock() |
||||
timer := clock.AfterFunc(10*time.Second, func() { |
||||
t.Fatal("unexpected function execution") |
||||
}) |
||||
gosched() |
||||
|
||||
// Stop timer & move clock forward.
|
||||
timer.Stop() |
||||
clock.Add(10 * time.Second) |
||||
gosched() |
||||
} |
||||
|
||||
// Ensure that the mock's current time can be changed.
|
||||
func TestMock_Now(t *testing.T) { |
||||
clock := clock.NewMock() |
||||
if now := clock.Now(); !now.Equal(time.Unix(0, 0)) { |
||||
t.Fatalf("expected epoch, got: ", now) |
||||
} |
||||
|
||||
// Add 10 seconds and check the time.
|
||||
clock.Add(10 * time.Second) |
||||
if now := clock.Now(); !now.Equal(time.Unix(10, 0)) { |
||||
t.Fatalf("expected epoch, got: ", now) |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock can sleep for the correct time.
|
||||
func TestMock_Sleep(t *testing.T) { |
||||
var ok int32 |
||||
clock := clock.NewMock() |
||||
|
||||
// Create a channel to execute after 10 mock seconds.
|
||||
go func() { |
||||
clock.Sleep(10 * time.Second) |
||||
atomic.StoreInt32(&ok, 1) |
||||
}() |
||||
gosched() |
||||
|
||||
// Move clock forward to just before the sleep duration.
|
||||
clock.Add(9 * time.Second) |
||||
if atomic.LoadInt32(&ok) == 1 { |
||||
t.Fatal("too early") |
||||
} |
||||
|
||||
// Move clock forward to the after the sleep duration.
|
||||
clock.Add(1 * time.Second) |
||||
if atomic.LoadInt32(&ok) == 0 { |
||||
t.Fatal("too late") |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock's Tick channel sends at the correct time.
|
||||
func TestMock_Tick(t *testing.T) { |
||||
var n int32 |
||||
clock := clock.NewMock() |
||||
|
||||
// Create a channel to increment every 10 seconds.
|
||||
go func() { |
||||
tick := clock.Tick(10 * time.Second) |
||||
for { |
||||
<-tick |
||||
atomic.AddInt32(&n, 1) |
||||
} |
||||
}() |
||||
gosched() |
||||
|
||||
// Move clock forward to just before the first tick.
|
||||
clock.Add(9 * time.Second) |
||||
if atomic.LoadInt32(&n) != 0 { |
||||
t.Fatalf("expected 0, got %d", n) |
||||
} |
||||
|
||||
// Move clock forward to the start of the first tick.
|
||||
clock.Add(1 * time.Second) |
||||
if atomic.LoadInt32(&n) != 1 { |
||||
t.Fatalf("expected 1, got %d", n) |
||||
} |
||||
|
||||
// Move clock forward over several ticks.
|
||||
clock.Add(30 * time.Second) |
||||
if atomic.LoadInt32(&n) != 4 { |
||||
t.Fatalf("expected 4, got %d", n) |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock's Ticker channel sends at the correct time.
|
||||
func TestMock_Ticker(t *testing.T) { |
||||
var n int32 |
||||
clock := clock.NewMock() |
||||
|
||||
// Create a channel to increment every microsecond.
|
||||
go func() { |
||||
ticker := clock.Ticker(1 * time.Microsecond) |
||||
for { |
||||
<-ticker.C |
||||
atomic.AddInt32(&n, 1) |
||||
} |
||||
}() |
||||
gosched() |
||||
|
||||
// Move clock forward.
|
||||
clock.Add(10 * time.Microsecond) |
||||
if atomic.LoadInt32(&n) != 10 { |
||||
t.Fatalf("unexpected: %d", n) |
||||
} |
||||
} |
||||
|
||||
// Ensure that the mock's Ticker channel won't block if not read from.
|
||||
func TestMock_Ticker_Overflow(t *testing.T) { |
||||
clock := clock.NewMock() |
||||
ticker := clock.Ticker(1 * time.Microsecond) |
||||
clock.Add(10 * time.Microsecond) |
||||
ticker.Stop() |
||||
} |
||||
|
||||
// Ensure that the mock's Ticker can be stopped.
|
||||
func TestMock_Ticker_Stop(t *testing.T) { |
||||
var n int32 |
||||
clock := clock.NewMock() |
||||
|
||||
// Create a channel to increment every second.
|
||||
ticker := clock.Ticker(1 * time.Second) |
||||
go func() { |
||||
for { |
||||
<-ticker.C |
||||
atomic.AddInt32(&n, 1) |
||||
} |
||||
}() |
||||
gosched() |
||||
|
||||
// Move clock forward.
|
||||
clock.Add(5 * time.Second) |
||||
if atomic.LoadInt32(&n) != 5 { |
||||
t.Fatalf("expected 5, got: %d", n) |
||||
} |
||||
|
||||
ticker.Stop() |
||||
|
||||
// Move clock forward again.
|
||||
clock.Add(5 * time.Second) |
||||
if atomic.LoadInt32(&n) != 5 { |
||||
t.Fatalf("still expected 5, got: %d", n) |
||||
} |
||||
} |
||||
|
||||
// Ensure that multiple tickers can be used together.
|
||||
func TestMock_Ticker_Multi(t *testing.T) { |
||||
var n int32 |
||||
clock := clock.NewMock() |
||||
|
||||
go func() { |
||||
a := clock.Ticker(1 * time.Microsecond) |
||||
b := clock.Ticker(3 * time.Microsecond) |
||||
|
||||
for { |
||||
select { |
||||
case <-a.C: |
||||
atomic.AddInt32(&n, 1) |
||||
case <-b.C: |
||||
atomic.AddInt32(&n, 100) |
||||
} |
||||
} |
||||
}() |
||||
gosched() |
||||
|
||||
// Move clock forward.
|
||||
clock.Add(10 * time.Microsecond) |
||||
gosched() |
||||
if atomic.LoadInt32(&n) != 310 { |
||||
t.Fatalf("unexpected: %d", n) |
||||
} |
||||
} |
||||
|
||||
func ExampleMock_After() { |
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock() |
||||
count := 0 |
||||
|
||||
// Create a channel to execute after 10 mock seconds.
|
||||
go func() { |
||||
<-clock.After(10 * time.Second) |
||||
count = 100 |
||||
}() |
||||
runtime.Gosched() |
||||
|
||||
// Print the starting value.
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Move the clock forward 5 seconds and print the value again.
|
||||
clock.Add(5 * time.Second) |
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Move the clock forward 5 seconds to the tick time and check the value.
|
||||
clock.Add(5 * time.Second) |
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Output:
|
||||
// 1970-01-01 00:00:00 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:05 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:10 +0000 UTC: 100
|
||||
} |
||||
|
||||
func ExampleMock_AfterFunc() { |
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock() |
||||
count := 0 |
||||
|
||||
// Execute a function after 10 mock seconds.
|
||||
clock.AfterFunc(10*time.Second, func() { |
||||
count = 100 |
||||
}) |
||||
runtime.Gosched() |
||||
|
||||
// Print the starting value.
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second) |
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Output:
|
||||
// 1970-01-01 00:00:00 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:10 +0000 UTC: 100
|
||||
} |
||||
|
||||
func ExampleMock_Sleep() { |
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock() |
||||
count := 0 |
||||
|
||||
// Execute a function after 10 mock seconds.
|
||||
go func() { |
||||
clock.Sleep(10 * time.Second) |
||||
count = 100 |
||||
}() |
||||
runtime.Gosched() |
||||
|
||||
// Print the starting value.
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second) |
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count) |
||||
|
||||
// Output:
|
||||
// 1970-01-01 00:00:00 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:10 +0000 UTC: 100
|
||||
} |
||||
|
||||
func ExampleMock_Ticker() { |
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock() |
||||
count := 0 |
||||
|
||||
// Increment count every mock second.
|
||||
go func() { |
||||
ticker := clock.Ticker(1 * time.Second) |
||||
for { |
||||
<-ticker.C |
||||
count++ |
||||
} |
||||
}() |
||||
runtime.Gosched() |
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second) |
||||
fmt.Printf("Count is %d after 10 seconds\n", count) |
||||
|
||||
// Move the clock forward 5 more seconds and print the new value.
|
||||
clock.Add(5 * time.Second) |
||||
fmt.Printf("Count is %d after 15 seconds\n", count) |
||||
|
||||
// Output:
|
||||
// Count is 10 after 10 seconds
|
||||
// Count is 15 after 15 seconds
|
||||
} |
||||
|
||||
func ExampleMock_Timer() { |
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock() |
||||
count := 0 |
||||
|
||||
// Increment count after a mock second.
|
||||
go func() { |
||||
timer := clock.Timer(1 * time.Second) |
||||
<-timer.C |
||||
count++ |
||||
}() |
||||
runtime.Gosched() |
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second) |
||||
fmt.Printf("Count is %d after 10 seconds\n", count) |
||||
|
||||
// Output:
|
||||
// Count is 1 after 10 seconds
|
||||
} |
||||
|
||||
func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) } |
||||
func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) } |
||||
|
||||
func gosched() { time.Sleep(1 * time.Millisecond) } |
@ -0,0 +1,677 @@ |
||||
package httpdown_test |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/tls" |
||||
"errors" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net" |
||||
"net/http" |
||||
"os" |
||||
"regexp" |
||||
"sync" |
||||
"sync/atomic" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/facebookgo/clock" |
||||
"github.com/facebookgo/ensure" |
||||
"github.com/facebookgo/freeport" |
||||
"github.com/facebookgo/httpdown" |
||||
"github.com/facebookgo/stats" |
||||
) |
||||
|
||||
type onCloseListener struct { |
||||
net.Listener |
||||
mutex sync.Mutex |
||||
onClose chan struct{} |
||||
} |
||||
|
||||
func (o *onCloseListener) Close() error { |
||||
// Listener is closed twice, once by Grace, and once by the http library, so
|
||||
// we guard against a double close of the chan.
|
||||
defer func() { |
||||
o.mutex.Lock() |
||||
defer o.mutex.Unlock() |
||||
if o.onClose != nil { |
||||
close(o.onClose) |
||||
o.onClose = nil |
||||
} |
||||
}() |
||||
return o.Listener.Close() |
||||
} |
||||
|
||||
func NewOnCloseListener(l net.Listener) (net.Listener, chan struct{}) { |
||||
c := make(chan struct{}) |
||||
return &onCloseListener{Listener: l, onClose: c}, c |
||||
} |
||||
|
||||
type closeErrListener struct { |
||||
net.Listener |
||||
err error |
||||
} |
||||
|
||||
func (c *closeErrListener) Close() error { |
||||
c.Listener.Close() |
||||
return c.err |
||||
} |
||||
|
||||
type acceptErrListener struct { |
||||
net.Listener |
||||
err chan error |
||||
} |
||||
|
||||
func (c *acceptErrListener) Accept() (net.Conn, error) { |
||||
return nil, <-c.err |
||||
} |
||||
|
||||
type closeErrConn struct { |
||||
net.Conn |
||||
unblockClose chan chan struct{} |
||||
} |
||||
|
||||
func (c *closeErrConn) Close() error { |
||||
ch := <-c.unblockClose |
||||
|
||||
// Close gets called multiple times, but only the first one gets this ch
|
||||
if ch != nil { |
||||
defer close(ch) |
||||
} |
||||
|
||||
return c.Conn.Close() |
||||
} |
||||
|
||||
type closeErrConnListener struct { |
||||
net.Listener |
||||
unblockClose chan chan struct{} |
||||
} |
||||
|
||||
func (l *closeErrConnListener) Accept() (net.Conn, error) { |
||||
c, err := l.Listener.Accept() |
||||
if err != nil { |
||||
return c, err |
||||
} |
||||
return &closeErrConn{Conn: c, unblockClose: l.unblockClose}, nil |
||||
} |
||||
|
||||
func TestHTTPStopWithNoRequest(t *testing.T) { |
||||
t.Parallel() |
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
|
||||
statsDone := make(chan struct{}, 2) |
||||
hc := &stats.HookClient{ |
||||
BumpSumHook: func(key string, val float64) { |
||||
if key == "serve" && val == 1 { |
||||
statsDone <- struct{}{} |
||||
} |
||||
if key == "stop" && val == 1 { |
||||
statsDone <- struct{}{} |
||||
} |
||||
}, |
||||
} |
||||
|
||||
server := &http.Server{} |
||||
down := &httpdown.HTTP{Stats: hc} |
||||
s := down.Serve(server, listener) |
||||
ensure.Nil(t, s.Stop()) |
||||
<-statsDone |
||||
<-statsDone |
||||
} |
||||
|
||||
func TestHTTPStopWithFinishedRequest(t *testing.T) { |
||||
t.Parallel() |
||||
hello := []byte("hello") |
||||
fin := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(fin) |
||||
w.Write(hello) |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
actualBody, err := ioutil.ReadAll(res.Body) |
||||
ensure.Nil(t, err) |
||||
ensure.DeepEqual(t, actualBody, hello) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
|
||||
// At this point the request is finished, and the connection should be alive
|
||||
// but idle (because we have keep alive enabled by default in our Transport).
|
||||
ensure.Nil(t, s.Stop()) |
||||
<-fin |
||||
|
||||
ensure.Nil(t, s.Wait()) |
||||
} |
||||
|
||||
func TestHTTPStopWithActiveRequest(t *testing.T) { |
||||
t.Parallel() |
||||
const count = 10000 |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.WriteHeader(200) |
||||
for i := 0; i < count; i++ { |
||||
w.Write(hello) |
||||
} |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
actualBody, err := ioutil.ReadAll(res.Body) |
||||
ensure.Nil(t, err) |
||||
ensure.DeepEqual(t, actualBody, bytes.Repeat(hello, count)) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
<-finOkHandler |
||||
<-finStop |
||||
} |
||||
|
||||
func TestNewRequestAfterStop(t *testing.T) { |
||||
t.Parallel() |
||||
const count = 10000 |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
unblockOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.WriteHeader(200) |
||||
const diff = 500 |
||||
for i := 0; i < count-diff; i++ { |
||||
w.Write(hello) |
||||
} |
||||
<-unblockOkHandler |
||||
for i := 0; i < diff; i++ { |
||||
w.Write(hello) |
||||
} |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
listener, onClose := NewOnCloseListener(listener) |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
// Wait until the listener is closed.
|
||||
<-onClose |
||||
|
||||
// Now the next request should not be able to connect as the listener is
|
||||
// now closed.
|
||||
_, err = client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
|
||||
// We should just get "connection refused" here, but sometimes, very rarely,
|
||||
// we get a "connection reset" instead. Unclear why this happens.
|
||||
ensure.Err(t, err, regexp.MustCompile("(connection refused|connection reset by peer)$")) |
||||
|
||||
// Unblock the handler and ensure we finish writing the rest of the body
|
||||
// successfully.
|
||||
close(unblockOkHandler) |
||||
actualBody, err := ioutil.ReadAll(res.Body) |
||||
ensure.Nil(t, err) |
||||
ensure.DeepEqual(t, actualBody, bytes.Repeat(hello, count)) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
<-finOkHandler |
||||
<-finStop |
||||
} |
||||
|
||||
func TestHTTPListenerCloseError(t *testing.T) { |
||||
t.Parallel() |
||||
expectedError := errors.New("foo") |
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
listener = &closeErrListener{Listener: listener, err: expectedError} |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
ensure.DeepEqual(t, s.Stop(), expectedError) |
||||
} |
||||
|
||||
func TestHTTPServeError(t *testing.T) { |
||||
t.Parallel() |
||||
expectedError := errors.New("foo") |
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
errChan := make(chan error) |
||||
listener = &acceptErrListener{Listener: listener, err: errChan} |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
errChan <- expectedError |
||||
ensure.DeepEqual(t, s.Wait(), expectedError) |
||||
ensure.Nil(t, s.Stop()) |
||||
} |
||||
|
||||
func TestHTTPWithinStopTimeout(t *testing.T) { |
||||
t.Parallel() |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.WriteHeader(200) |
||||
w.Write(hello) |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{StopTimeout: time.Minute} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
actualBody, err := ioutil.ReadAll(res.Body) |
||||
ensure.Nil(t, err) |
||||
ensure.DeepEqual(t, actualBody, hello) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
<-finOkHandler |
||||
<-finStop |
||||
} |
||||
|
||||
func TestHTTPStopTimeoutMissed(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
klock := clock.NewMock() |
||||
|
||||
const count = 10000 |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
unblockOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.Header().Set("Content-Length", fmt.Sprint(len(hello)*count)) |
||||
w.WriteHeader(200) |
||||
for i := 0; i < count/2; i++ { |
||||
w.Write(hello) |
||||
} |
||||
<-unblockOkHandler |
||||
for i := 0; i < count/2; i++ { |
||||
w.Write(hello) |
||||
} |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{ |
||||
StopTimeout: time.Minute, |
||||
Clock: klock, |
||||
} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
klock.Wait(clock.Calls{After: 1}) // wait for Stop to call After
|
||||
klock.Add(down.StopTimeout) |
||||
|
||||
_, err = ioutil.ReadAll(res.Body) |
||||
ensure.Err(t, err, regexp.MustCompile("^unexpected EOF$")) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
close(unblockOkHandler) |
||||
<-finOkHandler |
||||
<-finStop |
||||
} |
||||
|
||||
func TestHTTPKillTimeout(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
klock := clock.NewMock() |
||||
|
||||
statsDone := make(chan struct{}, 1) |
||||
hc := &stats.HookClient{ |
||||
BumpSumHook: func(key string, val float64) { |
||||
if key == "kill" && val == 1 { |
||||
statsDone <- struct{}{} |
||||
} |
||||
}, |
||||
} |
||||
|
||||
const count = 10000 |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
unblockOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.Header().Set("Content-Length", fmt.Sprint(len(hello)*count)) |
||||
w.WriteHeader(200) |
||||
for i := 0; i < count/2; i++ { |
||||
w.Write(hello) |
||||
} |
||||
<-unblockOkHandler |
||||
for i := 0; i < count/2; i++ { |
||||
w.Write(hello) |
||||
} |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{ |
||||
StopTimeout: time.Minute, |
||||
KillTimeout: time.Minute, |
||||
Stats: hc, |
||||
Clock: klock, |
||||
} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
klock.Wait(clock.Calls{After: 1}) // wait for Stop to call After
|
||||
klock.Add(down.StopTimeout) |
||||
|
||||
_, err = ioutil.ReadAll(res.Body) |
||||
ensure.Err(t, err, regexp.MustCompile("^unexpected EOF$")) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
close(unblockOkHandler) |
||||
<-finOkHandler |
||||
<-finStop |
||||
<-statsDone |
||||
} |
||||
|
||||
func TestHTTPKillTimeoutMissed(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
klock := clock.NewMock() |
||||
|
||||
statsDone := make(chan struct{}, 1) |
||||
hc := &stats.HookClient{ |
||||
BumpSumHook: func(key string, val float64) { |
||||
if key == "kill.timeout" && val == 1 { |
||||
statsDone <- struct{}{} |
||||
} |
||||
}, |
||||
} |
||||
|
||||
const count = 10000 |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
unblockOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.Header().Set("Content-Length", fmt.Sprint(len(hello)*count)) |
||||
w.WriteHeader(200) |
||||
for i := 0; i < count/2; i++ { |
||||
w.Write(hello) |
||||
} |
||||
<-unblockOkHandler |
||||
for i := 0; i < count/2; i++ { |
||||
w.Write(hello) |
||||
} |
||||
} |
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
unblockConnClose := make(chan chan struct{}, 1) |
||||
listener = &closeErrConnListener{ |
||||
Listener: listener, |
||||
unblockClose: unblockConnClose, |
||||
} |
||||
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{ |
||||
StopTimeout: time.Minute, |
||||
KillTimeout: time.Minute, |
||||
Stats: hc, |
||||
Clock: klock, |
||||
} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
|
||||
// Start the Stop process.
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
klock.Wait(clock.Calls{After: 1}) // wait for Stop to call After
|
||||
klock.Add(down.StopTimeout) // trigger stop timeout
|
||||
klock.Wait(clock.Calls{After: 2}) // wait for Kill to call After
|
||||
klock.Add(down.KillTimeout) // trigger kill timeout
|
||||
|
||||
// We hit both the StopTimeout & the KillTimeout.
|
||||
<-finStop |
||||
|
||||
// Then we unblock the Close, so we get an unexpected EOF since we close
|
||||
// before we finish writing the response.
|
||||
connCloseDone := make(chan struct{}) |
||||
unblockConnClose <- connCloseDone |
||||
<-connCloseDone |
||||
close(unblockConnClose) |
||||
|
||||
// Then we unblock the handler which tries to write the rest of the data.
|
||||
close(unblockOkHandler) |
||||
|
||||
_, err = ioutil.ReadAll(res.Body) |
||||
ensure.Err(t, err, regexp.MustCompile("^unexpected EOF$")) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
<-finOkHandler |
||||
<-statsDone |
||||
} |
||||
|
||||
func TestDoubleStop(t *testing.T) { |
||||
t.Parallel() |
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
ensure.Nil(t, s.Stop()) |
||||
ensure.Nil(t, s.Stop()) |
||||
} |
||||
|
||||
func TestExistingConnState(t *testing.T) { |
||||
t.Parallel() |
||||
hello := []byte("hello") |
||||
fin := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(fin) |
||||
w.Write(hello) |
||||
} |
||||
|
||||
var called int32 |
||||
listener, err := net.Listen("tcp", "127.0.0.1:0") |
||||
ensure.Nil(t, err) |
||||
server := &http.Server{ |
||||
Handler: http.HandlerFunc(okHandler), |
||||
ConnState: func(c net.Conn, s http.ConnState) { |
||||
atomic.AddInt32(&called, 1) |
||||
}, |
||||
} |
||||
transport := &http.Transport{} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{} |
||||
s := down.Serve(server, listener) |
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String())) |
||||
ensure.Nil(t, err) |
||||
actualBody, err := ioutil.ReadAll(res.Body) |
||||
ensure.Nil(t, err) |
||||
ensure.DeepEqual(t, actualBody, hello) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
|
||||
ensure.Nil(t, s.Stop()) |
||||
<-fin |
||||
|
||||
ensure.True(t, atomic.LoadInt32(&called) > 0) |
||||
} |
||||
|
||||
func TestHTTPDefaultListenError(t *testing.T) { |
||||
if os.Getuid() == 0 { |
||||
t.Skip("cant run this test as root") |
||||
} |
||||
|
||||
statsDone := make(chan struct{}, 1) |
||||
hc := &stats.HookClient{ |
||||
BumpSumHook: func(key string, val float64) { |
||||
if key == "listen.error" && val == 1 { |
||||
statsDone <- struct{}{} |
||||
} |
||||
}, |
||||
} |
||||
|
||||
t.Parallel() |
||||
down := &httpdown.HTTP{Stats: hc} |
||||
_, err := down.ListenAndServe(&http.Server{}) |
||||
ensure.Err(t, err, regexp.MustCompile("listen tcp :80: bind: permission denied")) |
||||
<-statsDone |
||||
} |
||||
|
||||
func TestHTTPSDefaultListenError(t *testing.T) { |
||||
if os.Getuid() == 0 { |
||||
t.Skip("cant run this test as root") |
||||
} |
||||
t.Parallel() |
||||
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey) |
||||
if err != nil { |
||||
t.Fatalf("error loading cert: %v", err) |
||||
} |
||||
|
||||
down := &httpdown.HTTP{} |
||||
_, err = down.ListenAndServe(&http.Server{ |
||||
TLSConfig: &tls.Config{ |
||||
NextProtos: []string{"http/1.1"}, |
||||
Certificates: []tls.Certificate{cert}, |
||||
}, |
||||
}) |
||||
ensure.Err(t, err, regexp.MustCompile("listen tcp :443: bind: permission denied")) |
||||
} |
||||
|
||||
func TestTLS(t *testing.T) { |
||||
t.Parallel() |
||||
port, err := freeport.Get() |
||||
ensure.Nil(t, err) |
||||
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey) |
||||
if err != nil { |
||||
t.Fatalf("error loading cert: %v", err) |
||||
} |
||||
const count = 10000 |
||||
hello := []byte("hello") |
||||
finOkHandler := make(chan struct{}) |
||||
okHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
defer close(finOkHandler) |
||||
w.WriteHeader(200) |
||||
for i := 0; i < count; i++ { |
||||
w.Write(hello) |
||||
} |
||||
} |
||||
|
||||
server := &http.Server{ |
||||
Addr: fmt.Sprintf("0.0.0.0:%d", port), |
||||
Handler: http.HandlerFunc(okHandler), |
||||
TLSConfig: &tls.Config{ |
||||
NextProtos: []string{"http/1.1"}, |
||||
Certificates: []tls.Certificate{cert}, |
||||
}, |
||||
} |
||||
transport := &http.Transport{ |
||||
TLSClientConfig: &tls.Config{ |
||||
InsecureSkipVerify: true, |
||||
}, |
||||
} |
||||
client := &http.Client{Transport: transport} |
||||
down := &httpdown.HTTP{} |
||||
s, err := down.ListenAndServe(server) |
||||
ensure.Nil(t, err) |
||||
res, err := client.Get(fmt.Sprintf("https://%s/", server.Addr)) |
||||
ensure.Nil(t, err) |
||||
|
||||
finStop := make(chan struct{}) |
||||
go func() { |
||||
defer close(finStop) |
||||
ensure.Nil(t, s.Stop()) |
||||
}() |
||||
|
||||
actualBody, err := ioutil.ReadAll(res.Body) |
||||
ensure.Nil(t, err) |
||||
ensure.DeepEqual(t, actualBody, bytes.Repeat(hello, count)) |
||||
ensure.Nil(t, res.Body.Close()) |
||||
<-finOkHandler |
||||
<-finStop |
||||
} |
||||
|
||||
// localhostCert is a PEM-encoded TLS cert with SAN IPs
|
||||
// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
|
||||
// of ASN.1 time).
|
||||
// generated from src/pkg/crypto/tls:
|
||||
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var localhostCert = []byte(`-----BEGIN CERTIFICATE----- |
||||
MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD |
||||
bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj |
||||
bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBALyCfqwwip8BvTKgVKGdmjZTU8DD |
||||
ndR+WALmFPIRqn89bOU3s30olKiqYEju/SFoEvMyFRT/TWEhXHDaufThqaMCAwEA |
||||
AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud |
||||
EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA |
||||
AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAr/09uy108p51rheIOSnz4zgduyTl |
||||
M+4AmRo8/U1twEZLgfAGG/GZjREv2y4mCEUIM3HebCAqlA5jpRg76Rf8jw== |
||||
-----END CERTIFICATE-----`) |
||||
|
||||
// localhostKey is the private key for localhostCert.
|
||||
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- |
||||
MIIBOQIBAAJBALyCfqwwip8BvTKgVKGdmjZTU8DDndR+WALmFPIRqn89bOU3s30o |
||||
lKiqYEju/SFoEvMyFRT/TWEhXHDaufThqaMCAwEAAQJAPXuWUxTV8XyAt8VhNQER |
||||
LgzJcUKb9JVsoS1nwXgPksXnPDKnL9ax8VERrdNr+nZbj2Q9cDSXBUovfdtehcdP |
||||
qQIhAO48ZsPylbTrmtjDEKiHT2Ik04rLotZYS2U873J6I7WlAiEAypDjYxXyafv/ |
||||
Yo1pm9onwcetQKMW8CS3AjuV9Axzj6cCIEx2Il19fEMG4zny0WPlmbrcKvD/DpJQ |
||||
4FHrzsYlIVTpAiAas7S1uAvneqd0l02HlN9OxQKKlbUNXNme+rnOnOGS2wIgS0jW |
||||
zl1jvrOSJeP1PpAHohWz6LOhEr8uvltWkN6x3vE= |
||||
-----END RSA PRIVATE KEY-----`) |
@ -0,0 +1,77 @@ |
||||
package stats_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/facebookgo/ensure" |
||||
"github.com/facebookgo/stats" |
||||
) |
||||
|
||||
// Ensure calling End works even when a BumpTimeHook isn't provided.
|
||||
func TestHookClientBumpTime(t *testing.T) { |
||||
(&stats.HookClient{}).BumpTime("foo").End() |
||||
} |
||||
|
||||
func TestPrefixClient(t *testing.T) { |
||||
const ( |
||||
prefix1 = "prefix1" |
||||
prefix2 = "prefix2" |
||||
avgKey = "avg" |
||||
avgVal = float64(1) |
||||
sumKey = "sum" |
||||
sumVal = float64(2) |
||||
histogramKey = "histogram" |
||||
histogramVal = float64(3) |
||||
timeKey = "time" |
||||
) |
||||
|
||||
var keys []string |
||||
hc := &stats.HookClient{ |
||||
BumpAvgHook: func(key string, val float64) { |
||||
keys = append(keys, key) |
||||
ensure.DeepEqual(t, val, avgVal) |
||||
}, |
||||
BumpSumHook: func(key string, val float64) { |
||||
keys = append(keys, key) |
||||
ensure.DeepEqual(t, val, sumVal) |
||||
}, |
||||
BumpHistogramHook: func(key string, val float64) { |
||||
keys = append(keys, key) |
||||
ensure.DeepEqual(t, val, histogramVal) |
||||
}, |
||||
BumpTimeHook: func(key string) interface { |
||||
End() |
||||
} { |
||||
return multiEnderTest{ |
||||
EndHook: func() { |
||||
keys = append(keys, key) |
||||
}, |
||||
} |
||||
}, |
||||
} |
||||
|
||||
pc := stats.PrefixClient([]string{prefix1, prefix2}, hc) |
||||
pc.BumpAvg(avgKey, avgVal) |
||||
pc.BumpSum(sumKey, sumVal) |
||||
pc.BumpHistogram(histogramKey, histogramVal) |
||||
pc.BumpTime(timeKey).End() |
||||
|
||||
ensure.SameElements(t, keys, []string{ |
||||
prefix1 + avgKey, |
||||
prefix1 + sumKey, |
||||
prefix1 + histogramKey, |
||||
prefix1 + timeKey, |
||||
prefix2 + avgKey, |
||||
prefix2 + sumKey, |
||||
prefix2 + histogramKey, |
||||
prefix2 + timeKey, |
||||
}) |
||||
} |
||||
|
||||
type multiEnderTest struct { |
||||
EndHook func() |
||||
} |
||||
|
||||
func (e multiEnderTest) End() { |
||||
e.EndHook() |
||||
} |
0
internal/github.com/fatih/structs/LICENSE → vendor/github.com/fatih/structs/LICENSE
generated
vendored
0
internal/github.com/fatih/structs/LICENSE → vendor/github.com/fatih/structs/LICENSE
generated
vendored
@ -0,0 +1,324 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
// A test struct that defines all cases
|
||||
type Foo struct { |
||||
A string |
||||
B int `structs:"y"` |
||||
C bool `json:"c"` |
||||
d string // not exported
|
||||
E *Baz |
||||
x string `xml:"x"` // not exported, with tag
|
||||
Y []string |
||||
Z map[string]interface{} |
||||
*Bar // embedded
|
||||
} |
||||
|
||||
type Baz struct { |
||||
A string |
||||
B int |
||||
} |
||||
|
||||
type Bar struct { |
||||
E string |
||||
F int |
||||
g []string |
||||
} |
||||
|
||||
func newStruct() *Struct { |
||||
b := &Bar{ |
||||
E: "example", |
||||
F: 2, |
||||
g: []string{"zeynep", "fatih"}, |
||||
} |
||||
|
||||
// B and x is not initialized for testing
|
||||
f := &Foo{ |
||||
A: "gopher", |
||||
C: true, |
||||
d: "small", |
||||
E: nil, |
||||
Y: []string{"example"}, |
||||
Z: nil, |
||||
} |
||||
f.Bar = b |
||||
|
||||
return New(f) |
||||
} |
||||
|
||||
func TestField_Set(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
f := s.Field("A") |
||||
err := f.Set("fatih") |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
if f.Value().(string) != "fatih" { |
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") |
||||
} |
||||
|
||||
f = s.Field("Y") |
||||
err = f.Set([]string{"override", "with", "this"}) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
sliceLen := len(f.Value().([]string)) |
||||
if sliceLen != 3 { |
||||
t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3) |
||||
} |
||||
|
||||
f = s.Field("C") |
||||
err = f.Set(false) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
if f.Value().(bool) { |
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(bool), false) |
||||
} |
||||
|
||||
// let's pass a different type
|
||||
f = s.Field("A") |
||||
err = f.Set(123) // Field A is of type string, but we are going to pass an integer
|
||||
if err == nil { |
||||
t.Error("Setting a field's value with a different type than the field's type should return an error") |
||||
} |
||||
|
||||
// old value should be still there :)
|
||||
if f.Value().(string) != "fatih" { |
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") |
||||
} |
||||
|
||||
// let's access an unexported field, which should give an error
|
||||
f = s.Field("d") |
||||
err = f.Set("large") |
||||
if err != errNotExported { |
||||
t.Error(err) |
||||
} |
||||
|
||||
// let's set a pointer to struct
|
||||
b := &Bar{ |
||||
E: "gopher", |
||||
F: 2, |
||||
} |
||||
|
||||
f = s.Field("Bar") |
||||
err = f.Set(b) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
baz := &Baz{ |
||||
A: "helloWorld", |
||||
B: 42, |
||||
} |
||||
|
||||
f = s.Field("E") |
||||
err = f.Set(baz) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
ba := s.Field("E").Value().(*Baz) |
||||
|
||||
if ba.A != "helloWorld" { |
||||
t.Errorf("could not set baz. Got: %s Want: helloWorld", ba.A) |
||||
} |
||||
} |
||||
|
||||
func TestField(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Retrieveing a non existing field from the struct should panic") |
||||
} |
||||
}() |
||||
|
||||
_ = s.Field("no-field") |
||||
} |
||||
|
||||
func TestField_Kind(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
f := s.Field("A") |
||||
if f.Kind() != reflect.String { |
||||
t.Errorf("Field A has wrong kind: %s want: %s", f.Kind(), reflect.String) |
||||
} |
||||
|
||||
f = s.Field("B") |
||||
if f.Kind() != reflect.Int { |
||||
t.Errorf("Field B has wrong kind: %s want: %s", f.Kind(), reflect.Int) |
||||
} |
||||
|
||||
// unexported
|
||||
f = s.Field("d") |
||||
if f.Kind() != reflect.String { |
||||
t.Errorf("Field d has wrong kind: %s want: %s", f.Kind(), reflect.String) |
||||
} |
||||
} |
||||
|
||||
func TestField_Tag(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
v := s.Field("B").Tag("json") |
||||
if v != "" { |
||||
t.Errorf("Field's tag value of a non existing tag should return empty, got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("C").Tag("json") |
||||
if v != "c" { |
||||
t.Errorf("Field's tag value of the existing field C should return 'c', got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("d").Tag("json") |
||||
if v != "" { |
||||
t.Errorf("Field's tag value of a non exported field should return empty, got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("x").Tag("xml") |
||||
if v != "x" { |
||||
t.Errorf("Field's tag value of a non exported field with a tag should return 'x', got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("A").Tag("json") |
||||
if v != "" { |
||||
t.Errorf("Field's tag value of a existing field without a tag should return empty, got: %s", v) |
||||
} |
||||
} |
||||
|
||||
func TestField_Value(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
v := s.Field("A").Value() |
||||
val, ok := v.(string) |
||||
if !ok { |
||||
t.Errorf("Field's value of a A should be string") |
||||
} |
||||
|
||||
if val != "gopher" { |
||||
t.Errorf("Field's value of a existing tag should return 'gopher', got: %s", val) |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Value of a non exported field from the field should panic") |
||||
} |
||||
}() |
||||
|
||||
// should panic
|
||||
_ = s.Field("d").Value() |
||||
} |
||||
|
||||
func TestField_IsEmbedded(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if !s.Field("Bar").IsEmbedded() { |
||||
t.Errorf("Fields 'Bar' field is an embedded field") |
||||
} |
||||
|
||||
if s.Field("d").IsEmbedded() { |
||||
t.Errorf("Fields 'd' field is not an embedded field") |
||||
} |
||||
} |
||||
|
||||
func TestField_IsExported(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if !s.Field("Bar").IsExported() { |
||||
t.Errorf("Fields 'Bar' field is an exported field") |
||||
} |
||||
|
||||
if !s.Field("A").IsExported() { |
||||
t.Errorf("Fields 'A' field is an exported field") |
||||
} |
||||
|
||||
if s.Field("d").IsExported() { |
||||
t.Errorf("Fields 'd' field is not an exported field") |
||||
} |
||||
} |
||||
|
||||
func TestField_IsZero(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if s.Field("A").IsZero() { |
||||
t.Errorf("Fields 'A' field is an initialized field") |
||||
} |
||||
|
||||
if !s.Field("B").IsZero() { |
||||
t.Errorf("Fields 'B' field is not an initialized field") |
||||
} |
||||
} |
||||
|
||||
func TestField_Name(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if s.Field("A").Name() != "A" { |
||||
t.Errorf("Fields 'A' field should have the name 'A'") |
||||
} |
||||
} |
||||
|
||||
func TestField_Field(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
e := s.Field("Bar").Field("E") |
||||
|
||||
val, ok := e.Value().(string) |
||||
if !ok { |
||||
t.Error("The value of the field 'e' inside 'Bar' struct should be string") |
||||
} |
||||
|
||||
if val != "example" { |
||||
t.Errorf("The value of 'e' should be 'example, got: %s", val) |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Field of a non existing nested struct should panic") |
||||
} |
||||
}() |
||||
|
||||
_ = s.Field("Bar").Field("e") |
||||
} |
||||
|
||||
func TestField_Fields(t *testing.T) { |
||||
s := newStruct() |
||||
fields := s.Field("Bar").Fields() |
||||
|
||||
if len(fields) != 3 { |
||||
t.Errorf("We expect 3 fields in embedded struct, was: %d", len(fields)) |
||||
} |
||||
} |
||||
|
||||
func TestField_FieldOk(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
b, ok := s.FieldOk("Bar") |
||||
if !ok { |
||||
t.Error("The field 'Bar' should exists.") |
||||
} |
||||
|
||||
e, ok := b.FieldOk("E") |
||||
if !ok { |
||||
t.Error("The field 'E' should exists.") |
||||
} |
||||
|
||||
val, ok := e.Value().(string) |
||||
if !ok { |
||||
t.Error("The value of the field 'e' inside 'Bar' struct should be string") |
||||
} |
||||
|
||||
if val != "example" { |
||||
t.Errorf("The value of 'e' should be 'example, got: %s", val) |
||||
} |
||||
} |
@ -0,0 +1,351 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
func ExampleNew() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
server := &Server{ |
||||
Name: "Arslan", |
||||
ID: 123456, |
||||
Enabled: true, |
||||
} |
||||
|
||||
s := New(server) |
||||
|
||||
fmt.Printf("Name : %v\n", s.Name()) |
||||
fmt.Printf("Values : %v\n", s.Values()) |
||||
fmt.Printf("Value of ID : %v\n", s.Field("ID").Value()) |
||||
// Output:
|
||||
// Name : Server
|
||||
// Values : [Arslan 123456 true]
|
||||
// Value of ID : 123456
|
||||
|
||||
} |
||||
|
||||
func ExampleMap() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Arslan", |
||||
ID: 123456, |
||||
Enabled: true, |
||||
} |
||||
|
||||
m := Map(s) |
||||
|
||||
fmt.Printf("%#v\n", m["Name"]) |
||||
fmt.Printf("%#v\n", m["ID"]) |
||||
fmt.Printf("%#v\n", m["Enabled"]) |
||||
// Output:
|
||||
// "Arslan"
|
||||
// 123456
|
||||
// true
|
||||
|
||||
} |
||||
|
||||
func ExampleMap_tags() { |
||||
// Custom tags can change the map keys instead of using the fields name
|
||||
type Server struct { |
||||
Name string `structs:"server_name"` |
||||
ID int32 `structs:"server_id"` |
||||
Enabled bool `structs:"enabled"` |
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Zeynep", |
||||
ID: 789012, |
||||
} |
||||
|
||||
m := Map(s) |
||||
|
||||
// access them by the custom tags defined above
|
||||
fmt.Printf("%#v\n", m["server_name"]) |
||||
fmt.Printf("%#v\n", m["server_id"]) |
||||
fmt.Printf("%#v\n", m["enabled"]) |
||||
// Output:
|
||||
// "Zeynep"
|
||||
// 789012
|
||||
// false
|
||||
|
||||
} |
||||
|
||||
func ExampleMap_nested() { |
||||
// By default field with struct types are processed too. We can stop
|
||||
// processing them via "omitnested" tag option.
|
||||
type Server struct { |
||||
Name string `structs:"server_name"` |
||||
ID int32 `structs:"server_id"` |
||||
Time time.Time `structs:"time,omitnested"` // do not convert to map[string]interface{}
|
||||
} |
||||
|
||||
const shortForm = "2006-Jan-02" |
||||
t, _ := time.Parse("2006-Jan-02", "2013-Feb-03") |
||||
|
||||
s := &Server{ |
||||
Name: "Zeynep", |
||||
ID: 789012, |
||||
Time: t, |
||||
} |
||||
|
||||
m := Map(s) |
||||
|
||||
// access them by the custom tags defined above
|
||||
fmt.Printf("%v\n", m["server_name"]) |
||||
fmt.Printf("%v\n", m["server_id"]) |
||||
fmt.Printf("%v\n", m["time"].(time.Time)) |
||||
// Output:
|
||||
// Zeynep
|
||||
// 789012
|
||||
// 2013-02-03 00:00:00 +0000 UTC
|
||||
} |
||||
|
||||
func ExampleMap_omitEmpty() { |
||||
// By default field with struct types of zero values are processed too. We
|
||||
// can stop processing them via "omitempty" tag option.
|
||||
type Server struct { |
||||
Name string `structs:",omitempty"` |
||||
ID int32 `structs:"server_id,omitempty"` |
||||
Location string |
||||
} |
||||
|
||||
// Only add location
|
||||
s := &Server{ |
||||
Location: "Tokyo", |
||||
} |
||||
|
||||
m := Map(s) |
||||
|
||||
// map contains only the Location field
|
||||
fmt.Printf("%v\n", m) |
||||
// Output:
|
||||
// map[Location:Tokyo]
|
||||
} |
||||
|
||||
func ExampleValues() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Fatih", |
||||
ID: 135790, |
||||
Enabled: false, |
||||
} |
||||
|
||||
m := Values(s) |
||||
|
||||
fmt.Printf("Values: %+v\n", m) |
||||
// Output:
|
||||
// Values: [Fatih 135790 false]
|
||||
} |
||||
|
||||
func ExampleValues_omitEmpty() { |
||||
// By default field with struct types of zero values are processed too. We
|
||||
// can stop processing them via "omitempty" tag option.
|
||||
type Server struct { |
||||
Name string `structs:",omitempty"` |
||||
ID int32 `structs:"server_id,omitempty"` |
||||
Location string |
||||
} |
||||
|
||||
// Only add location
|
||||
s := &Server{ |
||||
Location: "Ankara", |
||||
} |
||||
|
||||
m := Values(s) |
||||
|
||||
// values contains only the Location field
|
||||
fmt.Printf("Values: %+v\n", m) |
||||
// Output:
|
||||
// Values: [Ankara]
|
||||
} |
||||
|
||||
func ExampleValues_tags() { |
||||
type Location struct { |
||||
City string |
||||
Country string |
||||
} |
||||
|
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
Location Location `structs:"-"` // values from location are not included anymore
|
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Fatih", |
||||
ID: 135790, |
||||
Enabled: false, |
||||
Location: Location{City: "Ankara", Country: "Turkey"}, |
||||
} |
||||
|
||||
// Let get all values from the struct s. Note that we don't include values
|
||||
// from the Location field
|
||||
m := Values(s) |
||||
|
||||
fmt.Printf("Values: %+v\n", m) |
||||
// Output:
|
||||
// Values: [Fatih 135790 false]
|
||||
} |
||||
|
||||
func ExampleFields() { |
||||
type Access struct { |
||||
Name string |
||||
LastAccessed time.Time |
||||
Number int |
||||
} |
||||
|
||||
s := &Access{ |
||||
Name: "Fatih", |
||||
LastAccessed: time.Now(), |
||||
Number: 1234567, |
||||
} |
||||
|
||||
fields := Fields(s) |
||||
|
||||
for i, field := range fields { |
||||
fmt.Printf("[%d] %+v\n", i, field.Name()) |
||||
} |
||||
|
||||
// Output:
|
||||
// [0] Name
|
||||
// [1] LastAccessed
|
||||
// [2] Number
|
||||
} |
||||
|
||||
func ExampleFields_nested() { |
||||
type Person struct { |
||||
Name string |
||||
Number int |
||||
} |
||||
|
||||
type Access struct { |
||||
Person Person |
||||
HasPermission bool |
||||
LastAccessed time.Time |
||||
} |
||||
|
||||
s := &Access{ |
||||
Person: Person{Name: "fatih", Number: 1234567}, |
||||
LastAccessed: time.Now(), |
||||
HasPermission: true, |
||||
} |
||||
|
||||
// Let's get all fields from the struct s.
|
||||
fields := Fields(s) |
||||
|
||||
for _, field := range fields { |
||||
if field.Name() == "Person" { |
||||
fmt.Printf("Access.Person.Name: %+v\n", field.Field("Name").Value()) |
||||
} |
||||
} |
||||
|
||||
// Output:
|
||||
// Access.Person.Name: fatih
|
||||
} |
||||
|
||||
func ExampleField() { |
||||
type Person struct { |
||||
Name string |
||||
Number int |
||||
} |
||||
|
||||
type Access struct { |
||||
Person Person |
||||
HasPermission bool |
||||
LastAccessed time.Time |
||||
} |
||||
|
||||
access := &Access{ |
||||
Person: Person{Name: "fatih", Number: 1234567}, |
||||
LastAccessed: time.Now(), |
||||
HasPermission: true, |
||||
} |
||||
|
||||
// Create a new Struct type
|
||||
s := New(access) |
||||
|
||||
// Get the Field type for "Person" field
|
||||
p := s.Field("Person") |
||||
|
||||
// Get the underlying "Name field" and print the value of it
|
||||
name := p.Field("Name") |
||||
|
||||
fmt.Printf("Value of Person.Access.Name: %+v\n", name.Value()) |
||||
|
||||
// Output:
|
||||
// Value of Person.Access.Name: fatih
|
||||
|
||||
} |
||||
|
||||
func ExampleIsZero() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
// Nothing is initalized
|
||||
a := &Server{} |
||||
isZeroA := IsZero(a) |
||||
|
||||
// Name and Enabled is initialized, but not ID
|
||||
b := &Server{ |
||||
Name: "Golang", |
||||
Enabled: true, |
||||
} |
||||
isZeroB := IsZero(b) |
||||
|
||||
fmt.Printf("%#v\n", isZeroA) |
||||
fmt.Printf("%#v\n", isZeroB) |
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
} |
||||
|
||||
func ExampleHasZero() { |
||||
// Let's define an Access struct. Note that the "Enabled" field is not
|
||||
// going to be checked because we added the "structs" tag to the field.
|
||||
type Access struct { |
||||
Name string |
||||
LastAccessed time.Time |
||||
Number int |
||||
Enabled bool `structs:"-"` |
||||
} |
||||
|
||||
// Name and Number is not initialized.
|
||||
a := &Access{ |
||||
LastAccessed: time.Now(), |
||||
} |
||||
hasZeroA := HasZero(a) |
||||
|
||||
// Name and Number is initialized.
|
||||
b := &Access{ |
||||
Name: "Fatih", |
||||
LastAccessed: time.Now(), |
||||
Number: 12345, |
||||
} |
||||
hasZeroB := HasZero(b) |
||||
|
||||
fmt.Printf("%#v\n", hasZeroA) |
||||
fmt.Printf("%#v\n", hasZeroB) |
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
} |
@ -0,0 +1,898 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestMapNonStruct(t *testing.T) { |
||||
foo := []string{"foo"} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Passing a non struct into Map should panic") |
||||
} |
||||
}() |
||||
|
||||
// this should panic. We are going to recover and and test it
|
||||
_ = Map(foo) |
||||
} |
||||
|
||||
func TestStructIndexes(t *testing.T) { |
||||
type C struct { |
||||
something int |
||||
Props map[string]interface{} |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err != nil { |
||||
fmt.Printf("err %+v\n", err) |
||||
t.Error("Using mixed indexes should not panic") |
||||
} |
||||
}() |
||||
|
||||
// They should not panic
|
||||
_ = Map(&C{}) |
||||
_ = Fields(&C{}) |
||||
_ = Values(&C{}) |
||||
_ = IsZero(&C{}) |
||||
_ = HasZero(&C{}) |
||||
} |
||||
|
||||
func TestMap(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
a := Map(T) |
||||
|
||||
if typ := reflect.TypeOf(a).Kind(); typ != reflect.Map { |
||||
t.Errorf("Map should return a map type, got: %v", typ) |
||||
} |
||||
|
||||
// we have three fields
|
||||
if len(a) != 3 { |
||||
t.Errorf("Map should return a map of len 3, got: %d", len(a)) |
||||
} |
||||
|
||||
inMap := func(val interface{}) bool { |
||||
for _, v := range a { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"a-value", 2, true} { |
||||
if !inMap(val) { |
||||
t.Errorf("Map should have the value %v", val) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestMap_Tag(t *testing.T) { |
||||
var T = struct { |
||||
A string `structs:"x"` |
||||
B int `structs:"y"` |
||||
C bool `structs:"z"` |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
a := Map(T) |
||||
|
||||
inMap := func(key interface{}) bool { |
||||
for k := range a { |
||||
if reflect.DeepEqual(k, key) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, key := range []string{"x", "y", "z"} { |
||||
if !inMap(key) { |
||||
t.Errorf("Map should have the key %v", key) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestMap_CustomTag(t *testing.T) { |
||||
var T = struct { |
||||
A string `json:"x"` |
||||
B int `json:"y"` |
||||
C bool `json:"z"` |
||||
D struct { |
||||
E string `json:"jkl"` |
||||
} `json:"nested"` |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
T.D.E = "e-value" |
||||
|
||||
s := New(T) |
||||
s.TagName = "json" |
||||
|
||||
a := s.Map() |
||||
|
||||
inMap := func(key interface{}) bool { |
||||
for k := range a { |
||||
if reflect.DeepEqual(k, key) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, key := range []string{"x", "y", "z"} { |
||||
if !inMap(key) { |
||||
t.Errorf("Map should have the key %v", key) |
||||
} |
||||
} |
||||
|
||||
nested, ok := a["nested"].(map[string]interface{}) |
||||
if !ok { |
||||
t.Fatalf("Map should contain the D field that is tagged as 'nested'") |
||||
} |
||||
|
||||
e, ok := nested["jkl"].(string) |
||||
if !ok { |
||||
t.Fatalf("Map should contain the D.E field that is tagged as 'jkl'") |
||||
} |
||||
|
||||
if e != "e-value" { |
||||
t.Errorf("D.E field should be equal to 'e-value', got: '%v'", e) |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestMap_MultipleCustomTag(t *testing.T) { |
||||
var A = struct { |
||||
X string `aa:"ax"` |
||||
}{"a_value"} |
||||
|
||||
aStruct := New(A) |
||||
aStruct.TagName = "aa" |
||||
|
||||
var B = struct { |
||||
X string `bb:"bx"` |
||||
}{"b_value"} |
||||
|
||||
bStruct := New(B) |
||||
bStruct.TagName = "bb" |
||||
|
||||
a, b := aStruct.Map(), bStruct.Map() |
||||
if !reflect.DeepEqual(a, map[string]interface{}{"ax": "a_value"}) { |
||||
t.Error("Map should have field ax with value a_value") |
||||
} |
||||
|
||||
if !reflect.DeepEqual(b, map[string]interface{}{"bx": "b_value"}) { |
||||
t.Error("Map should have field bx with value b_value") |
||||
} |
||||
} |
||||
|
||||
func TestMap_OmitEmpty(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value string `structs:",omitempty"` |
||||
Time time.Time `structs:",omitempty"` |
||||
} |
||||
a := A{} |
||||
|
||||
m := Map(a) |
||||
|
||||
_, ok := m["Value"].(map[string]interface{}) |
||||
if ok { |
||||
t.Error("Map should not contain the Value field that is tagged as omitempty") |
||||
} |
||||
|
||||
_, ok = m["Time"].(map[string]interface{}) |
||||
if ok { |
||||
t.Error("Map should not contain the Time field that is tagged as omitempty") |
||||
} |
||||
} |
||||
|
||||
func TestMap_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value string |
||||
Time time.Time `structs:",omitnested"` |
||||
} |
||||
a := A{Time: time.Now()} |
||||
|
||||
type B struct { |
||||
Desc string |
||||
A A |
||||
} |
||||
b := &B{A: a} |
||||
|
||||
m := Map(b) |
||||
|
||||
in, ok := m["A"].(map[string]interface{}) |
||||
if !ok { |
||||
t.Error("Map nested structs is not available in the map") |
||||
} |
||||
|
||||
// should not happen
|
||||
if _, ok := in["Time"].(map[string]interface{}); ok { |
||||
t.Error("Map nested struct should omit recursiving parsing of Time") |
||||
} |
||||
|
||||
if _, ok := in["Time"].(time.Time); !ok { |
||||
t.Error("Map nested struct should stop parsing of Time at is current value") |
||||
} |
||||
} |
||||
|
||||
func TestMap_Nested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := &A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A *A |
||||
} |
||||
b := &B{A: a} |
||||
|
||||
m := Map(b) |
||||
|
||||
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { |
||||
t.Errorf("Map should return a map type, got: %v", typ) |
||||
} |
||||
|
||||
in, ok := m["A"].(map[string]interface{}) |
||||
if !ok { |
||||
t.Error("Map nested structs is not available in the map") |
||||
} |
||||
|
||||
if name := in["Name"].(string); name != "example" { |
||||
t.Errorf("Map nested struct's name field should give example, got: %s", name) |
||||
} |
||||
} |
||||
|
||||
func TestMap_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := &A{Name: "example"} |
||||
|
||||
type B struct { |
||||
*A |
||||
} |
||||
b := &B{} |
||||
b.A = a |
||||
|
||||
m := Map(b) |
||||
|
||||
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { |
||||
t.Errorf("Map should return a map type, got: %v", typ) |
||||
} |
||||
|
||||
in, ok := m["A"].(map[string]interface{}) |
||||
if !ok { |
||||
t.Error("Embedded structs is not available in the map") |
||||
} |
||||
|
||||
if name := in["Name"].(string); name != "example" { |
||||
t.Errorf("Embedded A struct's Name field should give example, got: %s", name) |
||||
} |
||||
} |
||||
|
||||
func TestStruct(t *testing.T) { |
||||
var T = struct{}{} |
||||
|
||||
if !IsStruct(T) { |
||||
t.Errorf("T should be a struct, got: %T", T) |
||||
} |
||||
|
||||
if !IsStruct(&T) { |
||||
t.Errorf("T should be a struct, got: %T", T) |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestValues(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
s := Values(T) |
||||
|
||||
if typ := reflect.TypeOf(s).Kind(); typ != reflect.Slice { |
||||
t.Errorf("Values should return a slice type, got: %v", typ) |
||||
} |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"a-value", 2, true} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestValues_OmitEmpty(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value int `structs:",omitempty"` |
||||
} |
||||
|
||||
a := A{Name: "example"} |
||||
s := Values(a) |
||||
|
||||
if len(s) != 1 { |
||||
t.Errorf("Values of omitted empty fields should be not counted") |
||||
} |
||||
|
||||
if s[0].(string) != "example" { |
||||
t.Errorf("Values of omitted empty fields should left the value example") |
||||
} |
||||
} |
||||
|
||||
func TestValues_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value int |
||||
} |
||||
|
||||
a := A{ |
||||
Name: "example", |
||||
Value: 123, |
||||
} |
||||
|
||||
type B struct { |
||||
A A `structs:",omitnested"` |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
s := Values(b) |
||||
|
||||
if len(s) != 2 { |
||||
t.Errorf("Values of omitted nested struct should be not counted") |
||||
} |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{123, a} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestValues_Nested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
s := Values(b) |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"example", 123} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestValues_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
s := Values(b) |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"example", 123} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestNames(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
s := Names(T) |
||||
|
||||
if len(s) != 3 { |
||||
t.Errorf("Names should return a slice of len 3, got: %d", len(s)) |
||||
} |
||||
|
||||
inSlice := func(val string) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []string{"A", "B", "C"} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Names should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFields(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
s := Fields(T) |
||||
|
||||
if len(s) != 3 { |
||||
t.Errorf("Fields should return a slice of len 3, got: %d", len(s)) |
||||
} |
||||
|
||||
inSlice := func(val string) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v.Name(), val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []string{"A", "B", "C"} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Fields should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFields_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Enabled bool |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
Value string `structs:"-"` |
||||
Number int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
s := Fields(b) |
||||
|
||||
if len(s) != 3 { |
||||
t.Errorf("Fields should omit nested struct. Expecting 2 got: %d", len(s)) |
||||
} |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v.Name(), val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"A", "C"} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Fields should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFields_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
s := Fields(b) |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v.Name(), val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"A", "C"} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Fields should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestIsZero(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool `structs:"-"` |
||||
D []string |
||||
}{} |
||||
|
||||
ok := IsZero(T) |
||||
if !ok { |
||||
t.Error("IsZero should return true because none of the fields are initialized.") |
||||
} |
||||
|
||||
var X = struct { |
||||
A string |
||||
F *bool |
||||
}{ |
||||
A: "a-value", |
||||
} |
||||
|
||||
ok = IsZero(X) |
||||
if ok { |
||||
t.Error("IsZero should return false because A is initialized") |
||||
} |
||||
|
||||
var Y = struct { |
||||
A string |
||||
B int |
||||
}{ |
||||
A: "a-value", |
||||
B: 123, |
||||
} |
||||
|
||||
ok = IsZero(Y) |
||||
if ok { |
||||
t.Error("IsZero should return false because A and B is initialized") |
||||
} |
||||
} |
||||
|
||||
func TestIsZero_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A `structs:",omitnested"` |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
ok := IsZero(b) |
||||
if ok { |
||||
t.Error("IsZero should return false because A, B and C are initialized") |
||||
} |
||||
|
||||
aZero := A{} |
||||
bZero := &B{A: aZero} |
||||
|
||||
ok = IsZero(bZero) |
||||
if !ok { |
||||
t.Error("IsZero should return true because neither A nor B is initialized") |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestIsZero_Nested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
ok := IsZero(b) |
||||
if ok { |
||||
t.Error("IsZero should return false because A, B and C are initialized") |
||||
} |
||||
|
||||
aZero := A{} |
||||
bZero := &B{A: aZero} |
||||
|
||||
ok = IsZero(bZero) |
||||
if !ok { |
||||
t.Error("IsZero should return true because neither A nor B is initialized") |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestIsZero_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
ok := IsZero(b) |
||||
if ok { |
||||
t.Error("IsZero should return false because A, B and C are initialized") |
||||
} |
||||
|
||||
aZero := A{} |
||||
bZero := &B{} |
||||
bZero.A = aZero |
||||
|
||||
ok = IsZero(bZero) |
||||
if !ok { |
||||
t.Error("IsZero should return true because neither A nor B is initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool `structs:"-"` |
||||
D []string |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
} |
||||
|
||||
ok := HasZero(T) |
||||
if !ok { |
||||
t.Error("HasZero should return true because A and B are initialized.") |
||||
} |
||||
|
||||
var X = struct { |
||||
A string |
||||
F *bool |
||||
}{ |
||||
A: "a-value", |
||||
} |
||||
|
||||
ok = HasZero(X) |
||||
if !ok { |
||||
t.Error("HasZero should return true because A is initialized") |
||||
} |
||||
|
||||
var Y = struct { |
||||
A string |
||||
B int |
||||
}{ |
||||
A: "a-value", |
||||
B: 123, |
||||
} |
||||
|
||||
ok = HasZero(Y) |
||||
if ok { |
||||
t.Error("HasZero should return false because A and B is initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A `structs:",omitnested"` |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
// Because the Field A inside B is omitted HasZero should return false
|
||||
// because it will stop iterating deeper andnot going to lookup for D
|
||||
ok := HasZero(b) |
||||
if ok { |
||||
t.Error("HasZero should return false because A and C are initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero_Nested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
ok := HasZero(b) |
||||
if !ok { |
||||
t.Error("HasZero should return true because D is not initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
ok := HasZero(b) |
||||
if !ok { |
||||
t.Error("HasZero should return false because D is not initialized") |
||||
} |
||||
} |
||||
|
||||
func TestName(t *testing.T) { |
||||
type Foo struct { |
||||
A string |
||||
B bool |
||||
} |
||||
f := &Foo{} |
||||
|
||||
n := Name(f) |
||||
if n != "Foo" { |
||||
t.Errorf("Name should return Foo, got: %s", n) |
||||
} |
||||
|
||||
unnamed := struct{ Name string }{Name: "Cihangir"} |
||||
m := Name(unnamed) |
||||
if m != "" { |
||||
t.Errorf("Name should return empty string for unnamed struct, got: %s", n) |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Name should panic if a non struct is passed") |
||||
} |
||||
}() |
||||
|
||||
Name([]string{}) |
||||
} |
||||
|
||||
func TestNestedNilPointer(t *testing.T) { |
||||
type Collar struct { |
||||
Engraving string |
||||
} |
||||
|
||||
type Dog struct { |
||||
Name string |
||||
Collar *Collar |
||||
} |
||||
|
||||
type Person struct { |
||||
Name string |
||||
Dog *Dog |
||||
} |
||||
|
||||
person := &Person{ |
||||
Name: "John", |
||||
} |
||||
|
||||
personWithDog := &Person{ |
||||
Name: "Ron", |
||||
Dog: &Dog{ |
||||
Name: "Rover", |
||||
}, |
||||
} |
||||
|
||||
personWithDogWithCollar := &Person{ |
||||
Name: "Kon", |
||||
Dog: &Dog{ |
||||
Name: "Ruffles", |
||||
Collar: &Collar{ |
||||
Engraving: "If lost, call Kon", |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err != nil { |
||||
fmt.Printf("err %+v\n", err) |
||||
t.Error("Internal nil pointer should not panic") |
||||
} |
||||
}() |
||||
|
||||
_ = Map(person) // Panics
|
||||
_ = Map(personWithDog) // Panics
|
||||
_ = Map(personWithDogWithCollar) // Doesn't panic
|
||||
} |
0
internal/github.com/fatih/structs/tags.go → vendor/github.com/fatih/structs/tags.go
generated
vendored
0
internal/github.com/fatih/structs/tags.go → vendor/github.com/fatih/structs/tags.go
generated
vendored
@ -0,0 +1,46 @@ |
||||
package structs |
||||
|
||||
import "testing" |
||||
|
||||
func TestParseTag_Name(t *testing.T) { |
||||
tags := []struct { |
||||
tag string |
||||
has bool |
||||
}{ |
||||
{"", false}, |
||||
{"name", true}, |
||||
{"name,opt", true}, |
||||
{"name , opt, opt2", false}, // has a single whitespace
|
||||
{", opt, opt2", false}, |
||||
} |
||||
|
||||
for _, tag := range tags { |
||||
name, _ := parseTag(tag.tag) |
||||
|
||||
if (name != "name") && tag.has { |
||||
t.Errorf("Parse tag should return name: %#v", tag) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestParseTag_Opts(t *testing.T) { |
||||
tags := []struct { |
||||
opts string |
||||
has bool |
||||
}{ |
||||
{"name", false}, |
||||
{"name,opt", true}, |
||||
{"name , opt, opt2", false}, // has a single whitespace
|
||||
{",opt, opt2", true}, |
||||
{", opt3, opt4", false}, |
||||
} |
||||
|
||||
// search for "opt"
|
||||
for _, tag := range tags { |
||||
_, opts := parseTag(tag.opts) |
||||
|
||||
if opts.Has("opt") != tag.has { |
||||
t.Errorf("Tag opts should have opt: %#v", tag) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,161 @@ |
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context |
||||
|
||||
import ( |
||||
"net/http" |
||||
"testing" |
||||
) |
||||
|
||||
type keyType int |
||||
|
||||
const ( |
||||
key1 keyType = iota |
||||
key2 |
||||
) |
||||
|
||||
func TestContext(t *testing.T) { |
||||
assertEqual := func(val interface{}, exp interface{}) { |
||||
if val != exp { |
||||
t.Errorf("Expected %v, got %v.", exp, val) |
||||
} |
||||
} |
||||
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) |
||||
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) |
||||
|
||||
// Get()
|
||||
assertEqual(Get(r, key1), nil) |
||||
|
||||
// Set()
|
||||
Set(r, key1, "1") |
||||
assertEqual(Get(r, key1), "1") |
||||
assertEqual(len(data[r]), 1) |
||||
|
||||
Set(r, key2, "2") |
||||
assertEqual(Get(r, key2), "2") |
||||
assertEqual(len(data[r]), 2) |
||||
|
||||
//GetOk
|
||||
value, ok := GetOk(r, key1) |
||||
assertEqual(value, "1") |
||||
assertEqual(ok, true) |
||||
|
||||
value, ok = GetOk(r, "not exists") |
||||
assertEqual(value, nil) |
||||
assertEqual(ok, false) |
||||
|
||||
Set(r, "nil value", nil) |
||||
value, ok = GetOk(r, "nil value") |
||||
assertEqual(value, nil) |
||||
assertEqual(ok, true) |
||||
|
||||
// GetAll()
|
||||
values := GetAll(r) |
||||
assertEqual(len(values), 3) |
||||
|
||||
// GetAll() for empty request
|
||||
values = GetAll(emptyR) |
||||
if values != nil { |
||||
t.Error("GetAll didn't return nil value for invalid request") |
||||
} |
||||
|
||||
// GetAllOk()
|
||||
values, ok = GetAllOk(r) |
||||
assertEqual(len(values), 3) |
||||
assertEqual(ok, true) |
||||
|
||||
// GetAllOk() for empty request
|
||||
values, ok = GetAllOk(emptyR) |
||||
assertEqual(value, nil) |
||||
assertEqual(ok, false) |
||||
|
||||
// Delete()
|
||||
Delete(r, key1) |
||||
assertEqual(Get(r, key1), nil) |
||||
assertEqual(len(data[r]), 2) |
||||
|
||||
Delete(r, key2) |
||||
assertEqual(Get(r, key2), nil) |
||||
assertEqual(len(data[r]), 1) |
||||
|
||||
// Clear()
|
||||
Clear(r) |
||||
assertEqual(len(data), 0) |
||||
} |
||||
|
||||
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { |
||||
<-wait |
||||
for i := 0; i < iterations; i++ { |
||||
Get(r, key) |
||||
} |
||||
done <- struct{}{} |
||||
|
||||
} |
||||
|
||||
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { |
||||
<-wait |
||||
for i := 0; i < iterations; i++ { |
||||
Set(r, key, value) |
||||
} |
||||
done <- struct{}{} |
||||
|
||||
} |
||||
|
||||
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { |
||||
|
||||
b.StopTimer() |
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) |
||||
done := make(chan struct{}) |
||||
b.StartTimer() |
||||
|
||||
for i := 0; i < b.N; i++ { |
||||
wait := make(chan struct{}) |
||||
|
||||
for i := 0; i < numReaders; i++ { |
||||
go parallelReader(r, "test", iterations, wait, done) |
||||
} |
||||
|
||||
for i := 0; i < numWriters; i++ { |
||||
go parallelWriter(r, "test", "123", iterations, wait, done) |
||||
} |
||||
|
||||
close(wait) |
||||
|
||||
for i := 0; i < numReaders+numWriters; i++ { |
||||
<-done |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
func BenchmarkMutexSameReadWrite1(b *testing.B) { |
||||
benchmarkMutex(b, 1, 1, 32) |
||||
} |
||||
func BenchmarkMutexSameReadWrite2(b *testing.B) { |
||||
benchmarkMutex(b, 2, 2, 32) |
||||
} |
||||
func BenchmarkMutexSameReadWrite4(b *testing.B) { |
||||
benchmarkMutex(b, 4, 4, 32) |
||||
} |
||||
func BenchmarkMutex1(b *testing.B) { |
||||
benchmarkMutex(b, 2, 8, 32) |
||||
} |
||||
func BenchmarkMutex2(b *testing.B) { |
||||
benchmarkMutex(b, 16, 4, 64) |
||||
} |
||||
func BenchmarkMutex3(b *testing.B) { |
||||
benchmarkMutex(b, 1, 2, 128) |
||||
} |
||||
func BenchmarkMutex4(b *testing.B) { |
||||
benchmarkMutex(b, 128, 32, 256) |
||||
} |
||||
func BenchmarkMutex5(b *testing.B) { |
||||
benchmarkMutex(b, 1024, 2048, 64) |
||||
} |
||||
func BenchmarkMutex6(b *testing.B) { |
||||
benchmarkMutex(b, 2048, 1024, 512) |
||||
} |
0
internal/github.com/gorilla/mux/LICENSE → vendor/github.com/gorilla/mux/LICENSE
generated
vendored
0
internal/github.com/gorilla/mux/LICENSE → vendor/github.com/gorilla/mux/LICENSE
generated
vendored
0
internal/github.com/gorilla/mux/README.md → vendor/github.com/gorilla/mux/README.md
generated
vendored
0
internal/github.com/gorilla/mux/README.md → vendor/github.com/gorilla/mux/README.md
generated
vendored
@ -0,0 +1,21 @@ |
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mux |
||||
|
||||
import ( |
||||
"net/http" |
||||
"testing" |
||||
) |
||||
|
||||
func BenchmarkMux(b *testing.B) { |
||||
router := new(Router) |
||||
handler := func(w http.ResponseWriter, r *http.Request) {} |
||||
router.HandleFunc("/v1/{v1}", handler) |
||||
|
||||
request, _ := http.NewRequest("GET", "/v1/anything", nil) |
||||
for i := 0; i < b.N; i++ { |
||||
router.ServeHTTP(nil, request) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,714 @@ |
||||
// Old tests ported to Go1. This is a mess. Want to drop it one day.
|
||||
|
||||
// Copyright 2011 Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mux |
||||
|
||||
import ( |
||||
"bytes" |
||||
"net/http" |
||||
"testing" |
||||
) |
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ResponseRecorder
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct { |
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool |
||||
} |
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder { |
||||
return &ResponseRecorder{ |
||||
HeaderMap: make(http.Header), |
||||
Body: new(bytes.Buffer), |
||||
} |
||||
} |
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4" |
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *ResponseRecorder) Header() http.Header { |
||||
return rw.HeaderMap |
||||
} |
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) { |
||||
if rw.Body != nil { |
||||
rw.Body.Write(buf) |
||||
} |
||||
if rw.Code == 0 { |
||||
rw.Code = http.StatusOK |
||||
} |
||||
return len(buf), nil |
||||
} |
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) { |
||||
rw.Code = code |
||||
} |
||||
|
||||
// Flush sets rw.Flushed to true.
|
||||
func (rw *ResponseRecorder) Flush() { |
||||
rw.Flushed = true |
||||
} |
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func TestRouteMatchers(t *testing.T) { |
||||
var scheme, host, path, query, method string |
||||
var headers map[string]string |
||||
var resultVars map[bool]map[string]string |
||||
|
||||
router := NewRouter() |
||||
router.NewRoute().Host("{var1}.google.com"). |
||||
Path("/{var2:[a-z]+}/{var3:[0-9]+}"). |
||||
Queries("foo", "bar"). |
||||
Methods("GET"). |
||||
Schemes("https"). |
||||
Headers("x-requested-with", "XMLHttpRequest") |
||||
router.NewRoute().Host("www.{var4}.com"). |
||||
PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}"). |
||||
Queries("baz", "ding"). |
||||
Methods("POST"). |
||||
Schemes("http"). |
||||
Headers("Content-Type", "application/json") |
||||
|
||||
reset := func() { |
||||
// Everything match.
|
||||
scheme = "https" |
||||
host = "www.google.com" |
||||
path = "/product/42" |
||||
query = "?foo=bar" |
||||
method = "GET" |
||||
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} |
||||
resultVars = map[bool]map[string]string{ |
||||
true: {"var1": "www", "var2": "product", "var3": "42"}, |
||||
false: {}, |
||||
} |
||||
} |
||||
|
||||
reset2 := func() { |
||||
// Everything match.
|
||||
scheme = "http" |
||||
host = "www.google.com" |
||||
path = "/foo/product/42/path/that/is/ignored" |
||||
query = "?baz=ding" |
||||
method = "POST" |
||||
headers = map[string]string{"Content-Type": "application/json"} |
||||
resultVars = map[bool]map[string]string{ |
||||
true: {"var4": "google", "var5": "product", "var6": "42"}, |
||||
false: {}, |
||||
} |
||||
} |
||||
|
||||
match := func(shouldMatch bool) { |
||||
url := scheme + "://" + host + path + query |
||||
request, _ := http.NewRequest(method, url, nil) |
||||
for key, value := range headers { |
||||
request.Header.Add(key, value) |
||||
} |
||||
|
||||
var routeMatch RouteMatch |
||||
matched := router.Match(request, &routeMatch) |
||||
if matched != shouldMatch { |
||||
// Need better messages. :)
|
||||
if matched { |
||||
t.Errorf("Should match.") |
||||
} else { |
||||
t.Errorf("Should not match.") |
||||
} |
||||
} |
||||
|
||||
if matched { |
||||
currentRoute := routeMatch.Route |
||||
if currentRoute == nil { |
||||
t.Errorf("Expected a current route.") |
||||
} |
||||
vars := routeMatch.Vars |
||||
expectedVars := resultVars[shouldMatch] |
||||
if len(vars) != len(expectedVars) { |
||||
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) |
||||
} |
||||
for name, value := range vars { |
||||
if expectedVars[name] != value { |
||||
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1st route --------------------------------------------------------------
|
||||
|
||||
// Everything match.
|
||||
reset() |
||||
match(true) |
||||
|
||||
// Scheme doesn't match.
|
||||
reset() |
||||
scheme = "http" |
||||
match(false) |
||||
|
||||
// Host doesn't match.
|
||||
reset() |
||||
host = "www.mygoogle.com" |
||||
match(false) |
||||
|
||||
// Path doesn't match.
|
||||
reset() |
||||
path = "/product/notdigits" |
||||
match(false) |
||||
|
||||
// Query doesn't match.
|
||||
reset() |
||||
query = "?foo=baz" |
||||
match(false) |
||||
|
||||
// Method doesn't match.
|
||||
reset() |
||||
method = "POST" |
||||
match(false) |
||||
|
||||
// Header doesn't match.
|
||||
reset() |
||||
headers = map[string]string{} |
||||
match(false) |
||||
|
||||
// Everything match, again.
|
||||
reset() |
||||
match(true) |
||||
|
||||
// 2nd route --------------------------------------------------------------
|
||||
|
||||
// Everything match.
|
||||
reset2() |
||||
match(true) |
||||
|
||||
// Scheme doesn't match.
|
||||
reset2() |
||||
scheme = "https" |
||||
match(false) |
||||
|
||||
// Host doesn't match.
|
||||
reset2() |
||||
host = "sub.google.com" |
||||
match(false) |
||||
|
||||
// Path doesn't match.
|
||||
reset2() |
||||
path = "/bar/product/42" |
||||
match(false) |
||||
|
||||
// Query doesn't match.
|
||||
reset2() |
||||
query = "?foo=baz" |
||||
match(false) |
||||
|
||||
// Method doesn't match.
|
||||
reset2() |
||||
method = "GET" |
||||
match(false) |
||||
|
||||
// Header doesn't match.
|
||||
reset2() |
||||
headers = map[string]string{} |
||||
match(false) |
||||
|
||||
// Everything match, again.
|
||||
reset2() |
||||
match(true) |
||||
} |
||||
|
||||
type headerMatcherTest struct { |
||||
matcher headerMatcher |
||||
headers map[string]string |
||||
result bool |
||||
} |
||||
|
||||
var headerMatcherTests = []headerMatcherTest{ |
||||
{ |
||||
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), |
||||
headers: map[string]string{"X-Requested-With": "XMLHttpRequest"}, |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: headerMatcher(map[string]string{"x-requested-with": ""}), |
||||
headers: map[string]string{"X-Requested-With": "anything"}, |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), |
||||
headers: map[string]string{}, |
||||
result: false, |
||||
}, |
||||
} |
||||
|
||||
type hostMatcherTest struct { |
||||
matcher *Route |
||||
url string |
||||
vars map[string]string |
||||
result bool |
||||
} |
||||
|
||||
var hostMatcherTests = []hostMatcherTest{ |
||||
{ |
||||
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), |
||||
url: "http://abc.def.ghi/", |
||||
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), |
||||
url: "http://a.b.c/", |
||||
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, |
||||
result: false, |
||||
}, |
||||
} |
||||
|
||||
type methodMatcherTest struct { |
||||
matcher methodMatcher |
||||
method string |
||||
result bool |
||||
} |
||||
|
||||
var methodMatcherTests = []methodMatcherTest{ |
||||
{ |
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}), |
||||
method: "GET", |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}), |
||||
method: "POST", |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}), |
||||
method: "PUT", |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}), |
||||
method: "DELETE", |
||||
result: false, |
||||
}, |
||||
} |
||||
|
||||
type pathMatcherTest struct { |
||||
matcher *Route |
||||
url string |
||||
vars map[string]string |
||||
result bool |
||||
} |
||||
|
||||
var pathMatcherTests = []pathMatcherTest{ |
||||
{ |
||||
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), |
||||
url: "http://localhost:8080/123/456/789", |
||||
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), |
||||
url: "http://localhost:8080/1/2/3", |
||||
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, |
||||
result: false, |
||||
}, |
||||
} |
||||
|
||||
type schemeMatcherTest struct { |
||||
matcher schemeMatcher |
||||
url string |
||||
result bool |
||||
} |
||||
|
||||
var schemeMatcherTests = []schemeMatcherTest{ |
||||
{ |
||||
matcher: schemeMatcher([]string{"http", "https"}), |
||||
url: "http://localhost:8080/", |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: schemeMatcher([]string{"http", "https"}), |
||||
url: "https://localhost:8080/", |
||||
result: true, |
||||
}, |
||||
{ |
||||
matcher: schemeMatcher([]string{"https"}), |
||||
url: "http://localhost:8080/", |
||||
result: false, |
||||
}, |
||||
{ |
||||
matcher: schemeMatcher([]string{"http"}), |
||||
url: "https://localhost:8080/", |
||||
result: false, |
||||
}, |
||||
} |
||||
|
||||
type urlBuildingTest struct { |
||||
route *Route |
||||
vars []string |
||||
url string |
||||
} |
||||
|
||||
var urlBuildingTests = []urlBuildingTest{ |
||||
{ |
||||
route: new(Route).Host("foo.domain.com"), |
||||
vars: []string{}, |
||||
url: "http://foo.domain.com", |
||||
}, |
||||
{ |
||||
route: new(Route).Host("{subdomain}.domain.com"), |
||||
vars: []string{"subdomain", "bar"}, |
||||
url: "http://bar.domain.com", |
||||
}, |
||||
{ |
||||
route: new(Route).Host("foo.domain.com").Path("/articles"), |
||||
vars: []string{}, |
||||
url: "http://foo.domain.com/articles", |
||||
}, |
||||
{ |
||||
route: new(Route).Path("/articles"), |
||||
vars: []string{}, |
||||
url: "/articles", |
||||
}, |
||||
{ |
||||
route: new(Route).Path("/articles/{category}/{id:[0-9]+}"), |
||||
vars: []string{"category", "technology", "id", "42"}, |
||||
url: "/articles/technology/42", |
||||
}, |
||||
{ |
||||
route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"), |
||||
vars: []string{"subdomain", "foo", "category", "technology", "id", "42"}, |
||||
url: "http://foo.domain.com/articles/technology/42", |
||||
}, |
||||
} |
||||
|
||||
func TestHeaderMatcher(t *testing.T) { |
||||
for _, v := range headerMatcherTests { |
||||
request, _ := http.NewRequest("GET", "http://localhost:8080/", nil) |
||||
for key, value := range v.headers { |
||||
request.Header.Add(key, value) |
||||
} |
||||
var routeMatch RouteMatch |
||||
result := v.matcher.Match(request, &routeMatch) |
||||
if result != v.result { |
||||
if v.result { |
||||
t.Errorf("%#v: should match %v.", v.matcher, request.Header) |
||||
} else { |
||||
t.Errorf("%#v: should not match %v.", v.matcher, request.Header) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestHostMatcher(t *testing.T) { |
||||
for _, v := range hostMatcherTests { |
||||
request, _ := http.NewRequest("GET", v.url, nil) |
||||
var routeMatch RouteMatch |
||||
result := v.matcher.Match(request, &routeMatch) |
||||
vars := routeMatch.Vars |
||||
if result != v.result { |
||||
if v.result { |
||||
t.Errorf("%#v: should match %v.", v.matcher, v.url) |
||||
} else { |
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url) |
||||
} |
||||
} |
||||
if result { |
||||
if len(vars) != len(v.vars) { |
||||
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) |
||||
} |
||||
for name, value := range vars { |
||||
if v.vars[name] != value { |
||||
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) |
||||
} |
||||
} |
||||
} else { |
||||
if len(vars) != 0 { |
||||
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestMethodMatcher(t *testing.T) { |
||||
for _, v := range methodMatcherTests { |
||||
request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil) |
||||
var routeMatch RouteMatch |
||||
result := v.matcher.Match(request, &routeMatch) |
||||
if result != v.result { |
||||
if v.result { |
||||
t.Errorf("%#v: should match %v.", v.matcher, v.method) |
||||
} else { |
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.method) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestPathMatcher(t *testing.T) { |
||||
for _, v := range pathMatcherTests { |
||||
request, _ := http.NewRequest("GET", v.url, nil) |
||||
var routeMatch RouteMatch |
||||
result := v.matcher.Match(request, &routeMatch) |
||||
vars := routeMatch.Vars |
||||
if result != v.result { |
||||
if v.result { |
||||
t.Errorf("%#v: should match %v.", v.matcher, v.url) |
||||
} else { |
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url) |
||||
} |
||||
} |
||||
if result { |
||||
if len(vars) != len(v.vars) { |
||||
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) |
||||
} |
||||
for name, value := range vars { |
||||
if v.vars[name] != value { |
||||
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) |
||||
} |
||||
} |
||||
} else { |
||||
if len(vars) != 0 { |
||||
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestSchemeMatcher(t *testing.T) { |
||||
for _, v := range schemeMatcherTests { |
||||
request, _ := http.NewRequest("GET", v.url, nil) |
||||
var routeMatch RouteMatch |
||||
result := v.matcher.Match(request, &routeMatch) |
||||
if result != v.result { |
||||
if v.result { |
||||
t.Errorf("%#v: should match %v.", v.matcher, v.url) |
||||
} else { |
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestUrlBuilding(t *testing.T) { |
||||
|
||||
for _, v := range urlBuildingTests { |
||||
u, _ := v.route.URL(v.vars...) |
||||
url := u.String() |
||||
if url != v.url { |
||||
t.Errorf("expected %v, got %v", v.url, url) |
||||
/* |
||||
reversePath := "" |
||||
reverseHost := "" |
||||
if v.route.pathTemplate != nil { |
||||
reversePath = v.route.pathTemplate.Reverse |
||||
} |
||||
if v.route.hostTemplate != nil { |
||||
reverseHost = v.route.hostTemplate.Reverse |
||||
} |
||||
|
||||
t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost) |
||||
*/ |
||||
} |
||||
} |
||||
|
||||
ArticleHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
} |
||||
|
||||
router := NewRouter() |
||||
router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article") |
||||
|
||||
url, _ := router.Get("article").URL("category", "technology", "id", "42") |
||||
expected := "/articles/technology/42" |
||||
if url.String() != expected { |
||||
t.Errorf("Expected %v, got %v", expected, url.String()) |
||||
} |
||||
} |
||||
|
||||
func TestMatchedRouteName(t *testing.T) { |
||||
routeName := "stock" |
||||
router := NewRouter() |
||||
route := router.NewRoute().Path("/products/").Name(routeName) |
||||
|
||||
url := "http://www.domain.com/products/" |
||||
request, _ := http.NewRequest("GET", url, nil) |
||||
var rv RouteMatch |
||||
ok := router.Match(request, &rv) |
||||
|
||||
if !ok || rv.Route != route { |
||||
t.Errorf("Expected same route, got %+v.", rv.Route) |
||||
} |
||||
|
||||
retName := rv.Route.GetName() |
||||
if retName != routeName { |
||||
t.Errorf("Expected %q, got %q.", routeName, retName) |
||||
} |
||||
} |
||||
|
||||
func TestSubRouting(t *testing.T) { |
||||
// Example from docs.
|
||||
router := NewRouter() |
||||
subrouter := router.NewRoute().Host("www.domain.com").Subrouter() |
||||
route := subrouter.NewRoute().Path("/products/").Name("products") |
||||
|
||||
url := "http://www.domain.com/products/" |
||||
request, _ := http.NewRequest("GET", url, nil) |
||||
var rv RouteMatch |
||||
ok := router.Match(request, &rv) |
||||
|
||||
if !ok || rv.Route != route { |
||||
t.Errorf("Expected same route, got %+v.", rv.Route) |
||||
} |
||||
|
||||
u, _ := router.Get("products").URL() |
||||
builtUrl := u.String() |
||||
// Yay, subroute aware of the domain when building!
|
||||
if builtUrl != url { |
||||
t.Errorf("Expected %q, got %q.", url, builtUrl) |
||||
} |
||||
} |
||||
|
||||
func TestVariableNames(t *testing.T) { |
||||
route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}") |
||||
if route.err == nil { |
||||
t.Errorf("Expected error for duplicated variable names") |
||||
} |
||||
} |
||||
|
||||
func TestRedirectSlash(t *testing.T) { |
||||
var route *Route |
||||
var routeMatch RouteMatch |
||||
r := NewRouter() |
||||
|
||||
r.StrictSlash(false) |
||||
route = r.NewRoute() |
||||
if route.strictSlash != false { |
||||
t.Errorf("Expected false redirectSlash.") |
||||
} |
||||
|
||||
r.StrictSlash(true) |
||||
route = r.NewRoute() |
||||
if route.strictSlash != true { |
||||
t.Errorf("Expected true redirectSlash.") |
||||
} |
||||
|
||||
route = new(Route) |
||||
route.strictSlash = true |
||||
route.Path("/{arg1}/{arg2:[0-9]+}/") |
||||
request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil) |
||||
routeMatch = RouteMatch{} |
||||
_ = route.Match(request, &routeMatch) |
||||
vars := routeMatch.Vars |
||||
if vars["arg1"] != "foo" { |
||||
t.Errorf("Expected foo.") |
||||
} |
||||
if vars["arg2"] != "123" { |
||||
t.Errorf("Expected 123.") |
||||
} |
||||
rsp := NewRecorder() |
||||
routeMatch.Handler.ServeHTTP(rsp, request) |
||||
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" { |
||||
t.Errorf("Expected redirect header.") |
||||
} |
||||
|
||||
route = new(Route) |
||||
route.strictSlash = true |
||||
route.Path("/{arg1}/{arg2:[0-9]+}") |
||||
request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil) |
||||
routeMatch = RouteMatch{} |
||||
_ = route.Match(request, &routeMatch) |
||||
vars = routeMatch.Vars |
||||
if vars["arg1"] != "foo" { |
||||
t.Errorf("Expected foo.") |
||||
} |
||||
if vars["arg2"] != "123" { |
||||
t.Errorf("Expected 123.") |
||||
} |
||||
rsp = NewRecorder() |
||||
routeMatch.Handler.ServeHTTP(rsp, request) |
||||
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" { |
||||
t.Errorf("Expected redirect header.") |
||||
} |
||||
} |
||||
|
||||
// Test for the new regexp library, still not available in stable Go.
|
||||
func TestNewRegexp(t *testing.T) { |
||||
var p *routeRegexp |
||||
var matches []string |
||||
|
||||
tests := map[string]map[string][]string{ |
||||
"/{foo:a{2}}": { |
||||
"/a": nil, |
||||
"/aa": {"aa"}, |
||||
"/aaa": nil, |
||||
"/aaaa": nil, |
||||
}, |
||||
"/{foo:a{2,}}": { |
||||
"/a": nil, |
||||
"/aa": {"aa"}, |
||||
"/aaa": {"aaa"}, |
||||
"/aaaa": {"aaaa"}, |
||||
}, |
||||
"/{foo:a{2,3}}": { |
||||
"/a": nil, |
||||
"/aa": {"aa"}, |
||||
"/aaa": {"aaa"}, |
||||
"/aaaa": nil, |
||||
}, |
||||
"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": { |
||||
"/a": nil, |
||||
"/ab": nil, |
||||
"/abc": nil, |
||||
"/abcd": nil, |
||||
"/abc/ab": {"abc", "ab"}, |
||||
"/abc/abc": nil, |
||||
"/abcd/ab": nil, |
||||
}, |
||||
`/{foo:\w{3,}}/{bar:\d{2,}}`: { |
||||
"/a": nil, |
||||
"/ab": nil, |
||||
"/abc": nil, |
||||
"/abc/1": nil, |
||||
"/abc/12": {"abc", "12"}, |
||||
"/abcd/12": {"abcd", "12"}, |
||||
"/abcd/123": {"abcd", "123"}, |
||||
}, |
||||
} |
||||
|
||||
for pattern, paths := range tests { |
||||
p, _ = newRouteRegexp(pattern, false, false, false, false) |
||||
for path, result := range paths { |
||||
matches = p.regexp.FindStringSubmatch(path) |
||||
if result == nil { |
||||
if matches != nil { |
||||
t.Errorf("%v should not match %v.", pattern, path) |
||||
} |
||||
} else { |
||||
if len(matches) != len(result)+1 { |
||||
t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches)) |
||||
} else { |
||||
for k, v := range result { |
||||
if matches[k+1] != v { |
||||
t.Errorf("Expected %v, got %v.", v, matches[k+1]) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
0
internal/github.com/gorilla/mux/regexp.go → vendor/github.com/gorilla/mux/regexp.go
generated
vendored
0
internal/github.com/gorilla/mux/regexp.go → vendor/github.com/gorilla/mux/regexp.go
generated
vendored
0
internal/github.com/gorilla/mux/route.go → vendor/github.com/gorilla/mux/route.go
generated
vendored
0
internal/github.com/gorilla/mux/route.go → vendor/github.com/gorilla/mux/route.go
generated
vendored
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue