Skip to content

Commit e48a2e1

Browse files
LaunchDarklyReleaseBoteli-darklyLaunchDarklyCIbwoskow-ldSam Stokes
authored
prepare 2.5.1 release (#21)
* implement Value type for arbitrary JSON values * force CI rebuild * add go get * fix go get * fix lint in CI * add Equal() * add EvaluationDetail and EvaluationReason * fix import path * readme fixes * update for v2 * fix doc comments * add OptionalString * misc cleanup, method renaming, better test coverage * package comments * changed repo name * numbers can't have leading decimal point * actually we decided on go-sdk-common, not go-sdk-core * add license file * OptionalString improvements + misc fixes * add OrElse * add tests * comment * remove unsafe interface{} usage * copy User and related types from go-server-sdk * OptionalString improvements + misc fixes (#2) * OptionalString improvements + misc fixes * add OrElse * add tests * comment * (common v2/SDK v5) don't use pointers or interface{}s in User * lint * lint * rm references to obsolete function * (v2 - #1) copy User and related types from go-server-sdk (#3) * copy User and related types from go-server-sdk * lint * lint * rm references to obsolete function * hide all User fields, improve internal structure, add getters * lint * add convenience methods for enumerable types * User model improvements related to custom/private attributes * lint * update release config * update release config * fix package references * add ldlog package to go-sdk-common * lint * drop Go 1.8-1.11 and update linter * gitignore * lint * add ldtime package for time helpers * add Go 1.14 in CI * desupport Go 1.12 * makefile improvement * make repo a module * don't install dep * yaml fix * update golangci-lint + misc code cleanup * add methods for detecting current log level * enforce consistent import groups * enable stylecheck, fix comment * add prerelease readme note * improve test coverage and enforce coverage goals (#18) * update readme note about import path, add godoc badge * better badge * omit unset properties in user JSON instead of serializing null value * don't allocate extra objects when setting user builder properties * add generic user attr setter and more Value helper methods * add benchmarks and enforce zero-allocation goals * implement streaming JSON serialization (#23) * fix bug in JSONBuffer state when writing zeroes * fix note about import path * go mod tidy * fix JSONBuffer string escaping, + slight refactor * remove accidental inclusions from another branch * lint * remove inapplicable comments * add ability to send buffered JSON output to another writer * add log test helper package * add UnmarshalText and other helper methods to OptionalString * use go-test-helpers v2 (removes unwanted eventsource dependency) (#28) * user unmarshaling should fail if key is missing (#30) * add OptionalInt type * test coverage * keep method signature of NewEvaluationDetail the same as it used to be * comment * add OptionalBool type and use it for User.Anonymous (#32) * add OptionalBool type and use it for User.Anonymous * comment * add ldlogtest helper for dumping captured log output * add Go 1.15 build and remove beta changelog * update Go version in readme * rm prerelease note * better error reporting when unmarshaling User, OptionalString, etc. (#35) * add IsDefined() methods to optional-like types * better copy-on-write behavior for maps/slices; expose map & array types * use new go-jsonstream API (#38) * change go-jsonstream import path back to gopkg.in since the repo is public now * add conditionally-compiled EasyJSON methods * update for go-jsonstream API changes * build in Windows with Go 1.14 * update CircleCI Windows image * treat null as equivalent to empty array for privateAttributeNames in user JSON * EasyJSON optimizations for ldvalue and lduser types (#43) * EasyJSON optimizations for ldvalue and lduser types * revert unnecessary changes to non-easyjson implementation * fix test (detection of trailing comma is not a well-defined use case) * require a JSON object (not null) when parsing a User * Removed the guides link * Add inExperiment attribute to FALLTHROUGH and RULE_MATCH reasons (#45) This adds an attribute inExperiment to reasons of kinds FALLTHROUGH and RULE_MATCH, as specified in the Experiment Traffic Allocation spec. This attribute is intended to indicate whether the user was targeted by an experiment rollout, and if so, whether they were allocated to one of the experiment variations. We currently plan to use this for two things: * to allow event consumers (currently, the experimentation pipeline and Data Export customers) to filter feature events to only those from users who are part of an experiment. * to allow some SDKs, which use reasons to determine whether to send full events, to avoid sending feature events in the first place after evaluating experiment rollouts for users who are not part of the experiment. (It might seem like this makes the filtering requirement redundant, since there's no need to filter out events that the SDKs declined to send in the first place, but for Data Export customers we want to send all of the events but still filter them for experiment analysis.) * Enable tests when releasing (#46) * Enable tests when releasing (#46) * add unbounded segments status to evaluation reasons (#44) * "big" is the new "unbounded" (#47) * update go-jsonstream version for ch110425 bugfix * update to newer Releaser config format, add Go 1.16 CI (#50) * update to newer Releaser config format, add Go 1.16 CI * run code coverage job in Go 1.15 * Updates docs URLs * add CI job for Go 1.17; update linter & test coverage script * add comparative benchmarks for v2 (#66) * add comparative benchmarks for v2 * DRY * transparently convert empty raw JSON value to null * fix linter in build * lint Co-authored-by: Eli Bishop <eli@launchdarkly.com> Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Sam Stokes <sstokes@launchdarkly.com> Co-authored-by: LaunchDarklyReleaseBot <launchdarklyreleasebot@launchdarkly.com> Co-authored-by: Ember Stevens <ember.stevens@launchdarkly.com> Co-authored-by: ember-stevens <79482775+ember-stevens@users.noreply.github.com>
1 parent ee73eda commit e48a2e1

14 files changed

+321
-81
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ benchmark-allocs:
7070

7171
$(LINTER_VERSION_FILE):
7272
rm -f $(LINTER)
73-
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s $(GOLANGCI_LINT_VERSION)
73+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s $(GOLANGCI_LINT_VERSION)
7474
touch $(LINTER_VERSION_FILE)
7575

7676
lint: $(LINTER_VERSION_FILE)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package sharedtest
2+
3+
import (
4+
"fmt"
5+
6+
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
7+
)
8+
9+
const (
10+
SmallNumberOfCustomAttributes = 2 //nolint:revive
11+
LargeNumberOfCustomAttributes = 20 //nolint:revive
12+
)
13+
14+
type NameAndLDValue struct { //nolint:revive
15+
Name string
16+
Value ldvalue.Value
17+
}
18+
19+
func MakeCustomAttributeNamesAndValues(count int) []NameAndLDValue { //nolint:revive
20+
ret := make([]NameAndLDValue, 0, count)
21+
for i := 1; i <= count; i++ {
22+
ret = append(ret, NameAndLDValue{fmt.Sprintf("attr%d", i), ldvalue.String(fmt.Sprintf("value%d", i))})
23+
}
24+
return ret
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package sharedtest
2+
3+
// These functions return standardized test inputs for unmarshaling JSON user data in the old user schema.
4+
5+
type UnmarshalingTestParams struct { //nolint:revive
6+
Name string
7+
Data []byte
8+
}
9+
10+
func MakeOldUserUnmarshalingTestParams() []UnmarshalingTestParams { //nolint:revive
11+
return []UnmarshalingTestParams{
12+
{"old user with key only", MakeOldUserWithKeyOnlyJSON()},
13+
{"old user with few attrs", MakeOldUserWithFewAttributesJSON()},
14+
{"old user with all attrs", MakeOldUserWithAllAttributesJSON()},
15+
}
16+
}
17+
18+
func MakeOldUserWithKeyOnlyJSON() []byte { //nolint:revive
19+
return []byte(`{"key":"user-key"}`)
20+
}
21+
22+
func MakeOldUserWithFewAttributesJSON() []byte { //nolint:revive
23+
return []byte(`{"key":"user-key","name":"Name","email":"test@example.com","custom":{"attr":"value"}}`)
24+
}
25+
26+
func MakeOldUserWithAllAttributesJSON() []byte { //nolint:revive
27+
return []byte(`{"key":"user-key","secondary":"secondary-value","name":"Name","ip":"ip-value","country":"us",` +
28+
`"email":"test@example.com","firstName":"First","lastName":"Last","avatar":"avatar-value","anonymous":true,` +
29+
`"custom":{"attr":"value"}}`)
30+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package sharedtest contains test helpers that may be used in unit tests and/or benchmarks for
2+
// multiple packages in go-sdk-common. It should not import any go-sdk-common packages except ldvalue.
3+
package sharedtest

lduser/testdata_benchmark_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package lduser
2+
3+
import (
4+
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
5+
)
6+
7+
var (
8+
benchmarkUserBuilder = NewUserBuilder("key")
9+
benchmarkUserBuilderWithAllScalarAttributes = NewUserBuilder("key").
10+
Secondary("s").
11+
IP("i").
12+
Country("c").
13+
Email("e").
14+
FirstName("f").
15+
LastName("l").
16+
Avatar("a").
17+
Name("n").
18+
Anonymous(true)
19+
20+
benchmarkUserResult User
21+
benchmarkUserPointer *User
22+
23+
benchmarkValue ldvalue.Value
24+
25+
benchmarkJSONResult []byte
26+
27+
benchmarkErr error
28+
)
29+
30+
type benchmarkMarshalTestParams struct {
31+
name string
32+
user User
33+
}
34+
35+
func makeBenchmarkMarshalTestParams() []benchmarkMarshalTestParams {
36+
return []benchmarkMarshalTestParams{
37+
{"user with key only", makeBenchmarkUserWithKeyOnly()},
38+
{"user with few attrs", makeBenchmarkUserWithFewAttributes()},
39+
{"user with all attrs", makeBenchmarkUserWithAllAttributes()},
40+
}
41+
}
42+
43+
func makeBenchmarkUserWithKeyOnly() User {
44+
return NewUser("user-key")
45+
}
46+
47+
func makeBenchmarkUserWithFewAttributes() User {
48+
return NewUserBuilder("user-key").
49+
Name("Name").
50+
Email("test@example.com").
51+
SetAttribute(UserAttribute("attr"), ldvalue.String("value")).
52+
Build()
53+
}
54+
55+
func makeBenchmarkUserWithAllAttributes() User {
56+
return NewUserBuilder("user-key").
57+
Secondary("secondary-value").
58+
Name("Name").
59+
IP("ip-value").
60+
Country("us").
61+
Email("test@example.com").
62+
FirstName("First").
63+
LastName("Last").
64+
Avatar("avatar-value").
65+
Anonymous(true).
66+
SetAttribute(UserAttribute("attr"), ldvalue.String("value")).
67+
Build()
68+
}

lduser/user_benchmark_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package lduser
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"gopkg.in/launchdarkly/go-sdk-common.v2/internal/sharedtest"
8+
)
9+
10+
func BenchmarkUserMinimumAllocationSize(b *testing.B) {
11+
// This just measures the minimum number of bytes taken up by a User.
12+
for i := 0; i < b.N; i++ {
13+
benchmarkUserPointer = makeEmptyUserPointer()
14+
}
15+
}
16+
17+
func makeEmptyUserPointer() *User {
18+
return &User{}
19+
}
20+
21+
func BenchmarkUserGetCustomAttrNoAlloc(b *testing.B) {
22+
for _, n := range []int{sharedtest.SmallNumberOfCustomAttributes, sharedtest.LargeNumberOfCustomAttributes} {
23+
b.Run(fmt.Sprintf("with %d attributes", n), func(b *testing.B) {
24+
builder := NewUserBuilder("key")
25+
attrs := sharedtest.MakeCustomAttributeNamesAndValues(n)
26+
for _, a := range attrs {
27+
builder.SetAttribute(UserAttribute(a.Name), a.Value)
28+
}
29+
user := builder.Build()
30+
lastAttr := UserAttribute(attrs[n-1].Name)
31+
b.ResetTimer()
32+
for i := 0; i < b.N; i++ {
33+
benchmarkValue = user.GetAttribute(lastAttr)
34+
}
35+
})
36+
}
37+
}

lduser/user_builder_benchmark_test.go

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,10 @@
11
package lduser
22

33
import (
4+
"fmt"
45
"testing"
56

6-
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
7-
)
8-
9-
var (
10-
benchmarkUserBuilder = NewUserBuilder("key")
11-
benchmarkUserBuilderWithAllScalarAttributes = NewUserBuilder("key").
12-
Secondary("s").
13-
IP("i").
14-
Country("c").
15-
Email("e").
16-
FirstName("f").
17-
LastName("l").
18-
Avatar("a").
19-
Name("n").
20-
Anonymous(true)
21-
22-
benchmarkUserResult User
7+
"gopkg.in/launchdarkly/go-sdk-common.v2/internal/sharedtest"
238
)
249

2510
func BenchmarkNewUserNoAlloc(b *testing.B) {
@@ -40,12 +25,19 @@ func BenchmarkNewUserFromBuilder(b *testing.B) {
4025
}
4126
}
4227

43-
func BenchmarkNewUserFromBuilderWithCustom(b *testing.B) {
44-
for i := 0; i < b.N; i++ {
45-
benchmarkUserResult = NewUserBuilder("key").
46-
Custom("attr1", ldvalue.String("value1")).
47-
Custom("attr2", ldvalue.String("value2")).
48-
Build()
28+
func BenchmarkBuildWithCustomAttributes(b *testing.B) {
29+
for _, n := range []int{sharedtest.SmallNumberOfCustomAttributes, sharedtest.LargeNumberOfCustomAttributes} {
30+
b.Run(fmt.Sprintf("with %d attributes", n), func(b *testing.B) {
31+
attrs := sharedtest.MakeCustomAttributeNamesAndValues(n)
32+
b.ResetTimer()
33+
for i := 0; i < b.N; i++ {
34+
builder := NewUserBuilder("key")
35+
for _, a := range attrs {
36+
builder.SetAttribute(UserAttribute(a.Name), a.Value)
37+
}
38+
benchmarkUserResult = builder.Build()
39+
}
40+
})
4941
}
5042
}
5143

lduser/user_serialization_benchmark_test.go

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,68 @@ import (
44
"encoding/json"
55
"testing"
66

7-
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
8-
)
7+
"gopkg.in/launchdarkly/go-sdk-common.v2/internal/sharedtest"
98

10-
var (
11-
benchmarkSimpleUser = NewUser("user-key")
12-
benchmarkSimpleUserJSON = []byte(`{"key":"user-key"}`)
13-
benchmarkUserWithAllAttributes = NewUserBuilder("user-key").
14-
Secondary("s").
15-
IP("i").
16-
Country("c").
17-
Email("e").
18-
FirstName("f").
19-
LastName("l").
20-
Avatar("a").
21-
Name("n").
22-
Anonymous(true).
23-
Custom("attr", ldvalue.String("value")).
24-
Build()
25-
benchmarkUserWithAllAttributesJSON = []byte(benchmarkUserWithAllAttributes.String())
26-
27-
benchmarkJSONResult []byte
9+
"gopkg.in/launchdarkly/go-jsonstream.v1/jreader"
10+
"gopkg.in/launchdarkly/go-jsonstream.v1/jwriter"
2811
)
2912

30-
func BenchmarkUserSerializationWithKeyOnly(b *testing.B) {
31-
for i := 0; i < b.N; i++ {
32-
benchmarkJSONResult, _ = json.Marshal(benchmarkUserWithAllAttributes)
13+
func doMarshalBenchmark(b *testing.B, marshalFn func(*User) ([]byte, error)) {
14+
for _, p := range makeBenchmarkMarshalTestParams() {
15+
b.Run(p.name, func(b *testing.B) {
16+
for i := 0; i < b.N; i++ {
17+
if benchmarkJSONResult, benchmarkErr = marshalFn(&p.user); benchmarkErr != nil {
18+
b.Fatal(benchmarkErr)
19+
}
20+
}
21+
})
3322
}
3423
}
3524

36-
func BenchmarkUserSerializationWithAllAttributes(b *testing.B) {
37-
for i := 0; i < b.N; i++ {
38-
benchmarkJSONResult, _ = json.Marshal(benchmarkSimpleUser)
25+
func doUnmarshalBenchmark(b *testing.B, unmarshalFn func(*User, []byte) error) {
26+
for _, p := range sharedtest.MakeOldUserUnmarshalingTestParams() {
27+
b.Run(p.Name, func(b *testing.B) {
28+
for i := 0; i < b.N; i++ {
29+
if benchmarkErr = unmarshalFn(&benchmarkUserResult, p.Data); benchmarkErr != nil {
30+
b.Fatal(benchmarkErr)
31+
}
32+
}
33+
})
3934
}
4035
}
4136

42-
func BenchmarkUserDeserializationWithKeyOnly(b *testing.B) {
43-
for i := 0; i < b.N; i++ {
44-
_ = json.Unmarshal(benchmarkSimpleUserJSON, &benchmarkUserResult)
45-
}
37+
func jsonMarshalTestFn(u *User) ([]byte, error) {
38+
return json.Marshal(u)
4639
}
4740

48-
func BenchmarkUserDeserializationWithAllAttributes(b *testing.B) {
49-
for i := 0; i < b.N; i++ {
50-
_ = json.Unmarshal(benchmarkUserWithAllAttributesJSON, &benchmarkUserResult)
51-
}
41+
func jsonStreamMarshalTestFn(u *User) ([]byte, error) {
42+
w := jwriter.NewWriter()
43+
u.WriteToJSONWriter(&w)
44+
return w.Bytes(), w.Error()
45+
}
46+
47+
func jsonUnmarshalTestFn(u *User, data []byte) error {
48+
return json.Unmarshal(data, u)
49+
}
50+
51+
func jsonStreamUnmarshalTestFn(u *User, data []byte) error {
52+
r := jreader.NewReader(data)
53+
u.ReadFromJSONReader(&r)
54+
return r.Error()
55+
}
56+
57+
func BenchmarkJSONMarshal(b *testing.B) {
58+
doMarshalBenchmark(b, jsonMarshalTestFn)
59+
}
60+
61+
func BenchmarkJSONStreamMarshal(b *testing.B) {
62+
doMarshalBenchmark(b, jsonStreamMarshalTestFn)
63+
}
64+
65+
func BenchmarkJSONUnmarshal(b *testing.B) {
66+
doUnmarshalBenchmark(b, jsonUnmarshalTestFn)
67+
}
68+
69+
func BenchmarkJSONStreamUnmarshal(b *testing.B) {
70+
doUnmarshalBenchmark(b, jsonStreamUnmarshalTestFn)
5271
}
Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
1+
//go:build launchdarkly_easyjson
12
// +build launchdarkly_easyjson
23

34
package lduser
45

56
import (
67
"testing"
78

8-
"github.com/mailru/easyjson/jlexer"
9-
"github.com/mailru/easyjson/jwriter"
9+
easyjson "github.com/mailru/easyjson"
1010
)
1111

12-
func BenchmarkUserSerializationWithAllAttributesEasyJSON(b *testing.B) {
13-
for i := 0; i < b.N; i++ {
14-
writer := jwriter.Writer{}
15-
benchmarkSimpleUser.MarshalEasyJSON(&writer)
16-
}
12+
func easyJSONMarshalTestFn(u *User) ([]byte, error) {
13+
return easyjson.Marshal(u)
1714
}
1815

19-
func BenchmarkUserDeserializationWithAllAttributesEasyJSON(b *testing.B) {
20-
for i := 0; i < b.N; i++ {
21-
lexer := jlexer.Lexer{Data: benchmarkUserWithAllAttributesJSON}
22-
benchmarkUserResult.UnmarshalEasyJSON(&lexer)
23-
}
16+
func easyJSONUnmarshalTestFn(u *User, data []byte) error {
17+
return easyjson.Unmarshal(data, u)
18+
}
19+
20+
func BenchmarkEasyJSONMarshal(b *testing.B) {
21+
doMarshalBenchmark(b, easyJSONMarshalTestFn)
22+
}
23+
24+
func BenchmarkEasyJSONUnmarshal(b *testing.B) {
25+
doUnmarshalBenchmark(b, easyJSONUnmarshalTestFn)
2426
}

0 commit comments

Comments
 (0)