Skip to content

Commit 125bb14

Browse files
authored
[4.4] TestKit backend: add full support for temporal types
Cherry-pick: neo4j#1259
1 parent 3c0b9ab commit 125bb14

28 files changed

+1004
-93
lines changed

testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import neo4j.org.testkit.backend.holder.TransactionHolder;
3838
import neo4j.org.testkit.backend.messages.requests.TestkitCallbackResult;
3939
import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
40-
import org.neo4j.driver.exceptions.Neo4jException;
4140
import org.neo4j.driver.internal.cluster.RoutingTableRegistry;
4241
import reactor.core.publisher.Mono;
4342

@@ -63,7 +62,7 @@ public class TestkitState {
6362
private final Map<String, RxTransactionHolder> transactionIdToRxTransactionHolder = new HashMap<>();
6463

6564
@Getter
66-
private final Map<String, Neo4jException> errors = new HashMap<>();
65+
private final Map<String, Exception> errors = new HashMap<>();
6766

6867
private final AtomicInteger idGenerator = new AtomicInteger(0);
6968

testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.netty.channel.Channel;
2222
import io.netty.channel.ChannelHandlerContext;
2323
import io.netty.channel.ChannelInboundHandlerAdapter;
24+
import java.time.zone.ZoneRulesException;
2425
import java.util.concurrent.CompletableFuture;
2526
import java.util.concurrent.CompletionException;
2627
import java.util.concurrent.CompletionStage;
@@ -79,7 +80,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
7980
}
8081
});
8182
} catch (Throwable throwable) {
82-
ctx.writeAndFlush(createErrorResponse(throwable));
83+
exceptionCaught(ctx, throwable);
8384
}
8485
});
8586
}
@@ -95,6 +96,11 @@ private static CompletionStage<TestkitResponse> wrapSyncRequest(
9596
return result;
9697
}
9798

99+
@Override
100+
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
101+
ctx.writeAndFlush(createErrorResponse(cause));
102+
}
103+
98104
private TestkitResponse createErrorResponse(Throwable throwable) {
99105
if (throwable instanceof CompletionException) {
100106
throwable = throwable.getCause();
@@ -111,8 +117,11 @@ private TestkitResponse createErrorResponse(Throwable throwable) {
111117
.msg(e.getMessage())
112118
.build())
113119
.build();
114-
} else if (isConnectionPoolClosedException(throwable) || throwable instanceof UntrustedServerException) {
120+
} else if (isConnectionPoolClosedException(throwable)
121+
|| throwable instanceof UntrustedServerException
122+
|| throwable instanceof ZoneRulesException) {
115123
String id = testkitState.newId();
124+
testkitState.getErrors().put(id, (Exception) throwable);
116125
return DriverError.builder()
117126
.data(DriverError.DriverErrorBody.builder()
118127
.id(id)

testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestResponseMapperHandler.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*/
1919
package neo4j.org.testkit.backend.channel.handler;
2020

21-
import com.fasterxml.jackson.core.JsonProcessingException;
2221
import com.fasterxml.jackson.databind.DeserializationFeature;
2322
import com.fasterxml.jackson.databind.ObjectMapper;
2423
import io.netty.channel.ChannelDuplexHandler;
@@ -32,14 +31,10 @@ public class TestkitRequestResponseMapperHandler extends ChannelDuplexHandler {
3231
private final ObjectMapper objectMapper = newObjectMapper();
3332

3433
@Override
35-
public void channelRead(ChannelHandlerContext ctx, Object msg) {
34+
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
3635
String testkitMessage = (String) msg;
3736
TestkitRequest testkitRequest;
38-
try {
39-
testkitRequest = objectMapper.readValue(testkitMessage, TestkitRequest.class);
40-
} catch (JsonProcessingException e) {
41-
throw new RuntimeException("Failed to deserialize Testkit message", e);
42-
}
37+
testkitRequest = objectMapper.readValue(testkitMessage, TestkitRequest.class);
4338
ctx.fireChannelRead(testkitRequest);
4439
}
4540

testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,62 @@
1919
package neo4j.org.testkit.backend.messages;
2020

2121
import com.fasterxml.jackson.databind.module.SimpleModule;
22-
import java.time.ZonedDateTime;
22+
import java.time.LocalDate;
2323
import java.util.List;
24+
import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDateDeserializer;
2425
import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDateTimeDeserializer;
26+
import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDurationDeserializer;
27+
import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherTimeDeserializer;
2528
import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitListDeserializer;
29+
import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherDateTime;
30+
import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherTime;
2631
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitBookmarkSerializer;
32+
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitDateTimeValueSerializer;
33+
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitDateValueSerializer;
34+
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitDurationValueSerializer;
2735
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitListValueSerializer;
36+
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitLocalDateTimeValueSerializer;
37+
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitLocalTimeValueSerializer;
2838
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitMapValueSerializer;
2939
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitNodeValueSerializer;
3040
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitPathValueSerializer;
3141
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitRecordSerializer;
3242
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitRelationshipValueSerializer;
43+
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitTimeValueSerializer;
3344
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitValueSerializer;
3445
import org.neo4j.driver.Bookmark;
3546
import org.neo4j.driver.Record;
3647
import org.neo4j.driver.Value;
48+
import org.neo4j.driver.internal.value.DateTimeValue;
49+
import org.neo4j.driver.internal.value.DateValue;
50+
import org.neo4j.driver.internal.value.DurationValue;
3751
import org.neo4j.driver.internal.value.ListValue;
52+
import org.neo4j.driver.internal.value.LocalDateTimeValue;
53+
import org.neo4j.driver.internal.value.LocalTimeValue;
3854
import org.neo4j.driver.internal.value.MapValue;
3955
import org.neo4j.driver.internal.value.NodeValue;
4056
import org.neo4j.driver.internal.value.PathValue;
4157
import org.neo4j.driver.internal.value.RelationshipValue;
58+
import org.neo4j.driver.internal.value.TimeValue;
59+
import org.neo4j.driver.types.IsoDuration;
4260

4361
public class TestkitModule extends SimpleModule {
4462
public TestkitModule() {
4563
this.addDeserializer(List.class, new TestkitListDeserializer());
46-
this.addDeserializer(ZonedDateTime.class, new TestkitCypherDateTimeDeserializer());
64+
this.addDeserializer(CypherDateTime.class, new TestkitCypherDateTimeDeserializer());
65+
this.addDeserializer(CypherTime.class, new TestkitCypherTimeDeserializer());
66+
this.addDeserializer(IsoDuration.class, new TestkitCypherDurationDeserializer());
67+
this.addDeserializer(LocalDate.class, new TestkitCypherDateDeserializer());
4768

4869
this.addSerializer(Value.class, new TestkitValueSerializer());
4970
this.addSerializer(NodeValue.class, new TestkitNodeValueSerializer());
5071
this.addSerializer(ListValue.class, new TestkitListValueSerializer());
72+
this.addSerializer(DateTimeValue.class, new TestkitDateTimeValueSerializer());
73+
this.addSerializer(DateValue.class, new TestkitDateValueSerializer());
74+
this.addSerializer(DurationValue.class, new TestkitDurationValueSerializer());
75+
this.addSerializer(LocalDateTimeValue.class, new TestkitLocalDateTimeValueSerializer());
76+
this.addSerializer(LocalTimeValue.class, new TestkitLocalTimeValueSerializer());
77+
this.addSerializer(TimeValue.class, new TestkitTimeValueSerializer());
5178
this.addSerializer(Record.class, new TestkitRecordSerializer());
5279
this.addSerializer(MapValue.class, new TestkitMapValueSerializer());
5380
this.addSerializer(Bookmark.class, new TestkitBookmarkSerializer());

testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionReadTransaction.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import org.neo4j.driver.TransactionWork;
3636
import org.neo4j.driver.async.AsyncSession;
3737
import org.neo4j.driver.async.AsyncTransactionWork;
38-
import org.neo4j.driver.exceptions.Neo4jException;
3938
import org.neo4j.driver.reactive.RxTransactionWork;
4039
import org.reactivestreams.Publisher;
4140
import reactor.core.publisher.Mono;
@@ -106,12 +105,10 @@ private TransactionWork<Void> handle(TestkitState testkitState, SessionHolder se
106105
if (workThrowable instanceof ExecutionException) {
107106
workThrowable = workThrowable.getCause();
108107
}
109-
if (workThrowable instanceof Neo4jException) {
110-
throw (Neo4jException) workThrowable;
111-
} else {
112-
throw new RuntimeException(
113-
"Unexpected exception occurred in transaction work function", workThrowable);
108+
if (workThrowable instanceof RuntimeException) {
109+
throw (RuntimeException) workThrowable;
114110
}
111+
throw new RuntimeException("Unexpected exception occurred in transaction work function", workThrowable);
115112
}
116113
};
117114
}

testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionWriteTransaction.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.neo4j.driver.TransactionWork;
3737
import org.neo4j.driver.async.AsyncSession;
3838
import org.neo4j.driver.async.AsyncTransactionWork;
39-
import org.neo4j.driver.exceptions.Neo4jException;
4039
import org.neo4j.driver.reactive.RxTransactionWork;
4140
import org.reactivestreams.Publisher;
4241
import reactor.core.publisher.Mono;
@@ -107,12 +106,10 @@ private TransactionWork<Void> handle(TestkitState testkitState, SessionHolder se
107106
if (workThrowable instanceof ExecutionException) {
108107
workThrowable = workThrowable.getCause();
109108
}
110-
if (workThrowable instanceof Neo4jException) {
111-
throw (Neo4jException) workThrowable;
112-
} else {
113-
throw new RuntimeException(
114-
"Unexpected exception occurred in transaction work function", workThrowable);
109+
if (workThrowable instanceof RuntimeException) {
110+
throw (RuntimeException) workThrowable;
115111
}
112+
throw new RuntimeException("Unexpected exception occurred in transaction work function", workThrowable);
116113
}
117114
};
118115
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package neo4j.org.testkit.backend.messages.requests;
20+
21+
import java.time.DateTimeException;
22+
import java.time.ZoneId;
23+
import java.time.ZonedDateTime;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
import java.util.concurrent.CompletableFuture;
27+
import java.util.concurrent.CompletionStage;
28+
import lombok.Getter;
29+
import lombok.Setter;
30+
import neo4j.org.testkit.backend.TestkitState;
31+
import neo4j.org.testkit.backend.messages.responses.RunTest;
32+
import neo4j.org.testkit.backend.messages.responses.SkipTest;
33+
import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
34+
import reactor.core.publisher.Mono;
35+
36+
@Setter
37+
@Getter
38+
public class StartSubTest implements TestkitRequest {
39+
interface SkipDeciderInterface {
40+
SkipDecision check(Map<String, Object> params);
41+
}
42+
43+
public static class SkipDecision {
44+
private final boolean skipped;
45+
private final String reason;
46+
47+
public SkipDecision(boolean skipped, String reason) {
48+
this.skipped = skipped;
49+
this.reason = reason;
50+
}
51+
52+
public boolean isSkipped() {
53+
return skipped;
54+
}
55+
56+
public String getReason() {
57+
return reason;
58+
}
59+
60+
static SkipDecision ofNonSkipped() {
61+
return new SkipDecision(false, null);
62+
}
63+
64+
static SkipDecision ofSkipped(String reason) {
65+
return new SkipDecision(true, reason);
66+
}
67+
}
68+
69+
private static final Map<String, SkipDeciderInterface> COMMON_SKIP_PATTERN_TO_CHECK = new HashMap<>();
70+
private static final Map<String, SkipDeciderInterface> ASYNC_SKIP_PATTERN_TO_CHECK = new HashMap<>();
71+
private static final Map<String, SkipDeciderInterface> REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK = new HashMap<>();
72+
private static final Map<String, SkipDeciderInterface> REACTIVE_SKIP_PATTERN_TO_CHECK = new HashMap<>();
73+
74+
private static SkipDecision checkTzIdSupported(Map<String, Object> params) {
75+
String tzId = (String) params.get("tz_id");
76+
try {
77+
ZoneId.of(tzId);
78+
return SkipDecision.ofNonSkipped();
79+
} catch (DateTimeException e) {
80+
return SkipDecision.ofSkipped("Timezone not supported: " + tzId);
81+
}
82+
}
83+
84+
private static SkipDecision checkDateTimeSupported(Map<String, Object> params) {
85+
@SuppressWarnings("unchecked")
86+
HashMap<String, Object> dt_param = (HashMap<String, Object>) params.get("dt");
87+
if (dt_param == null) {
88+
throw new RuntimeException("params expected to contain 'dt'");
89+
}
90+
@SuppressWarnings("unchecked")
91+
HashMap<String, Object> data = (HashMap<String, Object>) dt_param.get("data");
92+
if (data == null) {
93+
throw new RuntimeException("param 'dt' expected to contain 'data'");
94+
}
95+
Integer year = (Integer) data.get("year");
96+
Integer month = (Integer) data.get("month");
97+
Integer day = (Integer) data.get("day");
98+
Integer hour = (Integer) data.get("hour");
99+
Integer minute = (Integer) data.get("minute");
100+
Integer second = (Integer) data.get("second");
101+
Integer nano = (Integer) data.get("nanosecond");
102+
Integer utcOffset = (Integer) data.get("utc_offset_s");
103+
String tzId = (String) data.get("timezone_id");
104+
try {
105+
ZonedDateTime dt = ZonedDateTime.of(year, month, day, hour, minute, second, nano, ZoneId.of(tzId));
106+
if (dt.getOffset().getTotalSeconds() != utcOffset) {
107+
throw new DateTimeException(String.format(
108+
"Unmatched UTC offset. TestKit expected %d, local zone db yielded %d",
109+
utcOffset, dt.getOffset().getTotalSeconds()));
110+
}
111+
return SkipDecision.ofNonSkipped();
112+
} catch (DateTimeException e) {
113+
return SkipDecision.ofSkipped("DateTime not supported: " + e.getMessage());
114+
}
115+
}
116+
117+
static {
118+
COMMON_SKIP_PATTERN_TO_CHECK.put(
119+
"neo4j\\.datatypes\\.test_temporal_types\\.TestDataTypes\\.test_should_echo_all_timezone_ids",
120+
StartSubTest::checkDateTimeSupported);
121+
COMMON_SKIP_PATTERN_TO_CHECK.put(
122+
"neo4j\\.datatypes\\.test_temporal_types\\.TestDataTypes\\.test_date_time_cypher_created_tz_id",
123+
StartSubTest::checkTzIdSupported);
124+
125+
ASYNC_SKIP_PATTERN_TO_CHECK.putAll(COMMON_SKIP_PATTERN_TO_CHECK);
126+
127+
REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK.putAll(COMMON_SKIP_PATTERN_TO_CHECK);
128+
129+
REACTIVE_SKIP_PATTERN_TO_CHECK.putAll(COMMON_SKIP_PATTERN_TO_CHECK);
130+
}
131+
132+
private StartSubTestBody data;
133+
134+
public static boolean decidePerSubTest(String testName) {
135+
return skipPatternMatches(testName, COMMON_SKIP_PATTERN_TO_CHECK);
136+
}
137+
138+
public static boolean decidePerSubTestAsync(String testName) {
139+
return skipPatternMatches(testName, ASYNC_SKIP_PATTERN_TO_CHECK);
140+
}
141+
142+
public static boolean decidePerSubTestReactiveLegacy(String testName) {
143+
return skipPatternMatches(testName, REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK);
144+
}
145+
146+
public static boolean decidePerSubTestReactive(String testName) {
147+
return skipPatternMatches(testName, REACTIVE_SKIP_PATTERN_TO_CHECK);
148+
}
149+
150+
private static boolean skipPatternMatches(
151+
String testName, Map<String, SkipDeciderInterface> skipPatternToFunction) {
152+
return skipPatternToFunction.entrySet().stream().anyMatch(entry -> testName.matches(entry.getKey()));
153+
}
154+
155+
@Override
156+
public TestkitResponse process(TestkitState testkitState) {
157+
return createResponse(COMMON_SKIP_PATTERN_TO_CHECK);
158+
}
159+
160+
@Override
161+
public CompletionStage<TestkitResponse> processAsync(TestkitState testkitState) {
162+
TestkitResponse testkitResponse = createResponse(ASYNC_SKIP_PATTERN_TO_CHECK);
163+
return CompletableFuture.completedFuture(testkitResponse);
164+
}
165+
166+
@Override
167+
public Mono<TestkitResponse> processRx(TestkitState testkitState) {
168+
TestkitResponse testkitResponse = createResponse(REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK);
169+
return Mono.just(testkitResponse);
170+
}
171+
172+
private TestkitResponse createResponse(Map<String, SkipDeciderInterface> skipPatternToCheck) {
173+
return skipPatternToCheck.entrySet().stream()
174+
.filter(entry -> data.getTestName().matches(entry.getKey()))
175+
.findFirst()
176+
.map(entry -> {
177+
SkipDecision decision = entry.getValue().check(data.getSubtestArguments());
178+
if (decision.isSkipped()) {
179+
return SkipTest.builder()
180+
.data(SkipTest.SkipTestBody.builder()
181+
.reason(decision.getReason())
182+
.build())
183+
.build();
184+
}
185+
return RunTest.builder().build();
186+
})
187+
.orElse(RunTest.builder().build());
188+
}
189+
190+
@Setter
191+
@Getter
192+
public static class StartSubTestBody {
193+
private String testName;
194+
private Map<String, Object> subtestArguments;
195+
}
196+
}

0 commit comments

Comments
 (0)