diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java index 9f445fc3d2..4ad0e758ee 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java @@ -40,7 +40,6 @@ import neo4j.org.testkit.backend.holder.TransactionHolder; import neo4j.org.testkit.backend.messages.requests.TestkitCallbackResult; import neo4j.org.testkit.backend.messages.responses.TestkitResponse; -import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.internal.cluster.RoutingTableRegistry; import reactor.core.publisher.Mono; @@ -69,7 +68,7 @@ public class TestkitState { private final Map transactionIdToReactiveTransactionHolder = new HashMap<>(); @Getter - private final Map errors = new HashMap<>(); + private final Map errors = new HashMap<>(); private final AtomicInteger idGenerator = new AtomicInteger(0); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java index bc816856cf..ffd6a73bb5 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java @@ -21,6 +21,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import java.time.zone.ZoneRulesException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; @@ -86,7 +87,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { } }); } catch (Throwable throwable) { - ctx.writeAndFlush(createErrorResponse(throwable)); + exceptionCaught(ctx, throwable); } }); } @@ -102,6 +103,11 @@ private static CompletionStage wrapSyncRequest( return result; } + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.writeAndFlush(createErrorResponse(cause)); + } + private TestkitResponse createErrorResponse(Throwable throwable) { if (throwable instanceof CompletionException) { throwable = throwable.getCause(); @@ -120,8 +126,10 @@ private TestkitResponse createErrorResponse(Throwable throwable) { .build(); } else if (isConnectionPoolClosedException(throwable) || throwable instanceof UntrustedServerException - || throwable instanceof NoSuchRecordException) { + || throwable instanceof NoSuchRecordException + || throwable instanceof ZoneRulesException) { String id = testkitState.newId(); + testkitState.getErrors().put(id, (Exception) throwable); return DriverError.builder() .data(DriverError.DriverErrorBody.builder() .id(id) diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestResponseMapperHandler.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestResponseMapperHandler.java index 9afc93e308..321feba4ce 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestResponseMapperHandler.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestResponseMapperHandler.java @@ -18,7 +18,6 @@ */ package neo4j.org.testkit.backend.channel.handler; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.ChannelDuplexHandler; @@ -32,14 +31,10 @@ public class TestkitRequestResponseMapperHandler extends ChannelDuplexHandler { private final ObjectMapper objectMapper = newObjectMapper(); @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String testkitMessage = (String) msg; TestkitRequest testkitRequest; - try { - testkitRequest = objectMapper.readValue(testkitMessage, TestkitRequest.class); - } catch (JsonProcessingException e) { - throw new RuntimeException("Failed to deserialize Testkit message", e); - } + testkitRequest = objectMapper.readValue(testkitMessage, TestkitRequest.class); ctx.fireChannelRead(testkitRequest); } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java index 399d17ec22..26b1880ba5 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java @@ -19,33 +19,60 @@ package neo4j.org.testkit.backend.messages; import com.fasterxml.jackson.databind.module.SimpleModule; -import java.time.ZonedDateTime; +import java.time.LocalDate; import java.util.List; +import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDateDeserializer; import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDateTimeDeserializer; +import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDurationDeserializer; +import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherTimeDeserializer; import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitListDeserializer; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherDateTime; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherTime; +import neo4j.org.testkit.backend.messages.responses.serializer.TestkitDateTimeValueSerializer; +import neo4j.org.testkit.backend.messages.responses.serializer.TestkitDateValueSerializer; +import neo4j.org.testkit.backend.messages.responses.serializer.TestkitDurationValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitListValueSerializer; +import neo4j.org.testkit.backend.messages.responses.serializer.TestkitLocalDateTimeValueSerializer; +import neo4j.org.testkit.backend.messages.responses.serializer.TestkitLocalTimeValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitMapValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitNodeValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitPathValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitRecordSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitRelationshipValueSerializer; +import neo4j.org.testkit.backend.messages.responses.serializer.TestkitTimeValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitValueSerializer; import org.neo4j.driver.Record; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.value.DateTimeValue; +import org.neo4j.driver.internal.value.DateValue; +import org.neo4j.driver.internal.value.DurationValue; import org.neo4j.driver.internal.value.ListValue; +import org.neo4j.driver.internal.value.LocalDateTimeValue; +import org.neo4j.driver.internal.value.LocalTimeValue; import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.internal.value.NodeValue; import org.neo4j.driver.internal.value.PathValue; import org.neo4j.driver.internal.value.RelationshipValue; +import org.neo4j.driver.internal.value.TimeValue; +import org.neo4j.driver.types.IsoDuration; public class TestkitModule extends SimpleModule { public TestkitModule() { this.addDeserializer(List.class, new TestkitListDeserializer()); - this.addDeserializer(ZonedDateTime.class, new TestkitCypherDateTimeDeserializer()); + this.addDeserializer(CypherDateTime.class, new TestkitCypherDateTimeDeserializer()); + this.addDeserializer(CypherTime.class, new TestkitCypherTimeDeserializer()); + this.addDeserializer(IsoDuration.class, new TestkitCypherDurationDeserializer()); + this.addDeserializer(LocalDate.class, new TestkitCypherDateDeserializer()); this.addSerializer(Value.class, new TestkitValueSerializer()); this.addSerializer(NodeValue.class, new TestkitNodeValueSerializer()); this.addSerializer(ListValue.class, new TestkitListValueSerializer()); + this.addSerializer(DateTimeValue.class, new TestkitDateTimeValueSerializer()); + this.addSerializer(DateValue.class, new TestkitDateValueSerializer()); + this.addSerializer(DurationValue.class, new TestkitDurationValueSerializer()); + this.addSerializer(LocalDateTimeValue.class, new TestkitLocalDateTimeValueSerializer()); + this.addSerializer(LocalTimeValue.class, new TestkitLocalTimeValueSerializer()); + this.addSerializer(TimeValue.class, new TestkitTimeValueSerializer()); this.addSerializer(Record.class, new TestkitRecordSerializer()); this.addSerializer(MapValue.class, new TestkitMapValueSerializer()); this.addSerializer(PathValue.class, new TestkitPathValueSerializer()); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionReadTransaction.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionReadTransaction.java index 9a5aaa3b0c..0203edf84a 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionReadTransaction.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionReadTransaction.java @@ -23,7 +23,6 @@ import java.util.concurrent.ExecutionException; import lombok.Getter; import lombok.Setter; -import neo4j.org.testkit.backend.FrontendError; import neo4j.org.testkit.backend.ReactiveTransactionContextAdapter; import neo4j.org.testkit.backend.TestkitState; import neo4j.org.testkit.backend.holder.AsyncTransactionHolder; @@ -38,7 +37,6 @@ import org.neo4j.driver.TransactionWork; import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.async.AsyncTransactionWork; -import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.reactive.ReactiveTransactionCallback; import org.neo4j.driver.reactive.RxTransactionWork; import org.reactivestreams.Publisher; @@ -129,11 +127,8 @@ private TransactionWork handle(TestkitState testkitState, SessionHolder se if (workThrowable instanceof ExecutionException) { workThrowable = workThrowable.getCause(); } - if (workThrowable instanceof Neo4jException) { - throw (Neo4jException) workThrowable; - } - if (workThrowable instanceof FrontendError) { - throw (FrontendError) workThrowable; + if (workThrowable instanceof RuntimeException) { + throw (RuntimeException) workThrowable; } throw new RuntimeException("Unexpected exception occurred in transaction work function", workThrowable); } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionWriteTransaction.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionWriteTransaction.java index 6a7f898c4a..50803689fc 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionWriteTransaction.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SessionWriteTransaction.java @@ -24,7 +24,6 @@ import java.util.concurrent.ExecutionException; import lombok.Getter; import lombok.Setter; -import neo4j.org.testkit.backend.FrontendError; import neo4j.org.testkit.backend.ReactiveTransactionContextAdapter; import neo4j.org.testkit.backend.TestkitState; import neo4j.org.testkit.backend.holder.AsyncTransactionHolder; @@ -39,7 +38,6 @@ import org.neo4j.driver.TransactionWork; import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.async.AsyncTransactionWork; -import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.reactive.ReactiveTransactionCallback; import org.neo4j.driver.reactive.RxTransactionWork; import org.reactivestreams.Publisher; @@ -130,11 +128,8 @@ private TransactionWork handle(TestkitState testkitState, SessionHolder se if (workThrowable instanceof ExecutionException) { workThrowable = workThrowable.getCause(); } - if (workThrowable instanceof Neo4jException) { - throw (Neo4jException) workThrowable; - } - if (workThrowable instanceof FrontendError) { - throw (FrontendError) workThrowable; + if (workThrowable instanceof RuntimeException) { + throw (RuntimeException) workThrowable; } throw new RuntimeException("Unexpected exception occurred in transaction work function", workThrowable); } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartSubTest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartSubTest.java new file mode 100644 index 0000000000..86544c2994 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartSubTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests; + +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import lombok.Getter; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.RunTest; +import neo4j.org.testkit.backend.messages.responses.SkipTest; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import reactor.core.publisher.Mono; + +@Setter +@Getter +public class StartSubTest implements TestkitRequest { + interface SkipDeciderInterface { + SkipDecision check(Map params); + } + + public static class SkipDecision { + private final boolean skipped; + private final String reason; + + public SkipDecision(boolean skipped, String reason) { + this.skipped = skipped; + this.reason = reason; + } + + public boolean isSkipped() { + return skipped; + } + + public String getReason() { + return reason; + } + + static SkipDecision ofNonSkipped() { + return new SkipDecision(false, null); + } + + static SkipDecision ofSkipped(String reason) { + return new SkipDecision(true, reason); + } + } + + private static final Map COMMON_SKIP_PATTERN_TO_CHECK = new HashMap<>(); + private static final Map ASYNC_SKIP_PATTERN_TO_CHECK = new HashMap<>(); + private static final Map REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK = new HashMap<>(); + private static final Map REACTIVE_SKIP_PATTERN_TO_CHECK = new HashMap<>(); + + private static SkipDecision checkTzIdSupported(Map params) { + String tzId = (String) params.get("tz_id"); + try { + ZoneId.of(tzId); + return SkipDecision.ofNonSkipped(); + } catch (DateTimeException e) { + return SkipDecision.ofSkipped("Timezone not supported: " + tzId); + } + } + + private static SkipDecision checkDateTimeSupported(Map params) { + @SuppressWarnings("unchecked") + HashMap dt_param = (HashMap) params.get("dt"); + if (dt_param == null) { + throw new RuntimeException("params expected to contain 'dt'"); + } + @SuppressWarnings("unchecked") + HashMap data = (HashMap) dt_param.get("data"); + if (data == null) { + throw new RuntimeException("param 'dt' expected to contain 'data'"); + } + Integer year = (Integer) data.get("year"); + Integer month = (Integer) data.get("month"); + Integer day = (Integer) data.get("day"); + Integer hour = (Integer) data.get("hour"); + Integer minute = (Integer) data.get("minute"); + Integer second = (Integer) data.get("second"); + Integer nano = (Integer) data.get("nanosecond"); + Integer utcOffset = (Integer) data.get("utc_offset_s"); + String tzId = (String) data.get("timezone_id"); + try { + ZonedDateTime dt = ZonedDateTime.of(year, month, day, hour, minute, second, nano, ZoneId.of(tzId)); + if (dt.getOffset().getTotalSeconds() != utcOffset) { + throw new DateTimeException(String.format( + "Unmatched UTC offset. TestKit expected %d, local zone db yielded %d", + utcOffset, dt.getOffset().getTotalSeconds())); + } + return SkipDecision.ofNonSkipped(); + } catch (DateTimeException e) { + return SkipDecision.ofSkipped("DateTime not supported: " + e.getMessage()); + } + } + + static { + COMMON_SKIP_PATTERN_TO_CHECK.put( + "neo4j\\.datatypes\\.test_temporal_types\\.TestDataTypes\\.test_should_echo_all_timezone_ids", + StartSubTest::checkDateTimeSupported); + COMMON_SKIP_PATTERN_TO_CHECK.put( + "neo4j\\.datatypes\\.test_temporal_types\\.TestDataTypes\\.test_date_time_cypher_created_tz_id", + StartSubTest::checkTzIdSupported); + + ASYNC_SKIP_PATTERN_TO_CHECK.putAll(COMMON_SKIP_PATTERN_TO_CHECK); + + REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK.putAll(COMMON_SKIP_PATTERN_TO_CHECK); + + REACTIVE_SKIP_PATTERN_TO_CHECK.putAll(COMMON_SKIP_PATTERN_TO_CHECK); + } + + private StartSubTestBody data; + + public static boolean decidePerSubTest(String testName) { + return skipPatternMatches(testName, COMMON_SKIP_PATTERN_TO_CHECK); + } + + public static boolean decidePerSubTestAsync(String testName) { + return skipPatternMatches(testName, ASYNC_SKIP_PATTERN_TO_CHECK); + } + + public static boolean decidePerSubTestReactiveLegacy(String testName) { + return skipPatternMatches(testName, REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK); + } + + public static boolean decidePerSubTestReactive(String testName) { + return skipPatternMatches(testName, REACTIVE_SKIP_PATTERN_TO_CHECK); + } + + private static boolean skipPatternMatches( + String testName, Map skipPatternToFunction) { + return skipPatternToFunction.entrySet().stream().anyMatch(entry -> testName.matches(entry.getKey())); + } + + @Override + public TestkitResponse process(TestkitState testkitState) { + return createResponse(COMMON_SKIP_PATTERN_TO_CHECK); + } + + @Override + public CompletionStage processAsync(TestkitState testkitState) { + TestkitResponse testkitResponse = createResponse(ASYNC_SKIP_PATTERN_TO_CHECK); + return CompletableFuture.completedFuture(testkitResponse); + } + + @Override + public Mono processRx(TestkitState testkitState) { + TestkitResponse testkitResponse = createResponse(REACTIVE_LEGACY_SKIP_PATTERN_TO_CHECK); + return Mono.just(testkitResponse); + } + + @Override + public Mono processReactive(TestkitState testkitState) { + TestkitResponse testkitResponse = createResponse(REACTIVE_SKIP_PATTERN_TO_CHECK); + return Mono.just(testkitResponse); + } + + private TestkitResponse createResponse(Map skipPatternToCheck) { + return skipPatternToCheck.entrySet().stream() + .filter(entry -> data.getTestName().matches(entry.getKey())) + .findFirst() + .map(entry -> { + SkipDecision decision = entry.getValue().check(data.getSubtestArguments()); + if (decision.isSkipped()) { + return SkipTest.builder() + .data(SkipTest.SkipTestBody.builder() + .reason(decision.getReason()) + .build()) + .build(); + } + return RunTest.builder().build(); + }) + .orElse(RunTest.builder().build()); + } + + @Setter + @Getter + public static class StartSubTestBody { + private String testName; + private Map subtestArguments; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java index 00cec49b60..794c14c566 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java @@ -20,11 +20,13 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import lombok.Getter; import lombok.Setter; import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.RunSubTests; import neo4j.org.testkit.backend.messages.responses.RunTest; import neo4j.org.testkit.backend.messages.responses.SkipTest; import neo4j.org.testkit.backend.messages.responses.TestkitResponse; @@ -81,9 +83,9 @@ public class StartTest implements TestkitRequest { "^.*\\.TestOptimizations\\.test_uses_implicit_default_arguments_multi_query$", skipMessage); COMMON_SKIP_PATTERN_TO_REASON.put( "^.*\\.TestOptimizations\\.test_uses_implicit_default_arguments_multi_query_nested$", skipMessage); - skipMessage = "Additional type support is needed"; COMMON_SKIP_PATTERN_TO_REASON.put( - "^neo4j\\.datatypes\\.test_temporal_types\\.TestDataTypes\\..*$", skipMessage); + "^.*\\.test_unknown_then_known_zoned_date_time(_patched)?$", + "Unknown zone names make the driver close the connection."); ASYNC_SKIP_PATTERN_TO_REASON.putAll(COMMON_SKIP_PATTERN_TO_REASON); @@ -150,38 +152,48 @@ public class StartTest implements TestkitRequest { @Override public TestkitResponse process(TestkitState testkitState) { - return createResponse(COMMON_SKIP_PATTERN_TO_REASON); + return createSkipResponse(COMMON_SKIP_PATTERN_TO_REASON) + .orElseGet(() -> StartSubTest.decidePerSubTestReactive(data.getTestName()) + ? RunSubTests.builder().build() + : RunTest.builder().build()); } @Override public CompletionStage processAsync(TestkitState testkitState) { - TestkitResponse testkitResponse = createResponse(ASYNC_SKIP_PATTERN_TO_REASON); + TestkitResponse testkitResponse = createSkipResponse(ASYNC_SKIP_PATTERN_TO_REASON) + .orElseGet(() -> StartSubTest.decidePerSubTestReactive(data.getTestName()) + ? RunSubTests.builder().build() + : RunTest.builder().build()); return CompletableFuture.completedFuture(testkitResponse); } @Override public Mono processRx(TestkitState testkitState) { - TestkitResponse testkitResponse = createResponse(REACTIVE_LEGACY_SKIP_PATTERN_TO_REASON); - return Mono.fromCompletionStage(CompletableFuture.completedFuture(testkitResponse)); + TestkitResponse testkitResponse = createSkipResponse(REACTIVE_LEGACY_SKIP_PATTERN_TO_REASON) + .orElseGet(() -> StartSubTest.decidePerSubTestReactive(data.getTestName()) + ? RunSubTests.builder().build() + : RunTest.builder().build()); + return Mono.just(testkitResponse); } @Override public Mono processReactive(TestkitState testkitState) { - TestkitResponse testkitResponse = createResponse(REACTIVE_SKIP_PATTERN_TO_REASON); - return Mono.fromCompletionStage(CompletableFuture.completedFuture(testkitResponse)); + TestkitResponse testkitResponse = createSkipResponse(REACTIVE_SKIP_PATTERN_TO_REASON) + .orElseGet(() -> StartSubTest.decidePerSubTestReactive(data.getTestName()) + ? RunSubTests.builder().build() + : RunTest.builder().build()); + return Mono.just(testkitResponse); } - private TestkitResponse createResponse(Map skipPatternToReason) { - System.out.println(data.getTestName()); + private Optional createSkipResponse(Map skipPatternToReason) { return skipPatternToReason.entrySet().stream() .filter(entry -> data.getTestName().matches(entry.getKey())) .findFirst() - .map(entry -> (TestkitResponse) SkipTest.builder() + .map(entry -> SkipTest.builder() .data(SkipTest.SkipTestBody.builder() .reason(entry.getValue()) .build()) - .build()) - .orElseGet(() -> RunTest.builder().build()); + .build()); } @Setter diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java index 37d45b89c6..b9009dbd5d 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java @@ -41,7 +41,8 @@ @JsonSubTypes.Type(GetRoutingTable.class), @JsonSubTypes.Type(TransactionClose.class), @JsonSubTypes.Type(ResultList.class), @JsonSubTypes.Type(GetConnectionPoolMetrics.class), @JsonSubTypes.Type(ResultPeek.class), @JsonSubTypes.Type(CheckDriverIsEncrypted.class), - @JsonSubTypes.Type(CypherTypeField.class), @JsonSubTypes.Type(ResultSingle.class) + @JsonSubTypes.Type(CypherTypeField.class), @JsonSubTypes.Type(ResultSingle.class), + @JsonSubTypes.Type(StartSubTest.class) }) public interface TestkitRequest { TestkitResponse process(TestkitState testkitState); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateDeserializer.java new file mode 100644 index 0000000000..7d4247f7ef --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateDeserializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.time.LocalDate; +import java.util.Date; + +public class TestkitCypherDateDeserializer extends StdDeserializer { + + private final TestkitCypherTypeMapper mapper; + + public TestkitCypherDateDeserializer() { + super(Date.class); + mapper = new TestkitCypherTypeMapper(); + } + + public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + CypherDateData data = mapper.mapData(p, ctxt, new CypherDateData()); + return LocalDate.of(data.year, data.month, data.day); + } + + private static final class CypherDateData { + Integer year; + Integer month; + Integer day; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java index b1d24d4b53..97e59e7210 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java @@ -18,67 +18,38 @@ */ package neo4j.org.testkit.backend.messages.requests.deserializer; -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; -import java.lang.reflect.Field; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherDateTime; + +public class TestkitCypherDateTimeDeserializer extends StdDeserializer { + private final TestkitCypherTypeMapper mapper; -public class TestkitCypherDateTimeDeserializer extends StdDeserializer { public TestkitCypherDateTimeDeserializer() { - super(ZonedDateTime.class); + super(CypherDateTime.class); + mapper = new TestkitCypherTypeMapper(); } @Override - public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { - - CypherDateTime dateTime = new CypherDateTime(); - - JsonToken token = p.currentToken(); - while (token == JsonToken.FIELD_NAME - || token == JsonToken.VALUE_NUMBER_INT - || token == JsonToken.VALUE_STRING) { - if (token == JsonToken.VALUE_NUMBER_INT) { - String field = p.getCurrentName(); - int value = p.getValueAsInt(); - setField(dateTime, field, value); - } else if (token == JsonToken.VALUE_STRING) { - String field = p.getCurrentName(); - String value = p.getValueAsString(); - setField(dateTime, field, value); - } - token = p.nextToken(); - } - - ZoneId zoneId = dateTime.timezone_id != null - ? ZoneId.of(dateTime.timezone_id) - : ZoneOffset.ofTotalSeconds(dateTime.utc_offset_s); - return ZonedDateTime.of( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - dateTime.nanosecond, - zoneId); - } - - private void setField(CypherDateTime dateTime, String fieldName, Object value) { - try { - Field field = CypherDateTime.class.getDeclaredField(fieldName); - field.set(dateTime, value); - } catch (NoSuchFieldException | IllegalAccessException e) { - // ignored - } + public CypherDateTime deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + CypherDateTimeData data = mapper.mapData(p, ctxt, new CypherDateTimeData()); + return new CypherDateTime( + data.year, + data.month, + data.day, + data.hour, + data.minute, + data.second, + data.nanosecond, + data.timezone_id, + data.utc_offset_s); } - private static class CypherDateTime { + private static final class CypherDateTimeData { Integer year; Integer month; Integer day; diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDurationDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDurationDeserializer.java new file mode 100644 index 0000000000..b1d7fb49bf --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDurationDeserializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import org.neo4j.driver.internal.InternalIsoDuration; +import org.neo4j.driver.types.IsoDuration; + +public class TestkitCypherDurationDeserializer extends StdDeserializer { + private final TestkitCypherTypeMapper mapper; + + public TestkitCypherDurationDeserializer() { + super(IsoDuration.class); + mapper = new TestkitCypherTypeMapper(); + } + + public IsoDuration deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + CypherDurationData data = mapper.mapData(p, ctxt, new CypherDurationData()); + return new InternalIsoDuration(data.months, data.days, data.seconds, data.nanoseconds); + } + + private static final class CypherDurationData { + Long months; + Long days; + Long seconds; + Integer nanoseconds; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherParamDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherParamDeserializer.java index 929c67ba30..b5740d0db0 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherParamDeserializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherParamDeserializer.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherType; public class TestkitCypherParamDeserializer extends StdDeserializer> { public TestkitCypherParamDeserializer() { @@ -77,7 +78,11 @@ public Map deserialize(JsonParser p, DeserializationContext ctxt { result.put(key, deserialize(p, ctxt)); } else { - result.put(key, p.readValueAs(mapValueType)); + Object obj = p.readValueAs(mapValueType); + if (obj instanceof CypherType) { + obj = ((CypherType) obj).asValue(); + } + result.put(key, obj); } } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherTimeDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherTimeDeserializer.java new file mode 100644 index 0000000000..1717568b5c --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherTimeDeserializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherTime; + +public class TestkitCypherTimeDeserializer extends StdDeserializer { + private final TestkitCypherTypeMapper mapper; + + public TestkitCypherTimeDeserializer() { + super(CypherTime.class); + mapper = new TestkitCypherTypeMapper(); + } + + @Override + public CypherTime deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + CypherTimeData data = mapper.mapData(p, ctxt, new CypherTimeData()); + return new CypherTime(data.hour, data.minute, data.second, data.nanosecond, data.utc_offset_s); + } + + private static final class CypherTimeData { + Integer hour; + Integer minute; + Integer second; + Integer nanosecond; + Integer utc_offset_s; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherTypeMapper.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherTypeMapper.java new file mode 100644 index 0000000000..ee97479391 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherTypeMapper.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import java.io.IOException; +import java.lang.reflect.Field; + +public class TestkitCypherTypeMapper { + public TestkitCypherTypeMapper() {} + + public T mapData(JsonParser p, DeserializationContext ctxt, T data) throws IOException, JacksonException { + JsonToken token = p.currentToken(); + while (token == JsonToken.FIELD_NAME + || token == JsonToken.VALUE_NUMBER_INT + || token == JsonToken.VALUE_STRING) { + if (token == JsonToken.VALUE_NUMBER_INT) { + String field = p.getCurrentName(); + if (fieldIsType(data, field, Long.class)) { + setField(data, field, p.getLongValue()); + } else if (fieldIsType(data, field, Integer.class)) { + setField(data, field, p.getIntValue()); + } else { + throw new RuntimeException("Unhandled field type: " + field); + } + } else if (token == JsonToken.VALUE_STRING) { + String field = p.getCurrentName(); + String value = p.getValueAsString(); + setField(data, field, value); + } + token = p.nextToken(); + } + return data; + } + + private boolean fieldIsType(Object data, String field, Class type) { + try { + Field f = data.getClass().getDeclaredField(field); + return f.getType().equals(type); + } catch (NoSuchFieldException e) { + return false; + } + } + + private void setField(Object data, String fieldName, Object value) { + try { + Field field = data.getClass().getDeclaredField(fieldName); + field.set(data, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(String.format( + "Received unexpected TestKit data field %s while parsing into %s", + fieldName, data.getClass().getName())); + } + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitListDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitListDeserializer.java index 034fd24116..e5dacda40b 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitListDeserializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitListDeserializer.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherType; public class TestkitListDeserializer extends StdDeserializer> { private final TestkitCypherParamDeserializer mapDeserializer; @@ -80,7 +81,11 @@ public List deserialize(JsonParser p, DeserializationContext ctxt) throws IOE { result.add(mapDeserializer.deserialize(p, ctxt)); } else { - result.add(p.readValueAs(mapValueType)); + Object obj = p.readValueAs(mapValueType); + if (obj instanceof CypherType) { + obj = ((CypherType) obj).asValue(); + } + result.add(obj); } } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherDateTime.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherDateTime.java new file mode 100644 index 0000000000..6706ad79ed --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherDateTime.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer.types; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.value.DateTimeValue; +import org.neo4j.driver.internal.value.LocalDateTimeValue; + +public class CypherDateTime implements CypherType { + private final int year, month, day, hour, minute, second, nano; + private final String zoneId; + private final Integer offset; + + public CypherDateTime( + int year, + int month, + int day, + int hour, + int minute, + int second, + int nanosecond, + String zoneId, + Integer offset) { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + this.nano = nanosecond; + this.zoneId = zoneId; + this.offset = offset; + } + + @Override + public Value asValue() { + if (zoneId != null) { + ZonedDateTime dateTime = ZonedDateTime.of(year, month, day, hour, minute, second, nano, ZoneId.of(zoneId)); + if (dateTime.getOffset().getTotalSeconds() != offset) { + throw new RuntimeException(String.format( + "TestKit's and driver's tz info diverge. " + + "TestKit assumes %ds offset for %s while the driver assumes %ds for %s.", + offset, zoneId, dateTime.getOffset().getTotalSeconds(), dateTime)); + } + return new DateTimeValue(dateTime); + } + + if (offset != null) { + ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(offset); + return new DateTimeValue(ZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneOffset)); + } + return new LocalDateTimeValue(LocalDateTime.of(year, month, day, hour, minute, second, nano)); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherTime.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherTime.java new file mode 100644 index 0000000000..34f6156d76 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherTime.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer.types; + +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.value.LocalTimeValue; +import org.neo4j.driver.internal.value.TimeValue; + +public class CypherTime implements CypherType { + private final int hour, minute, second, nano; + private final Integer offset; + + public CypherTime(int hour, int minute, int second, int nanosecond, Integer offset) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.nano = nanosecond; + this.offset = offset; + } + + @Override + public Value asValue() { + if (offset != null) { + return new TimeValue(OffsetTime.of(hour, minute, second, nano, ZoneOffset.ofTotalSeconds(offset))); + } + return new LocalTimeValue(LocalTime.of(hour, minute, second, nano)); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherType.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherType.java new file mode 100644 index 0000000000..208de86b7e --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/types/CypherType.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer.types; + +import org.neo4j.driver.Value; + +public interface CypherType { + Value asValue(); +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/RunSubTests.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/RunSubTests.java new file mode 100644 index 0000000000..70dc30db02 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/RunSubTests.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; + +@Builder +public class RunSubTests implements TestkitResponse { + @Override + public String testkitName() { + return "RunSubTests"; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java index c783c3efa0..dd0c7d1515 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java @@ -20,11 +20,14 @@ import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; -import java.time.ZonedDateTime; +import java.time.LocalDate; import java.util.List; import java.util.Map; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherDateTime; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherTime; +import org.neo4j.driver.types.IsoDuration; @AllArgsConstructor(access = AccessLevel.PRIVATE) public final class GenUtils { @@ -55,12 +58,32 @@ public static void cypherObject(JsonGenerator gen, String name, RunnableWith }); } + public static void writeDate(JsonGenerator gen, int year, int month, int day) throws IOException { + gen.writeFieldName("year"); + gen.writeNumber(year); + gen.writeFieldName("month"); + gen.writeNumber(month); + gen.writeFieldName("day"); + gen.writeNumber(day); + } + + public static void writeTime(JsonGenerator gen, int hour, int minute, int second, int nano) throws IOException { + gen.writeFieldName("hour"); + gen.writeNumber(hour); + gen.writeFieldName("minute"); + gen.writeNumber(minute); + gen.writeFieldName("second"); + gen.writeNumber(second); + gen.writeFieldName("nanosecond"); + gen.writeNumber(nano); + } + public static Class cypherTypeToJavaType(String typeString) { switch (typeString) { case "CypherBool": return Boolean.class; case "CypherInt": - return Integer.class; + return Long.class; case "CypherFloat": return Double.class; case "CypherString": @@ -70,7 +93,13 @@ public static Class cypherTypeToJavaType(String typeString) { case "CypherMap": return Map.class; case "CypherDateTime": - return ZonedDateTime.class; + return CypherDateTime.class; + case "CypherTime": + return CypherTime.class; + case "CypherDate": + return LocalDate.class; + case "CypherDuration": + return IsoDuration.class; case "CypherNull": return null; default: diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDateTimeValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDateTimeValueSerializer.java new file mode 100644 index 0000000000..c40fef3fd5 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDateTimeValueSerializer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import org.neo4j.driver.internal.value.DateTimeValue; + +public class TestkitDateTimeValueSerializer extends StdSerializer { + public TestkitDateTimeValueSerializer() { + super(DateTimeValue.class); + } + + @Override + public void serialize(DateTimeValue timeValue, JsonGenerator gen, SerializerProvider provider) throws IOException { + cypherObject(gen, "CypherDateTime", () -> { + ZonedDateTime dateTime = timeValue.asZonedDateTime(); + GenUtils.writeDate(gen, dateTime.getYear(), dateTime.getMonthValue(), dateTime.getDayOfMonth()); + GenUtils.writeTime(gen, dateTime.getHour(), dateTime.getMinute(), dateTime.getSecond(), dateTime.getNano()); + ZoneOffset offset = dateTime.getOffset(); + gen.writeFieldName("utc_offset_s"); + gen.writeNumber(offset.getTotalSeconds()); + ZoneId zoneId = dateTime.getZone(); + if (zoneId != offset) { + // not fixed offset + gen.writeFieldName("timezone_id"); + gen.writeString(zoneId.toString()); + } + }); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDateValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDateValueSerializer.java new file mode 100644 index 0000000000..32c9f297fc --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDateValueSerializer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.time.LocalDate; +import org.neo4j.driver.internal.value.DateValue; + +public class TestkitDateValueSerializer extends StdSerializer { + public TestkitDateValueSerializer() { + super(DateValue.class); + } + + @Override + public void serialize(DateValue dateValue, JsonGenerator gen, SerializerProvider provider) throws IOException { + cypherObject(gen, "CypherDate", () -> { + LocalDate date = dateValue.asLocalDate(); + GenUtils.writeDate(gen, date.getYear(), date.getMonthValue(), date.getDayOfMonth()); + }); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDurationValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDurationValueSerializer.java new file mode 100644 index 0000000000..23df07f2a4 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitDurationValueSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import org.neo4j.driver.internal.value.DurationValue; +import org.neo4j.driver.types.IsoDuration; + +public class TestkitDurationValueSerializer extends StdSerializer { + public TestkitDurationValueSerializer() { + super(DurationValue.class); + } + + @Override + public void serialize(DurationValue durationValue, JsonGenerator gen, SerializerProvider provider) + throws IOException { + cypherObject(gen, "CypherDuration", () -> { + IsoDuration duration = durationValue.asIsoDuration(); + gen.writeFieldName("months"); + gen.writeNumber(duration.months()); + gen.writeFieldName("days"); + gen.writeNumber(duration.days()); + gen.writeFieldName("seconds"); + gen.writeNumber(duration.seconds()); + gen.writeFieldName("nanoseconds"); + gen.writeNumber(duration.nanoseconds()); + }); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitLocalDateTimeValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitLocalDateTimeValueSerializer.java new file mode 100644 index 0000000000..edf52f8292 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitLocalDateTimeValueSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.time.LocalDateTime; +import org.neo4j.driver.internal.value.LocalDateTimeValue; + +public class TestkitLocalDateTimeValueSerializer extends StdSerializer { + public TestkitLocalDateTimeValueSerializer() { + super(LocalDateTimeValue.class); + } + + @Override + public void serialize(LocalDateTimeValue timeValue, JsonGenerator gen, SerializerProvider provider) + throws IOException { + cypherObject(gen, "CypherDateTime", () -> { + LocalDateTime dateTime = timeValue.asLocalDateTime(); + GenUtils.writeDate(gen, dateTime.getYear(), dateTime.getMonthValue(), dateTime.getDayOfMonth()); + GenUtils.writeTime(gen, dateTime.getHour(), dateTime.getMinute(), dateTime.getSecond(), dateTime.getNano()); + }); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitLocalTimeValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitLocalTimeValueSerializer.java new file mode 100644 index 0000000000..84a6e9f4a7 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitLocalTimeValueSerializer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.time.LocalTime; +import org.neo4j.driver.internal.value.LocalTimeValue; + +public class TestkitLocalTimeValueSerializer extends StdSerializer { + public TestkitLocalTimeValueSerializer() { + super(LocalTimeValue.class); + } + + @Override + public void serialize(LocalTimeValue timeValue, JsonGenerator gen, SerializerProvider provider) throws IOException { + cypherObject(gen, "CypherTime", () -> { + LocalTime time = timeValue.asLocalTime(); + GenUtils.writeTime(gen, time.getHour(), time.getMinute(), time.getSecond(), time.getNano()); + }); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitTimeValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitTimeValueSerializer.java new file mode 100644 index 0000000000..46fe0d21c0 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitTimeValueSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.time.OffsetTime; +import org.neo4j.driver.internal.value.TimeValue; + +public class TestkitTimeValueSerializer extends StdSerializer { + public TestkitTimeValueSerializer() { + super(TimeValue.class); + } + + @Override + public void serialize(TimeValue timeValue, JsonGenerator gen, SerializerProvider provider) throws IOException { + cypherObject(gen, "CypherTime", () -> { + OffsetTime time = timeValue.asOffsetTime(); + GenUtils.writeTime(gen, time.getHour(), time.getMinute(), time.getSecond(), time.getNano()); + gen.writeFieldName("utc_offset_s"); + gen.writeNumber(time.getOffset().getTotalSeconds()); + }); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java index a3ada4d893..91d8633d39 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java @@ -39,7 +39,7 @@ public void serialize(Value value, JsonGenerator gen, SerializerProvider provide } else if (InternalTypeSystem.TYPE_SYSTEM.NULL().isTypeOf(value)) { cypherObject(gen, "CypherNull", () -> gen.writeNullField("value")); } else if (InternalTypeSystem.TYPE_SYSTEM.INTEGER().isTypeOf(value)) { - cypherObject(gen, "CypherInt", value.asInt()); + cypherObject(gen, "CypherInt", value.asLong()); } else if (InternalTypeSystem.TYPE_SYSTEM.FLOAT().isTypeOf(value)) { cypherObject(gen, "CypherFloat", value.asDouble()); } else if (InternalTypeSystem.TYPE_SYSTEM.STRING().isTypeOf(value)) {