Skip to content

Commit

Permalink
prepare 5.9.0 release (#70)
Browse files Browse the repository at this point in the history
* add script to parse coverage profile and flag uncovered code

* include coverage profile in artifacts

* better output

* revert addition of analysis script

* rm obsolete message

* more coverage improvements, misc code cleanup

* use new coverage tool

* rm unused

* fix to capture HTML coverage reports

* exclude sharedtest package from coverage

* misc makefile improvements

* fill in test coverage for everything except StreamProcessor and databases; move some things to internal

* can't rely on a specific error message due to platform differences

* fix flaky test

* full test coverage of database integrations

* lint

* add godoc badge

* formatting

* formatting

* better badge

* minor CI cleanup: use newer images, drop Go 1.12 build (#239)

* make the SDK a module

* go mod tidy

* fix bug where nil references are passed to persistent stores (#241)

* remove support for indirect patch in stream

* (5.0) complete test coverage for StreamProcessor

* turn on coverage enforcement

* fix logic for disabling db packages in coverage

* move some internal code around for clearer organization

* make sharedtest package internal, put public test helpers in new package

* DRY

* comment about data store tests

* use latest public go-sdk-common, go-sdk-events, go-server-sdk-evaluation

* remove unused polling logic for indirect/patch

* update go-server-sdk-evaluation for data model changes

* rm unnecessary type aliases that broke tests

* fix test that used an overly sensitive equality test for a flag without variations

* another test issue just like the previous commit

* move more test helpers + misc fixes

* test coverage

* move data store test suite to subpackage

* (5.0) fix bug in deleted item serialization

* more test coverage

* (5.0) interface cleanup: move data store types out of the way

* dependency fixes

* (5.0 - #7) updates for go-sdk-events API changes (#251)

* (5.0) miscellaneous doc comment improvements

* fix remaining godoc.org links

* custom badge for pkg.go.dev

* fix go.dev badge

* add API docs link under Learn More

* update dependencies

* use test sandboxing to validate persistent store test failure modes

* lint (use more general interface)

* (5.0) specify proxy URL as a string

* fix test

* fix test

* fix another test

* 5.0.0-beta.1

* add client decorator that temporarily disables events

* add scoped evaluation without events

* remove all database subpackages (being moved to separate projects) (#256)

* bump dependency versions and use ldlogtest package

* lint

* code example

* move AllFlagsState-related types out of main package, add builder

* rm obsolete references to HTTPOptions setter

* minor Makefile fix

* add clientSideAvailability to flag model

* prepare 4.17.3 release (#36)

* Releasing version 4.17.3

* (v5) use new client-side availability fields (#262)

* ensure SDK non-test code has no module-only dependencies (#263)

* 5.0.0-beta.6

* update redis repo name in comments

* use optional int type

* misc cleanup

* update dependencies

* add Go 1.15 CI job, use newer CircleCI images

* typo

* fix CI working directory

* fix CI syntax

* update readme to mention Go 1.15

* drop Go 1.13 build

* use go-server-sdk-evaluation 1.0.0-beta.5

* (#2) add test data source (#266)

* logging fixes/standardization

* use non-beta packages, remove prerelease info

* update release metadata

* make Releaser create release tags with and without the "v" prefix

* update to go-server-sdk-evaluation 1.0.1 for "less omitempty in flag JSON" fix (#272)

* fix comment typos

* serialize deleted item placeholders with full set of properties (#274)

* update go-sdk-common to 2.0.1

* bump eventsource to 1.6.2 for ch95617 logging fix (#275)

* use new jsonstream API (#277)

* don't require path property in put event

* update alpha dependencies

* update for go-jsonstream API changes

* build in Windows with Go 1.14

* bump go-sdk-common version to get user JSON parsing fix

* remove excessive logging in ldfilewatch, add log message for reloading file

* fail fast if SDK key has invalid characters, don't log the key

* bump go-server-sdk-evaluation to get semver parsing fix

* remove spurious logging of stream data

* merge in alias event support

* Removed the guides link

* update to go-server-sdk-evaluation 1.1.2 for ch100737 fix

* (#1) add unbounded segments interfaces, status provider, test suite (#288)

* add unbounded segments interfaces, status provider, test suite

* lint

* add package comment

* better component organization

* rename cache time to poll interval, improve tests

* rename segment keys to segment refs in unbounded segments API

* fix naming some more

* (#2) simplify(?) broadcasters using reflection (#289)

* (#3) misc cleanup of data store test suites (#290)

* (#4) finish adding support for big ("unbounded") segment in evaluations (#291)

* (#5) change "unbounded segments" to "big segments" + use latest prerelease packages (#293)

* fix segment ref string format in tests, update evaluator version

* Point at exp-alloc feature branch of -common and -evaluation (#297)

This just repoints the Go SDK at the exp-alloc feature branch of the go-sdk-common-private and go-server-sdk-evaluation-private repos.

* expose big segment store wrapper component for reuse by Relay

* lint

* add DuplicateKeysHandling option to ldfiledata (#299)

* use latest prerelease tags of go-sdk-common, go-server-sdk-evaluation

* use prerelease tags of go-sdk-common and go-server-sdk-evaluation

* update dependencies for JSON number parsing bugfix

* use go-sdk-common 2.3.0, go-server-sdk-evaluation 1.3.0

* add big segments control methods for use by Relay

* rm unused

* update go-server-sdk-evaluation to 1.4.1 for big segments bugfix

* use Releaser v2 config, add Go 1.16 CI

* rename big segments to Big Segments

* remove "non-module-build" CI test which can no longer be run

* add CI job for Go 1.17; update linter & test coverage script

* Update pull request template to correct CONTRIBUTING.md link

* Remove reference to dep in CONTRIBUTING.md (#310)

* SC-128558: Allow users to set custom headers on requests (#308)

* Add ability to set custom headers via Headers method

* better tests for simple proxy configuration (#311)

* update evaluation engine to fix circular prereqs issue, add more eval error logging

* add ServiceEndpoints to configuration API (#314)

* fix setting of trackEvents/trackReason in AllFlagsState data when there's an experiment (#315)

* Go SDK test service for use with the SDK test harness (#312)

* fix AllFlagsState DetailsOnlyForTrackedFlags behavior

* fix reason logic

* enable big segments contract tests (#317)

* implement tags

* update doc comments to match latest spec

* more doc comment updates

Co-authored-by: Eli Bishop <eli@launchdarkly.com>
Co-authored-by: LaunchDarklyCI <LaunchDarklyCI@users.noreply.github.com>
Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com>
Co-authored-by: Harpo Roeder <hroeder@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: Casey Waldren <cwaldren@launchdarkly.com>
  • Loading branch information
9 people authored Mar 23, 2022
1 parent 380b7f4 commit 7f1cce2
Show file tree
Hide file tree
Showing 25 changed files with 1,088 additions and 33 deletions.
9 changes: 8 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
environment:
CIRCLE_TEST_REPORTS: /tmp/circle-reports
CIRCLE_ARTIFACTS: /tmp/circle-artifacts
TEST_HARNESS_PARAMS: -junit /tmp/circle-reports/contract-tests-junit.xml

steps:
- checkout
Expand Down Expand Up @@ -81,7 +82,13 @@ jobs:
name: Process test results
command: go-junit-report < $CIRCLE_ARTIFACTS/report.txt > $CIRCLE_TEST_REPORTS/junit.xml
when: always


- run: make build-contract-tests
- run:
command: make start-contract-test-service
background: true
- run: make run-contract-tests

- store_test_results:
path: /tmp/circle-reports

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ build/
go-server-sdk.test
allocations.out
.idea
.vscode
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,24 @@ $(LINTER_VERSION_FILE):

lint: $(LINTER_VERSION_FILE)
$(LINTER) run ./...


TEMP_TEST_OUTPUT=/tmp/sse-contract-test-service.log

build-contract-tests:
@cd testservice && go mod tidy && go build

start-contract-test-service: build-contract-tests
@./testservice/testservice

start-contract-test-service-bg:
@echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)"
@make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1

run-contract-tests:
@curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/v1.0.0/downloader/run.sh \
| VERSION=v1 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh

contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests

.PHONY: build-contract-tests start-contract-test-service start-contract-test-service-bg run-contract-tests contract-tests
33 changes: 25 additions & 8 deletions client_context_from_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package ldclient

import (
"errors"
"regexp"

"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog"
"gopkg.in/launchdarkly/go-server-sdk.v5/interfaces"
"gopkg.in/launchdarkly/go-server-sdk.v5/internal"
"gopkg.in/launchdarkly/go-server-sdk.v5/ldcomponents"
)

var validTagKeyOrValueRegex = regexp.MustCompile(`(?s)^[\w.-]*$`)

func newClientContextFromConfig(
sdkKey string,
config Config,
Expand All @@ -27,20 +31,25 @@ func newClientContextFromConfig(
ServiceEndpoints: config.ServiceEndpoints,
}

httpFactory := config.HTTP
if httpFactory == nil {
httpFactory = ldcomponents.HTTPConfiguration()
loggingFactory := config.Logging
if loggingFactory == nil {
loggingFactory = ldcomponents.Logging()
}
http, err := httpFactory.CreateHTTPConfiguration(basicConfig)
logging, err := loggingFactory.CreateLoggingConfiguration(basicConfig)
if err != nil {
return nil, err
}

loggingFactory := config.Logging
if loggingFactory == nil {
loggingFactory = ldcomponents.Logging()
basicConfig.ApplicationInfo.ApplicationID = validateTagValue(config.ApplicationInfo.ApplicationID,
"ApplicationID", logging.GetLoggers())
basicConfig.ApplicationInfo.ApplicationVersion = validateTagValue(config.ApplicationInfo.ApplicationVersion,
"ApplicationVersion", logging.GetLoggers())

httpFactory := config.HTTP
if httpFactory == nil {
httpFactory = ldcomponents.HTTPConfiguration()
}
logging, err := loggingFactory.CreateLoggingConfiguration(basicConfig)
http, err := httpFactory.CreateHTTPConfiguration(basicConfig)
if err != nil {
return nil, err
}
Expand All @@ -60,3 +69,11 @@ func stringIsValidHTTPHeaderValue(s string) bool {
}
return true
}

func validateTagValue(value, name string, loggers ldlog.Loggers) string {
if value != "" && !validTagKeyOrValueRegex.MatchString(value) {
loggers.Warnf("Value of Config.%s contained invalid characters and was discarded", name)
return ""
}
return value
}
6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,10 @@ type Config struct {
// may set the base URIs to whatever you want, although the SDK will still set the URI paths to
// the expected paths for LaunchDarkly services.
ServiceEndpoints interfaces.ServiceEndpoints

// Provides configuration of application metadata. See interfaces.ApplicationInfo.
//
// Application metadata may be used in LaunchDarkly analytics or other product features, but does not
// affect feature flag evaluations.
ApplicationInfo interfaces.ApplicationInfo
}
26 changes: 26 additions & 0 deletions interfaces/application_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package interfaces

// ApplicationInfo allows configuration of application metadata.
//
// Application metadata may be used in LaunchDarkly analytics or other product features, but does not
// affect feature flag evaluations.
//
// If you want to set non-default values for any of these fields, set the ApplicationInfo field
// in the SDK's Config struct.
type ApplicationInfo struct {
// ApplicationID is a unique identifier representing the application where the LaunchDarkly SDK is
// running.
//
// This can be specified as any string value as long as it only uses the following characters: ASCII
// letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be
// ignored.
ApplicationID string

// ApplicationVersion is a unique identifier representing the version of the application where the
// LaunchDarkly SDK is running.
//
// This can be specified as any string value as long as it only uses the following characters: ASCII
// letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be
// ignored.
ApplicationVersion string
}
3 changes: 3 additions & 0 deletions interfaces/client_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ type BasicConfiguration struct {

// ServiceEndpoints include any configured custom service URIs.
ServiceEndpoints ServiceEndpoints

// ApplicationInfo includes any configured application metadata.
ApplicationInfo ApplicationInfo
}
15 changes: 9 additions & 6 deletions interfaces/flagstate/flags_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ type FlagState struct {

// DebugEventsUntilDate is non-zero if event debugging is enabled for this flag until the specified time.
DebugEventsUntilDate ldtime.UnixMillisecondTime

// OmitDetails is true if, based on the options passed to AllFlagsState and the flag state, some of the
// metadata can be left out of the JSON representation.
OmitDetails bool
}

// Option is the interface for optional parameters that can be passed to LDClient.AllFlagsState.
Expand Down Expand Up @@ -143,8 +147,8 @@ func (a AllFlags) MarshalJSON() ([]byte, error) {
for key, flag := range a.flags {
flagObj := stateObj.Name(key).Object()
flagObj.Maybe("variation", flag.Variation.IsDefined()).Int(flag.Variation.IntValue())
flagObj.Name("version").Int(flag.Version)
if flag.Reason.IsDefined() {
flagObj.Maybe("version", !flag.OmitDetails).Int(flag.Version)
if flag.Reason.IsDefined() && !flag.OmitDetails {
flag.Reason.WriteToJSONWriter(flagObj.Name("reason"))
}
flagObj.Maybe("trackEvents", flag.TrackEvents).Bool(flag.TrackEvents)
Expand Down Expand Up @@ -189,14 +193,13 @@ func (b *AllFlagsBuilder) Build() AllFlags {
func (b *AllFlagsBuilder) AddFlag(flagKey string, flag FlagState) *AllFlagsBuilder {
// To save bandwidth, we include evaluation reasons only if 1. the application explicitly said to
// include them or 2. they must be included because of experimentation
wantReason := b.options.withReasons || flag.TrackReason
if wantReason && b.options.detailsOnlyIfTracked {
if b.options.detailsOnlyIfTracked {
if !flag.TrackEvents && !flag.TrackReason &&
!(flag.DebugEventsUntilDate != 0 && flag.DebugEventsUntilDate > ldtime.UnixMillisNow()) {
wantReason = false
flag.OmitDetails = true
}
}
if !wantReason {
if !b.options.withReasons && !flag.TrackReason {
flag.Reason = ldreason.EvaluationReason{}
}
b.state.flags[flagKey] = flag
Expand Down
46 changes: 35 additions & 11 deletions interfaces/flagstate/flags_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import (

"gopkg.in/launchdarkly/go-sdk-common.v2/ldreason"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldtime"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"

"github.com/stretchr/testify/assert"

"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
)

func TestAllFlags(t *testing.T) {
Expand Down Expand Up @@ -49,8 +48,8 @@ func TestAllFlags(t *testing.T) {

a1 := AllFlags{
flags: map[string]FlagState{
"flag1": FlagState{Value: ldvalue.String("value1")},
"flag2": FlagState{Value: ldvalue.String("value2")},
"flag1": {Value: ldvalue.String("value1")},
"flag2": {Value: ldvalue.String("value2")},
},
}
assert.Equal(t, map[string]ldvalue.Value{
Expand Down Expand Up @@ -138,6 +137,31 @@ func TestAllFlagsJSON(t *testing.T) {
"$flagsState":{
"flag1": {"variation":1,"version":1000,"reason":{"kind":"FALLTHROUGH"},"trackEvents":true,"trackReason":true}
}
}`, string(bytes))
})

t.Run("omitting details", func(t *testing.T) {
a := AllFlags{
valid: true,
flags: map[string]FlagState{
"flag1": {
Value: ldvalue.String("value1"),
Variation: ldvalue.NewOptionalInt(1),
Version: 1000,
Reason: ldreason.NewEvalReasonFallthrough(),
OmitDetails: true,
},
},
}
bytes, err := a.MarshalJSON()
assert.NoError(t, err)
assert.JSONEq(t,
`{
"$valid":true,
"flag1": "value1",
"$flagsState":{
"flag1": {"variation":1}
}
}`, string(bytes))
})
}
Expand Down Expand Up @@ -204,10 +228,10 @@ func TestAllFlagsBuilder(t *testing.T) {
}, a.flags)
})

t.Run("add flags with reasons only if tracked", func(t *testing.T) {
t.Run("add flags with details only if tracked", func(t *testing.T) {
b := NewAllFlagsBuilder(OptionWithReasons(), OptionDetailsOnlyForTrackedFlags())

// flag1 should not get a reason
// flag1 should not get full details
flag1 := FlagState{
Value: ldvalue.String("value1"),
Variation: ldvalue.NewOptionalInt(1),
Expand Down Expand Up @@ -258,14 +282,14 @@ func TestAllFlagsBuilder(t *testing.T) {
b.AddFlag("flag4", flag4)
b.AddFlag("flag5", flag5)

flag1WithoutReason, flag2WithoutReason := flag1, flag2
flag1WithoutReason.Reason = ldreason.EvaluationReason{}
flag2WithoutReason.Reason = ldreason.EvaluationReason{}
flag1WithoutDetails, flag2WithoutDetails := flag1, flag2
flag1WithoutDetails.OmitDetails = true
flag2WithoutDetails.OmitDetails = true

a := b.Build()
assert.Equal(t, map[string]FlagState{
"flag1": flag1WithoutReason,
"flag2": flag2WithoutReason,
"flag1": flag1WithoutDetails,
"flag2": flag2WithoutDetails,
"flag3": flag3,
"flag4": flag4,
"flag5": flag5,
Expand Down
16 changes: 9 additions & 7 deletions ldclient_evaluation_all_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,18 @@ func TestAllFlagsStateCanFilterForOnlyClientSideFlags(t *testing.T) {
func TestAllFlagsStateCanOmitDetailForUntrackedFlags(t *testing.T) {
futureTime := ldtime.UnixMillisNow() + 100000

// flag1 does not get a reason because neither event tracking nor debugging is on and there's no experiment
// flag1 does not get full detials because neither event tracking nor debugging is on and there's no experiment
flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).OffVariation(0).Variations(ldvalue.String("value1")).Build()

// flag2 gets a reason because event tracking is on
// flag2 gets full details because event tracking is on
flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).Variations(ldvalue.String("x"), ldvalue.String("value2")).
TrackEvents(true).Build()

// flag3 gets a reason because debugging is on
// flag3 gets full details because debugging is on
flag3 := ldbuilders.NewFlagBuilder("key3").Version(300).OffVariation(1).Variations(ldvalue.String("x"), ldvalue.String("value3")).
TrackEvents(false).DebugEventsUntilDate(futureTime).Build()

// flag4 gets a reason because there's an experiment (evaluation is a fallthrough and TrackEventsFallthrough is on)
// flag4 gets full details because there's an experiment (evaluation is a fallthrough and TrackEventsFallthrough is on)
flag4 := ldbuilders.NewFlagBuilder("key4").Version(400).On(true).FallthroughVariation(1).
Variations(ldvalue.String("x"), ldvalue.String("value4")).
TrackEvents(false).TrackEventsFallthrough(true).Build()
Expand All @@ -153,9 +153,11 @@ func TestAllFlagsStateCanOmitDetailForUntrackedFlags(t *testing.T) {

expected := flagstate.NewAllFlagsBuilder(flagstate.OptionWithReasons()).
AddFlag("key1", flagstate.FlagState{
Value: ldvalue.String("value1"),
Variation: ldvalue.NewOptionalInt(0),
Version: 100,
Value: ldvalue.String("value1"),
Variation: ldvalue.NewOptionalInt(0),
Version: 100,
Reason: ldreason.NewEvalReasonOff(),
OmitDetails: true,
}).
AddFlag("key2", flagstate.FlagState{
Value: ldvalue.String("value2"),
Expand Down
15 changes: 15 additions & 0 deletions ldcomponents/http_configuration_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"net/url"
"os"
"strings"
"time"

"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
Expand Down Expand Up @@ -187,6 +188,9 @@ func (b *HTTPConfigurationBuilder) CreateHTTPConfiguration(
if b.wrapperIdentifier != "" {
headers.Add("X-LaunchDarkly-Wrapper", b.wrapperIdentifier)
}
if tagsHeaderValue := buildTagsHeaderValue(basicConfiguration); tagsHeaderValue != "" {
headers.Add("X-LaunchDarkly-Tags", tagsHeaderValue)
}

// For consistency with other SDKs, custom headers are allowed to overwrite headers such as
// User-Agent and Authorization.
Expand Down Expand Up @@ -224,3 +228,14 @@ func (b *HTTPConfigurationBuilder) CreateHTTPConfiguration(
HTTPClientFactory: clientFactory,
}, nil
}

func buildTagsHeaderValue(basicConfig interfaces.BasicConfiguration) string {
var parts []string
if basicConfig.ApplicationInfo.ApplicationID != "" {
parts = append(parts, fmt.Sprintf("application-id/%s", basicConfig.ApplicationInfo.ApplicationID))
}
if basicConfig.ApplicationInfo.ApplicationVersion != "" {
parts = append(parts, fmt.Sprintf("application-version/%s", basicConfig.ApplicationInfo.ApplicationVersion))
}
return strings.Join(parts, " ")
}
16 changes: 16 additions & 0 deletions ldcomponents/http_configuration_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,20 @@ func TestHTTPConfigurationBuilder(t *testing.T) {
headers2 := c2.GetDefaultHeaders()
assert.Equal(t, "FancySDK/2.0", headers2.Get("X-LaunchDarkly-Wrapper"))
})

t.Run("tags header", func(t *testing.T) {
t.Run("no tags", func(t *testing.T) {
c, err := HTTPConfiguration().CreateHTTPConfiguration(basicConfig)
require.NoError(t, err)
assert.Nil(t, c.GetDefaultHeaders().Values("X-LaunchDarkly-Tags"))
})

t.Run("some tags", func(t *testing.T) {
bc := basicConfig
bc.ApplicationInfo = interfaces.ApplicationInfo{ApplicationID: "appid", ApplicationVersion: "appver"}
c, err := HTTPConfiguration().CreateHTTPConfiguration(bc)
require.NoError(t, err)
assert.Equal(t, "application-id/appid application-version/appver", c.GetDefaultHeaders().Get("X-LaunchDarkly-Tags"))
})
})
}
1 change: 1 addition & 0 deletions testservice/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testservice
7 changes: 7 additions & 0 deletions testservice/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SDK contract test service

This directory contains an implementation of the cross-platform SDK testing protocol defined by https://github.com/launchdarkly/sdk-test-harness. See that project's `README` for details of this protocol, and the kinds of SDK capabilities that are relevant to the contract tests. This code should not need to be updated unless the SDK has added or removed such capabilities.

To run these tests locally, run `make contract-tests` from the SDK project root directory. This downloads the correct version of the test harness tool automatically.

Or, to test against an in-progress local version of the test harness, run `make start-contract-test-service` from the SDK project root directory; then, in the root directory of the `sdk-test-harness` project, build the test harness and run it from the command line.
Loading

0 comments on commit 7f1cce2

Please sign in to comment.