syncthing/lib/protocol/deviceid_test.go
Jakob Borg 9682bbfbda lib/protocol: Optimize luhn and chunk functions
These functions were very naive and slow. We haven't done much about
them because they pretty much don't matter at all for Syncthing
performance. They are however called very often in the discovery server
and these optimizations have a huge effect on the CPU load on the
public discovery servers.

The code isn't exactly obvious, but we have good test coverage on all
these functions.

benchmark                 old ns/op     new ns/op     delta
BenchmarkLuhnify-8        12458         1045          -91.61%
BenchmarkUnluhnify-8      12598         1074          -91.47%
BenchmarkChunkify-8       10792         104           -99.04%

benchmark                 old allocs     new allocs     delta
BenchmarkLuhnify-8        18             1              -94.44%
BenchmarkUnluhnify-8      18             1              -94.44%
BenchmarkChunkify-8       44             2              -95.45%

benchmark                 old bytes     new bytes     delta
BenchmarkLuhnify-8        1278          64            -94.99%
BenchmarkUnluhnify-8      1278          64            -94.99%
BenchmarkChunkify-8       42552         128           -99.70%

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4346
2017-09-03 10:26:12 +00:00

191 lines
5.0 KiB
Go

// Copyright (C) 2014 The Protocol Authors.
//go:generate go run ../../script/protofmt.go deviceid_test.proto
//go:generate protoc -I ../../vendor/ -I ../../vendor/github.com/gogo/protobuf/protobuf -I . --gogofast_out=. deviceid_test.proto
package protocol
import "testing"
var formatted = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"
var formatCases = []string{
"P56IOI-7MZJNU-2IQGDR-EYDM2M-GTMGL3-BXNPQ6-W5BTBB-Z4TJXZ-WICQ",
"P56IOI-7MZJNU2Y-IQGDR-EYDM2M-GTI-MGL3-BXNPQ6-W5BM-TBB-Z4TJXZ-WICQ2",
"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ",
"P56IOI7 MZJNU2Y IQGDREY DM2MGTI MGL3BXN PQ6W5BM TBBZ4TJ XZWICQ2",
"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ",
"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicq",
"P56IOI7MZJNU2YIQGDREYDM2MGTIMGL3BXNPQ6W5BMTBBZ4TJXZWICQ2",
"P561017MZJNU2YIQGDREYDM2MGTIMGL3BXNPQ6W5BMT88Z4TJXZWICQ2",
"p56ioi7mzjnu2yiqgdreydm2mgtimgl3bxnpq6w5bmtbbz4tjxzwicq2",
"p561017mzjnu2yiqgdreydm2mgtimgl3bxnpq6w5bmt88z4tjxzwicq2",
}
func TestFormatDeviceID(t *testing.T) {
for i, tc := range formatCases {
var id DeviceID
err := id.UnmarshalText([]byte(tc))
if err != nil {
t.Errorf("#%d UnmarshalText(%q); %v", i, tc, err)
} else if f := id.String(); f != formatted {
t.Errorf("#%d FormatDeviceID(%q)\n\t%q !=\n\t%q", i, tc, f, formatted)
}
}
}
var validateCases = []struct {
s string
ok bool
}{
{"", true}, // Empty device ID, all zeroes
{"a", false},
{"P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2", true},
{"P56IOI7-MZJNU2-IQGDREY-DM2MGT-MGL3BXN-PQ6W5B-TBBZ4TJ-XZWICQ", true},
{"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ", true},
{"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ", true},
{"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQCCCC", false},
{"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicq", true},
{"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicqCCCC", false},
}
func TestValidateDeviceID(t *testing.T) {
for _, tc := range validateCases {
var id DeviceID
err := id.UnmarshalText([]byte(tc.s))
if (err == nil && !tc.ok) || (err != nil && tc.ok) {
t.Errorf("ValidateDeviceID(%q); %v != %v", tc.s, err, tc.ok)
}
}
}
func TestMarshallingDeviceID(t *testing.T) {
n0 := DeviceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 10, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
n1 := DeviceID{}
n2 := DeviceID{}
bs, _ := n0.MarshalText()
n1.UnmarshalText(bs)
bs, _ = n1.MarshalText()
n2.UnmarshalText(bs)
if n2.String() != n0.String() {
t.Errorf("String marshalling error; %q != %q", n2.String(), n0.String())
}
if !n2.Equals(n0) {
t.Error("Equals error")
}
if n2.Compare(n0) != 0 {
t.Error("Compare error")
}
}
func TestShortIDString(t *testing.T) {
id, _ := DeviceIDFromString(formatted)
sid := id.Short().String()
if len(sid) != 7 {
t.Errorf("Wrong length for short ID: got %d, want 7", len(sid))
}
want := formatted[:len(sid)]
if sid != want {
t.Errorf("Wrong short ID: got %q, want %q", sid, want)
}
}
func TestDeviceIDFromBytes(t *testing.T) {
id0, _ := DeviceIDFromString(formatted)
id1 := DeviceIDFromBytes(id0[:])
if id1.String() != formatted {
t.Errorf("Wrong device ID, got %q, want %q", id1, formatted)
}
}
func TestNewDeviceIDMarshalling(t *testing.T) {
// The new DeviceID.Unmarshal / DeviceID.MarshalTo serialization should
// be message compatible with how we used to serialize DeviceIDs.
// Create a message with a device ID in old style bytes format
id0, _ := DeviceIDFromString(formatted)
msg0 := TestOldDeviceID{id0[:]}
// Marshal it
bs, err := msg0.Marshal()
if err != nil {
t.Fatal(err)
}
// Unmarshal using the new DeviceID.Unmarshal
var msg1 TestNewDeviceID
if err := msg1.Unmarshal(bs); err != nil {
t.Fatal(err)
}
// Verify it's the same
if msg1.Test != id0 {
t.Error("Mismatch in old -> new direction")
}
// Marshal using the new DeviceID.MarshalTo
bs, err = msg1.Marshal()
if err != nil {
t.Fatal(err)
}
// Create an old style message and attempt unmarshal
var msg2 TestOldDeviceID
if err := msg2.Unmarshal(bs); err != nil {
t.Fatal(err)
}
// Verify it's the same
if DeviceIDFromBytes(msg2.Test) != id0 {
t.Error("Mismatch in old -> new direction")
}
}
var resStr string
func BenchmarkLuhnify(b *testing.B) {
str := "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB"
var err error
for i := 0; i < b.N; i++ {
resStr, err = luhnify(str)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkUnluhnify(b *testing.B) {
str, _ := luhnify("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB")
var err error
for i := 0; i < b.N; i++ {
resStr, err = unluhnify(str)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkChunkify(b *testing.B) {
str := "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB"
for i := 0; i < b.N; i++ {
resStr = chunkify(str)
}
}
func BenchmarkUnchunkify(b *testing.B) {
str := chunkify("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB")
for i := 0; i < b.N; i++ {
resStr = unchunkify(str)
}
}