Skip to content

Commit ae7fdcd

Browse files
committed
TestKit backend: add full support for temporal types
1 parent f7bcdf1 commit ae7fdcd

27 files changed

+1009
-94
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
@@ -40,7 +40,6 @@
4040
import neo4j.org.testkit.backend.holder.TransactionHolder;
4141
import neo4j.org.testkit.backend.messages.requests.TestkitCallbackResult;
4242
import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
43-
import org.neo4j.driver.exceptions.Neo4jException;
4443
import org.neo4j.driver.internal.cluster.RoutingTableRegistry;
4544
import reactor.core.publisher.Mono;
4645

@@ -69,7 +68,7 @@ public class TestkitState {
6968
private final Map<String, ReactiveTransactionHolder> transactionIdToReactiveTransactionHolder = new HashMap<>();
7069

7170
@Getter
72-
private final Map<String, Neo4jException> errors = new HashMap<>();
71+
private final Map<String, Exception> errors = new HashMap<>();
7372

7473
private final AtomicInteger idGenerator = new AtomicInteger(0);
7574

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

Lines changed: 4 additions & 1 deletion
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;
@@ -120,8 +121,10 @@ private TestkitResponse createErrorResponse(Throwable throwable) {
120121
.build();
121122
} else if (isConnectionPoolClosedException(throwable)
122123
|| throwable instanceof UntrustedServerException
123-
|| throwable instanceof NoSuchRecordException) {
124+
|| throwable instanceof NoSuchRecordException
125+
|| throwable instanceof ZoneRulesException) {
124126
String id = testkitState.newId();
127+
testkitState.getErrors().put(id, (Exception) throwable);
125128
return DriverError.builder()
126129
.data(DriverError.DriverErrorBody.builder()
127130
.id(id)

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,48 @@
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;
26-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitListValueSerializer;
27-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitMapValueSerializer;
28-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitNodeValueSerializer;
29-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitPathValueSerializer;
30-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitRecordSerializer;
31-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitRelationshipValueSerializer;
32-
import neo4j.org.testkit.backend.messages.responses.serializer.TestkitValueSerializer;
29+
import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherDateTime;
30+
import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherTime;
31+
import neo4j.org.testkit.backend.messages.responses.serializer.*;
3332
import org.neo4j.driver.Record;
3433
import org.neo4j.driver.Value;
34+
import org.neo4j.driver.internal.value.DateTimeValue;
35+
import org.neo4j.driver.internal.value.DateValue;
36+
import org.neo4j.driver.internal.value.DurationValue;
3537
import org.neo4j.driver.internal.value.ListValue;
38+
import org.neo4j.driver.internal.value.LocalDateTimeValue;
39+
import org.neo4j.driver.internal.value.LocalTimeValue;
3640
import org.neo4j.driver.internal.value.MapValue;
3741
import org.neo4j.driver.internal.value.NodeValue;
3842
import org.neo4j.driver.internal.value.PathValue;
3943
import org.neo4j.driver.internal.value.RelationshipValue;
44+
import org.neo4j.driver.internal.value.TimeValue;
45+
import org.neo4j.driver.types.IsoDuration;
4046

4147
public class TestkitModule extends SimpleModule {
4248
public TestkitModule() {
4349
this.addDeserializer(List.class, new TestkitListDeserializer());
44-
this.addDeserializer(ZonedDateTime.class, new TestkitCypherDateTimeDeserializer());
50+
this.addDeserializer(CypherDateTime.class, new TestkitCypherDateTimeDeserializer());
51+
this.addDeserializer(CypherTime.class, new TestkitCypherTimeDeserializer());
52+
this.addDeserializer(IsoDuration.class, new TestkitCypherDurationDeserializer());
53+
this.addDeserializer(LocalDate.class, new TestkitCypherDateDeserializer());
4554

4655
this.addSerializer(Value.class, new TestkitValueSerializer());
4756
this.addSerializer(NodeValue.class, new TestkitNodeValueSerializer());
4857
this.addSerializer(ListValue.class, new TestkitListValueSerializer());
58+
this.addSerializer(DateTimeValue.class, new TestkitDateTimeValueSerializer());
59+
this.addSerializer(DateValue.class, new TestkitDateValueSerializer());
60+
this.addSerializer(DurationValue.class, new TestkitDurationValueSerializer());
61+
this.addSerializer(LocalDateTimeValue.class, new TestkitLocalDateTimeValueSerializer());
62+
this.addSerializer(LocalTimeValue.class, new TestkitLocalTimeValueSerializer());
63+
this.addSerializer(TimeValue.class, new TestkitTimeValueSerializer());
4964
this.addSerializer(Record.class, new TestkitRecordSerializer());
5065
this.addSerializer(MapValue.class, new TestkitMapValueSerializer());
5166
this.addSerializer(PathValue.class, new TestkitPathValueSerializer());

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.concurrent.ExecutionException;
2424
import lombok.Getter;
2525
import lombok.Setter;
26-
import neo4j.org.testkit.backend.FrontendError;
2726
import neo4j.org.testkit.backend.ReactiveTransactionContextAdapter;
2827
import neo4j.org.testkit.backend.TestkitState;
2928
import neo4j.org.testkit.backend.holder.AsyncTransactionHolder;
@@ -38,7 +37,6 @@
3837
import org.neo4j.driver.TransactionWork;
3938
import org.neo4j.driver.async.AsyncSession;
4039
import org.neo4j.driver.async.AsyncTransactionWork;
41-
import org.neo4j.driver.exceptions.Neo4jException;
4240
import org.neo4j.driver.reactive.ReactiveTransactionCallback;
4341
import org.neo4j.driver.reactive.RxTransactionWork;
4442
import org.reactivestreams.Publisher;
@@ -129,11 +127,8 @@ private TransactionWork<Void> handle(TestkitState testkitState, SessionHolder se
129127
if (workThrowable instanceof ExecutionException) {
130128
workThrowable = workThrowable.getCause();
131129
}
132-
if (workThrowable instanceof Neo4jException) {
133-
throw (Neo4jException) workThrowable;
134-
}
135-
if (workThrowable instanceof FrontendError) {
136-
throw (FrontendError) workThrowable;
130+
if (workThrowable instanceof RuntimeException) {
131+
throw (RuntimeException) workThrowable;
137132
}
138133
throw new RuntimeException("Unexpected exception occurred in transaction work function", workThrowable);
139134
}

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.concurrent.ExecutionException;
2525
import lombok.Getter;
2626
import lombok.Setter;
27-
import neo4j.org.testkit.backend.FrontendError;
2827
import neo4j.org.testkit.backend.ReactiveTransactionContextAdapter;
2928
import neo4j.org.testkit.backend.TestkitState;
3029
import neo4j.org.testkit.backend.holder.AsyncTransactionHolder;
@@ -39,7 +38,6 @@
3938
import org.neo4j.driver.TransactionWork;
4039
import org.neo4j.driver.async.AsyncSession;
4140
import org.neo4j.driver.async.AsyncTransactionWork;
42-
import org.neo4j.driver.exceptions.Neo4jException;
4341
import org.neo4j.driver.reactive.ReactiveTransactionCallback;
4442
import org.neo4j.driver.reactive.RxTransactionWork;
4543
import org.reactivestreams.Publisher;
@@ -130,11 +128,8 @@ private TransactionWork<Void> handle(TestkitState testkitState, SessionHolder se
130128
if (workThrowable instanceof ExecutionException) {
131129
workThrowable = workThrowable.getCause();
132130
}
133-
if (workThrowable instanceof Neo4jException) {
134-
throw (Neo4jException) workThrowable;
135-
}
136-
if (workThrowable instanceof FrontendError) {
137-
throw (FrontendError) workThrowable;
131+
if (workThrowable instanceof RuntimeException) {
132+
throw (RuntimeException) workThrowable;
138133
}
139134
throw new RuntimeException("Unexpected exception occurred in transaction work function", workThrowable);
140135
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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.fromCompletionStage(CompletableFuture.completedFuture(testkitResponse));
170+
}
171+
172+
@Override
173+
public Mono<TestkitResponse> processReactive(TestkitState testkitState) {
174+
TestkitResponse testkitResponse = createResponse(REACTIVE_SKIP_PATTERN_TO_CHECK);
175+
return Mono.fromCompletionStage(CompletableFuture.completedFuture(testkitResponse));
176+
}
177+
178+
private TestkitResponse createResponse(Map<String, SkipDeciderInterface> skipPatternToCheck) {
179+
System.out.println(data.getTestName());
180+
return (TestkitResponse) skipPatternToCheck.entrySet().stream()
181+
.filter(entry -> data.getTestName().matches(entry.getKey()))
182+
.findFirst()
183+
.map(entry -> {
184+
SkipDecision decision = entry.getValue().check(data.getSubtestArguments());
185+
if (decision.isSkipped()) {
186+
return SkipTest.builder()
187+
.data(SkipTest.SkipTestBody.builder()
188+
.reason(decision.getReason())
189+
.build())
190+
.build();
191+
}
192+
return RunTest.builder().build();
193+
})
194+
.orElse(RunTest.builder().build());
195+
}
196+
197+
@Setter
198+
@Getter
199+
public static class StartSubTestBody {
200+
private String testName;
201+
private Map<String, Object> subtestArguments;
202+
}
203+
}

0 commit comments

Comments
 (0)