Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 4b00cde

Browse files
authored
Merge pull request #246 from launchdarkly/eb/ch77594/coverage-4-misc
(5.0 - #10) test coverage improvements + minor fixes
2 parents 6ddb11b + fc3d40e commit 4b00cde

26 files changed

+900
-190
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ The project in the `benchmarks` subdirectory uses [JMH](https://openjdk.java.net
4848

4949
## Code coverage
5050

51-
It is important to keep unit test coverage as close to 100% as possible in this project.
51+
It is important to keep unit test coverage as close to 100% as possible in this project. You can view the latest code coverage report in CircleCI, as `coverage/html/index.html` in the artifacts for the "Java 11 - Linux - OpenJDK" job. You can also run the report locally with `./gradlew jacocoTestCoverage` and view `./build/reports/jacoco/test`.
5252

53-
Sometimes a gap in coverage is unavoidable, usually because the compiler requires us to provide a code path for some condition that in practice can't happen and can't be tested, or because of a known issue with the code coverage tool. Please handle all such cases as follows:
54-
55-
* Mark the code with an explanatory comment beginning with "COVERAGE:".
56-
57-
The current coverage report can be observed by running `./gradlew jacocoTestReport` and viewing `build/reports/jacoco/test/html/index.html`. This report is also produced as an artifact of the CircleCI build for the most recent Java version.
53+
Sometimes a gap in coverage is unavoidable, usually because the compiler requires us to provide a code path for some condition that in practice can't happen and can't be tested, or because of a known issue with the code coverage tool. In all such cases, please mark the code with an explanatory comment beginning with "COVERAGE:".

src/main/java/com/launchdarkly/sdk/server/Components.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
* @since 4.0.0
5151
*/
5252
public abstract class Components {
53+
private Components() {}
54+
5355
/**
5456
* Returns a configuration object for using the default in-memory implementation of a data store.
5557
* <p>

src/main/java/com/launchdarkly/sdk/server/EventOutputFormatter.java

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
* Transforms analytics events and summary data into the JSON format that we send to LaunchDarkly.
1717
* Rather than creating intermediate objects to represent this schema, we use the Gson streaming
1818
* output API to construct JSON directly.
19+
*
20+
* Test coverage for this logic is in EventOutputTest and DefaultEventProcessorOutputTest.
1921
*/
2022
final class EventOutputFormatter {
2123
private final EventsConfiguration config;
@@ -26,14 +28,12 @@ final class EventOutputFormatter {
2628
this.gson = JsonHelpers.gsonInstanceForEventsSerialization(config);
2729
}
2830

29-
int writeOutputEvents(Event[] events, EventSummarizer.EventSummary summary, Writer writer) throws IOException {
30-
int count = 0;
31+
final int writeOutputEvents(Event[] events, EventSummarizer.EventSummary summary, Writer writer) throws IOException {
32+
int count = events.length;
3133
try (JsonWriter jsonWriter = new JsonWriter(writer)) {
3234
jsonWriter.beginArray();
3335
for (Event event: events) {
34-
if (writeOutputEvent(event, jsonWriter)) {
35-
count++;
36-
}
36+
writeOutputEvent(event, jsonWriter);
3737
}
3838
if (!summary.isEmpty()) {
3939
writeSummaryEvent(summary, jsonWriter);
@@ -44,7 +44,7 @@ int writeOutputEvents(Event[] events, EventSummarizer.EventSummary summary, Writ
4444
return count;
4545
}
4646

47-
private boolean writeOutputEvent(Event event, JsonWriter jw) throws IOException {
47+
private final void writeOutputEvent(Event event, JsonWriter jw) throws IOException {
4848
if (event instanceof Event.FeatureRequest) {
4949
Event.FeatureRequest fe = (Event.FeatureRequest)event;
5050
startEvent(fe, fe.isDebug() ? "debug" : "feature", fe.getKey(), jw);
@@ -83,13 +83,10 @@ private boolean writeOutputEvent(Event event, JsonWriter jw) throws IOException
8383
startEvent(event, "index", null, jw);
8484
writeUser(event.getUser(), jw);
8585
jw.endObject();
86-
} else {
87-
return false;
8886
}
89-
return true;
9087
}
9188

92-
private void writeSummaryEvent(EventSummarizer.EventSummary summary, JsonWriter jw) throws IOException {
89+
private final void writeSummaryEvent(EventSummarizer.EventSummary summary, JsonWriter jw) throws IOException {
9390
jw.beginObject();
9491

9592
jw.name("kind");
@@ -158,7 +155,7 @@ private void writeSummaryEvent(EventSummarizer.EventSummary summary, JsonWriter
158155
jw.endObject();
159156
}
160157

161-
private void startEvent(Event event, String kind, String key, JsonWriter jw) throws IOException {
158+
private final void startEvent(Event event, String kind, String key, JsonWriter jw) throws IOException {
162159
jw.beginObject();
163160
jw.name("kind");
164161
jw.value(kind);
@@ -170,7 +167,7 @@ private void startEvent(Event event, String kind, String key, JsonWriter jw) thr
170167
}
171168
}
172169

173-
private void writeUserOrKey(Event event, boolean forceInline, JsonWriter jw) throws IOException {
170+
private final void writeUserOrKey(Event event, boolean forceInline, JsonWriter jw) throws IOException {
174171
LDUser user = event.getUser();
175172
if (user != null) {
176173
if (config.inlineUsersInEvents || forceInline) {
@@ -182,14 +179,14 @@ private void writeUserOrKey(Event event, boolean forceInline, JsonWriter jw) thr
182179
}
183180
}
184181

185-
private void writeUser(LDUser user, JsonWriter jw) throws IOException {
182+
private final void writeUser(LDUser user, JsonWriter jw) throws IOException {
186183
jw.name("user");
187184
// config.gson is already set up to use our custom serializer, which knows about private attributes
188185
// and already uses the streaming approach
189186
gson.toJson(user, LDUser.class, jw);
190187
}
191188

192-
private void writeLDValue(String key, LDValue value, JsonWriter jw) throws IOException {
189+
private final void writeLDValue(String key, LDValue value, JsonWriter jw) throws IOException {
193190
if (value == null || value.isNull()) {
194191
return;
195192
}
@@ -198,38 +195,11 @@ private void writeLDValue(String key, LDValue value, JsonWriter jw) throws IOExc
198195
}
199196

200197
// This logic is so that we don't have to define multiple custom serializers for the various reason subclasses.
201-
private void writeEvaluationReason(String key, EvaluationReason er, JsonWriter jw) throws IOException {
198+
private final void writeEvaluationReason(String key, EvaluationReason er, JsonWriter jw) throws IOException {
202199
if (er == null) {
203200
return;
204201
}
205202
jw.name(key);
206-
207-
jw.beginObject();
208-
209-
jw.name("kind");
210-
jw.value(er.getKind().name());
211-
212-
switch (er.getKind()) {
213-
case ERROR:
214-
jw.name("errorKind");
215-
jw.value(er.getErrorKind().name());
216-
break;
217-
case PREREQUISITE_FAILED:
218-
jw.name("prerequisiteKey");
219-
jw.value(er.getPrerequisiteKey());
220-
break;
221-
case RULE_MATCH:
222-
jw.name("ruleIndex");
223-
jw.value(er.getRuleIndex());
224-
if (er.getRuleId() != null) {
225-
jw.name("ruleId");
226-
jw.value(er.getRuleId());
227-
}
228-
break;
229-
default:
230-
break;
231-
}
232-
233-
jw.endObject();
203+
gson.toJson(er, EvaluationReason.class, jw); // EvaluationReason defines its own custom serializer
234204
}
235205
}

src/main/java/com/launchdarkly/sdk/server/EventSummarizer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,15 @@ public boolean equals(Object other) {
9191
EventSummary o = (EventSummary)other;
9292
return o.counters.equals(counters) && startDate == o.startDate && endDate == o.endDate;
9393
}
94-
return true;
94+
return false;
9595
}
9696

9797
@Override
9898
public int hashCode() {
99-
return counters.hashCode() + 31 * ((int)startDate + 31 * (int)endDate);
99+
// We can't make meaningful hash codes for EventSummary, because the same counters could be
100+
// represented differently in our Map. It doesn't matter because there's no reason to use an
101+
// EventSummary instance as a hash key.
102+
return 0;
100103
}
101104
}
102105

@@ -157,6 +160,7 @@ public boolean equals(Object other)
157160
}
158161
return false;
159162
}
163+
160164
@Override
161165
public String toString() {
162166
return "(" + count + "," + flagValue + "," + defaultVal + ")";

src/main/java/com/launchdarkly/sdk/server/EventUserSerialization.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
import java.util.Set;
1212
import java.util.TreeSet;
1313

14-
class EventUserSerialization {
14+
abstract class EventUserSerialization {
15+
private EventUserSerialization() {}
1516

1617
// Used internally when including users in analytics events, to ensure that private attributes are stripped out.
1718
static class UserAdapterWithPrivateAttributeBehavior extends TypeAdapter<LDUser> {

src/main/java/com/launchdarkly/sdk/server/InMemoryDataStore.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
import com.google.common.collect.ImmutableList;
44
import com.google.common.collect.ImmutableMap;
5-
import com.launchdarkly.sdk.LDValue;
65
import com.launchdarkly.sdk.server.interfaces.DataStore;
76
import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider.CacheStats;
87
import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.DataKind;
98
import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.FullDataSet;
109
import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor;
1110
import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.KeyedItems;
12-
import com.launchdarkly.sdk.server.interfaces.DiagnosticDescription;
1311

1412
import java.io.IOException;
1513
import java.util.HashMap;
@@ -22,7 +20,7 @@
2220
* As of version 5.0.0, this is package-private; applications must use the factory method
2321
* {@link Components#inMemoryDataStore()}.
2422
*/
25-
class InMemoryDataStore implements DataStore, DiagnosticDescription {
23+
class InMemoryDataStore implements DataStore {
2624
private volatile ImmutableMap<DataKind, Map<String, ItemDescriptor>> allData = ImmutableMap.of();
2725
private volatile boolean initialized = false;
2826
private Object writeLock = new Object();
@@ -120,9 +118,4 @@ public CacheStats getCacheStats() {
120118
public void close() throws IOException {
121119
return;
122120
}
123-
124-
@Override
125-
public LDValue describeConfiguration(LDConfig config) {
126-
return LDValue.of("memory");
127-
}
128121
}

src/main/java/com/launchdarkly/sdk/server/JsonHelpers.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static com.launchdarkly.sdk.server.DataModel.SEGMENTS;
2323

2424
abstract class JsonHelpers {
25+
private JsonHelpers() {}
26+
2527
private static final Gson gson = new Gson();
2628

2729
/**

src/main/java/com/launchdarkly/sdk/server/LDConfig.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public final class LDConfig {
2020
static final URI DEFAULT_EVENTS_URI = URI.create("https://events.launchdarkly.com");
2121
static final URI DEFAULT_STREAM_URI = URI.create("https://stream.launchdarkly.com");
2222

23-
private static final Duration DEFAULT_START_WAIT = Duration.ofSeconds(5);
23+
static final Duration DEFAULT_START_WAIT = Duration.ofSeconds(5);
2424

2525
protected static final LDConfig DEFAULT = new Builder().build();
2626

@@ -49,18 +49,6 @@ protected LDConfig(Builder builder) {
4949
this.threadPriority = builder.threadPriority;
5050
}
5151

52-
LDConfig(LDConfig config) {
53-
this.dataSourceFactory = config.dataSourceFactory;
54-
this.dataStoreFactory = config.dataStoreFactory;
55-
this.diagnosticOptOut = config.diagnosticOptOut;
56-
this.eventProcessorFactory = config.eventProcessorFactory;
57-
this.httpConfig = config.httpConfig;
58-
this.loggingConfig = config.loggingConfig;
59-
this.offline = config.offline;
60-
this.startWait = config.startWait;
61-
this.threadPriority = config.threadPriority;
62-
}
63-
6452
/**
6553
* A <a href="http://en.wikipedia.org/wiki/Builder_pattern">builder</a> that helps construct
6654
* {@link com.launchdarkly.sdk.server.LDConfig} objects. Builder calls can be chained, enabling the

src/main/java/com/launchdarkly/sdk/server/SemanticVersion.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ public InvalidVersionException(String message) {
2424
private final int minor;
2525
private final int patch;
2626
private final String prerelease;
27+
private final String[] prereleaseComponents;
2728
private final String build;
2829

2930
public SemanticVersion(int major, int minor, int patch, String prerelease, String build) {
3031
this.major = major;
3132
this.minor = minor;
3233
this.patch = patch;
3334
this.prerelease = prerelease;
35+
this.prereleaseComponents = prerelease == null ? null : prerelease.split("\\.");
3436
this.build = build;
3537
}
3638

@@ -89,6 +91,7 @@ public static SemanticVersion parse(String input, boolean allowMissingMinorAndPa
8991
minor = matcher.group("minor") == null ? 0 : Integer.parseInt(matcher.group("minor"));
9092
patch = matcher.group("patch") == null ? 0 : Integer.parseInt(matcher.group("patch"));
9193
} catch (NumberFormatException e) {
94+
// COVERAGE: This should be impossible, because our regex should only match if these strings are numeric.
9295
throw new InvalidVersionException("Invalid semantic version");
9396
}
9497
String prerelease = matcher.group("prerel");
@@ -129,7 +132,7 @@ public int comparePrecedence(SemanticVersion other) {
129132
if (other.prerelease == null) {
130133
return -1;
131134
}
132-
return compareIdentifiers(prerelease.split("\\."), other.prerelease.split("\\."));
135+
return compareIdentifiers(prereleaseComponents, other.prereleaseComponents);
133136
}
134137

135138
private int compareIdentifiers(String[] ids1, String[] ids2) {

src/main/java/com/launchdarkly/sdk/server/Util.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import okhttp3.Response;
2020
import okhttp3.Route;
2121

22-
class Util {
22+
abstract class Util {
23+
private Util() {}
24+
2325
static Headers.Builder getHeadersBuilderFor(String sdkKey, HttpConfiguration config) {
2426
Headers.Builder builder = new Headers.Builder()
2527
.add("Authorization", sdkKey)

0 commit comments

Comments
 (0)