diff --git a/build.gradle b/build.gradle index 87d816e93..7da3e394f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id "io.github.gradle-nexus.publish-plugin" version "1.3.0" id "org.owasp.dependencycheck" version "9.0.9" + id "me.champeau.jmh" version "0.7.2" apply false id 'signing' } @@ -30,6 +31,7 @@ subprojects { apply plugin: 'maven-publish' apply plugin: 'signing' apply plugin: 'org.owasp.dependencycheck' + apply plugin: 'me.champeau.jmh' repositories { mavenCentral() @@ -57,6 +59,9 @@ subprojects { dependencies { implementation 'io.prometheus:simpleclient_dropwizard:0.16.0' + + jmh 'org.openjdk.jmh:jmh-core:1.37' + jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37' } jar { @@ -79,6 +84,14 @@ subprojects { withSourcesJar() } + jmh { + jmhTimeout = "1m" + iterations = 3 + fork = 2 + warmupIterations = 3 + warmupForks = 2 + } + // conditionals for publications tasks.withType(PublishToMavenRepository).configureEach { onlyIf { diff --git a/cradle-cassandra/src/jmh/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtilsBenchmark.java b/cradle-cassandra/src/jmh/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtilsBenchmark.java new file mode 100644 index 000000000..d2cfdc589 --- /dev/null +++ b/cradle-cassandra/src/jmh/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtilsBenchmark.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.cassandra.dao.testevents; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.Direction; +import com.exactpro.cradle.PageId; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventBatchToStore; +import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; +import com.exactpro.cradle.testevents.TestEventSingleToStore; +import com.exactpro.cradle.testevents.TestEventSingleToStoreBuilder; +import com.exactpro.cradle.utils.CompressException; +import com.exactpro.cradle.utils.CompressionType; +import com.exactpro.cradle.utils.CradleStorageException; +import org.apache.commons.lang3.RandomStringUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.io.IOException; +import java.time.Instant; +import java.util.UUID; + +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; +import static com.exactpro.cradle.CradleStorage.DEFAULT_MAX_TEST_EVENT_BATCH_SIZE; +import static com.exactpro.cradle.cassandra.CassandraStorageSettings.DEFAULT_MAX_UNCOMPRESSED_MESSAGE_BATCH_SIZE; +import static org.openjdk.jmh.annotations.Mode.Throughput; + +@State(Scope.Benchmark) +public class TestEventEntityUtilsBenchmark { + private static final BookId BOOK_ID = new BookId("benchmark-book"); + private static final PageId PAGE_ID = new PageId(BOOK_ID, Instant.now(), "benchmark-page"); + private static final String SCOPE = "benchmark-scope"; + private static final String SESSION_ALIAS_PREFIX = "benchmark-alias-"; + private static final String EVENT_NAME_PREFIX = "benchmark-event-"; + private static final int CONTENT_SIZE = 500; + private static final int EVENT_NUMBER = 100; + private static final int SESSION_ALIAS_NUMBER = 5; + private static final int MESSAGES_PER_DIRECTION = 2; + @State(Scope.Thread) + public static class EventBatchState { + private TestEventBatchToStore batch; + @Setup + public void init() throws CradleStorageException { + StoredTestEventId parentId = new StoredTestEventId(BOOK_ID, SCOPE, Instant.now(), UUID.randomUUID().toString()); + TestEventBatchToStoreBuilder batchBuilder = TestEventBatchToStore.builder(DEFAULT_MAX_TEST_EVENT_BATCH_SIZE, DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, Instant.now(), UUID.randomUUID().toString()) + .parentId(parentId); + + int seqCounter = 0; + for (int eventIndex = 0; eventIndex < EVENT_NUMBER; eventIndex++) { + TestEventSingleToStoreBuilder eventBuilder = TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, Instant.now(), UUID.randomUUID().toString()) + .parentId(parentId) + .name(EVENT_NAME_PREFIX + eventIndex) + .content(RandomStringUtils.random(CONTENT_SIZE, true, true).getBytes()); + + for (int aliasIndex = 0; aliasIndex < SESSION_ALIAS_NUMBER; aliasIndex++) { + for (Direction direction : Direction.values()) { + for (int msgIndex = 0; msgIndex < MESSAGES_PER_DIRECTION; msgIndex++) { + eventBuilder.message(new StoredMessageId(BOOK_ID, SESSION_ALIAS_PREFIX + aliasIndex, direction, Instant.now(), ++seqCounter)); + } + } + } + batchBuilder.addTestEvent(eventBuilder.build()); + } + batch = batchBuilder.build(); + } + } + + @Benchmark + @BenchmarkMode({Throughput}) + public void benchmarkSerializeBatchLinkedMessageIds(EventBatchState state) throws IOException, CompressException { + TestEventEntityUtils.toSerializedEntity(state.batch, PAGE_ID, CompressionType.LZ4, DEFAULT_MAX_UNCOMPRESSED_MESSAGE_BATCH_SIZE); + } +} diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraCradleStorage.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraCradleStorage.java index 02090bd47..b11b3fd21 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraCradleStorage.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraCradleStorage.java @@ -415,7 +415,7 @@ protected void doStoreTestEvent(TestEventToStore event, PageInfo page) throws IO PageId pageId = page.getId(); try { - eventsWorker.storeEvent(event, pageId); + eventsWorker.storeEvent(event, pageId).get(); eventsWorker.storeScope(event).get(); eventsWorker.storePageScope(event, pageId).get(); } diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraStorageSettings.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraStorageSettings.java index c7df0f4c0..e68a6785e 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraStorageSettings.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/CassandraStorageSettings.java @@ -54,7 +54,7 @@ public class CassandraStorageSettings extends CoreStorageSettings { public static final int DEFAULT_COUNTER_PERSISTENCE_INTERVAL_MS = 1000; public static final long DEFAULT_EVENT_BATCH_DURATION_MILLIS = 5_000; public static final long DEFAULT_TIMEOUT = 5000; - public static final CompressionType DEFAULT_COMPRESSION_TYPE = CompressionType.ZLIB; + public static final CompressionType DEFAULT_COMPRESSION_TYPE = CompressionType.LZ4; //we need to use Instant.EPOCH instead of Instant.MIN. //when cassandra driver tries to convert Instant.MIN to milliseconds using toEpochMilli() it causes long overflow. diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtils.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtils.java index 295b936e1..2570d480f 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtils.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.zip.DataFormatException; @@ -56,7 +57,9 @@ public static StoredTestEvent toStoredTestEvent(TestEventEntity testEventEntity, logger.trace("Creating test event '{}' from entity", eventId); byte[] content = restoreContent(testEventEntity, eventId); - return testEventEntity.isEventBatch() ? toStoredTestEventBatch(testEventEntity, pageId, eventId, content) : toStoredTestEventSingle(testEventEntity, pageId, eventId, content); + return testEventEntity.isEventBatch() + ? toStoredTestEventBatch(testEventEntity, pageId, eventId, content) + : toStoredTestEventSingle(testEventEntity, pageId, eventId, content); } @@ -87,21 +90,21 @@ private static byte[] restoreContent(TestEventEntity testEventEntity, StoredTest private static Set restoreMessages(TestEventEntity testEventEntity, BookId bookId) throws IOException { ByteBuffer messages = testEventEntity.getMessages(); - if (messages == null) - return null; + if (messages == null) { + return Collections.emptySet(); + } - byte[] result = messages.array(); - return TestEventUtils.deserializeLinkedMessageIds(result, bookId); + return TestEventUtils.deserializeLinkedMessageIds(messages, bookId); } - private static Map> restoreBatchMessages(TestEventEntity testEventEntity, BookId bookId) + private static Map> restoreBatchMessages(TestEventEntity testEventEntity, BookId bookId, String scope) throws IOException { ByteBuffer messages = testEventEntity.getMessages(); - if (messages == null) - return null; + if (messages == null) { + return Collections.emptyMap(); + } - byte[] result = messages.array(); - return TestEventUtils.deserializeBatchLinkedMessageIds(result, bookId); + return TestEventUtils.deserializeBatchLinkedMessageIds(messages, bookId, scope); } @@ -117,7 +120,7 @@ private static StoredTestEventBatch toStoredTestEventBatch(TestEventEntity testE throws IOException, CradleStorageException, CradleIdException { Collection children = TestEventUtils.deserializeTestEvents(content, eventId); - Map> messages = restoreBatchMessages(testEventEntity, pageId.getBookId()); + Map> messages = restoreBatchMessages(testEventEntity, pageId.getBookId(), eventId.getScope()); return new StoredTestEventBatch(eventId, testEventEntity.getName(), testEventEntity.getType(), createParentId(testEventEntity), children, messages, pageId, null, testEventEntity.getRecDate()); } @@ -133,7 +136,7 @@ public static Instant getStartTimestamp(TestEventEntity entity) { public static SerializedEntity toSerializedEntity(TestEventToStore event, PageId pageId, CompressionType compressionType, - int maxUncompressedSize) throws IOException, CompressException { + int maxUncompressedSize) throws CompressException { TestEventEntity.TestEventEntityBuilder builder = TestEventEntity.builder(); logger.debug("Creating entity from test event '{}'", event.getId()); @@ -155,7 +158,7 @@ public static SerializedEntity toSeri builder.setContentSize(content.length); } - byte[] messages = TestEventUtils.serializeLinkedMessageIds(event); + ByteBuffer messages = TestEventUtils.serializeLinkedMessageIds(event); StoredTestEventId parentId = event.getParentId(); LocalDateTime start = TimeUtils.toLocalTimestamp(event.getStartTimestamp()); @@ -172,12 +175,14 @@ public static SerializedEntity toSeri builder.setName(event.getName()); builder.setType(event.getType()); builder.setParentId(parentId != null ? parentId.toString() : ""); //Empty string for absent parentId allows using index to get root events - if (event.isBatch()) - builder.setEventCount(event.asBatch().getTestEventsCount()); + if (event.isBatch()) { + builder.setEventCount(event.asBatch().getTestEvents().size()); + } builder.setEndTimestamp(event.getEndTimestamp()); - if (messages != null) - builder.setMessages(ByteBuffer.wrap(messages)); + if (messages != null) { + builder.setMessages(messages); + } builder.setCompressed(compressed); //TODO: this.setLabels(event.getLabels()); diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/workers/EventsWorker.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/workers/EventsWorker.java index 873ad0dba..ab96be648 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/workers/EventsWorker.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/workers/EventsWorker.java @@ -38,6 +38,7 @@ import com.exactpro.cradle.serialization.SerializedEntityMetadata; import com.exactpro.cradle.testevents.StoredTestEvent; import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.StoredTestEventIdUtils; import com.exactpro.cradle.testevents.TestEventFilter; import com.exactpro.cradle.testevents.TestEventToStore; import com.exactpro.cradle.utils.CompressException; @@ -59,6 +60,7 @@ import java.util.concurrent.CompletionException; import java.util.zip.DataFormatException; +import static com.exactpro.cradle.testevents.StoredTestEventIdUtils.track; import static java.util.Objects.requireNonNull; public class EventsWorker extends Worker { @@ -144,40 +146,48 @@ public CompletableFuture storeEvent(TestEventToStore event, PageId pageId) TestEventOperator op = getOperators().getTestEventOperator(); BookStatisticsRecordsCaches.EntityKey key = new BookStatisticsRecordsCaches.EntityKey(pageId.getName(), EntityType.EVENT); + track(event, "wait serialize"); return CompletableFuture.supplyAsync(() -> { - try { - return TestEventEntityUtils.toSerializedEntity(event, pageId, settings.getCompressionType(), settings.getMaxUncompressedMessageBatchSize()); + try (AutoCloseable ignored = StoredTestEventIdUtils.Statistic.measure("serialize")) { + track(event, "serializing"); + var serializedEntity = TestEventEntityUtils.toSerializedEntity(event, pageId, settings.getCompressionType(), settings.getMaxUncompressedMessageBatchSize()); + track(event, "serialized"); + return serializedEntity; } catch (Exception e) { throw new CompletionException(e); } }, composingService).thenCompose(serializedEntity -> { + AutoCloseable measure = StoredTestEventIdUtils.Statistic.measure("write"); TestEventEntity entity = serializedEntity.getEntity(); List meta = serializedEntity.getSerializedEntityData().getSerializedEntityMetadata(); return op.write(entity, writeAttrs) + .thenRun(() -> { try { measure.close(); } catch (Exception e) { throw new RuntimeException(e); }}) .thenAcceptAsync(result -> { - try { - Instant firstTimestamp = meta.get(0).getTimestamp(); - Instant lastStartTimestamp = firstTimestamp; - for (SerializedEntityMetadata el : meta) { - if (el.getTimestamp() != null) { - if (firstTimestamp.isAfter(el.getTimestamp())) { - firstTimestamp = el.getTimestamp(); - } - if (lastStartTimestamp.isBefore(el.getTimestamp())) { - lastStartTimestamp = el.getTimestamp(); + try(AutoCloseable ignored = StoredTestEventIdUtils.Statistic.measure("update-statistics")) { + try { + Instant firstTimestamp = meta.get(0).getTimestamp(); + Instant lastStartTimestamp = firstTimestamp; + for (SerializedEntityMetadata el : meta) { + if (el.getTimestamp() != null) { + if (firstTimestamp.isAfter(el.getTimestamp())) { + firstTimestamp = el.getTimestamp(); + } + if (lastStartTimestamp.isBefore(el.getTimestamp())) { + lastStartTimestamp = el.getTimestamp(); + } } } + durationWorker.updateMaxDuration(pageId, entity.getScope(), + Duration.between(firstTimestamp, lastStartTimestamp).toMillis(), + writeAttrs); + } catch (CradleStorageException e) { + logger.error("Exception while updating max duration {}", e.getMessage()); } - durationWorker.updateMaxDuration(pageId, entity.getScope(), - Duration.between(firstTimestamp, lastStartTimestamp).toMillis(), - writeAttrs); - } catch (CradleStorageException e) { - logger.error("Exception while updating max duration {}", e.getMessage()); - } - entityStatisticsCollector.updateEntityBatchStatistics(pageId.getBookId(), key, meta); - updateEventWriteMetrics(entity, pageId.getBookId()); + entityStatisticsCollector.updateEntityBatchStatistics(pageId.getBookId(), key, meta); + updateEventWriteMetrics(entity, pageId.getBookId()); + } catch (Exception e) { throw new RuntimeException(e); } }, composingService); }); } diff --git a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityTest.java b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityTest.java index fd0ae408f..ad77b1193 100644 --- a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityTest.java +++ b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/testevents/TestEventEntityTest.java @@ -33,7 +33,7 @@ import com.exactpro.cradle.utils.CompressionType; import com.exactpro.cradle.utils.CradleIdException; import com.exactpro.cradle.utils.CradleStorageException; -import org.assertj.core.api.Assertions; +import com.exactpro.cradle.utils.TestEventUtils; import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -44,7 +44,10 @@ import java.util.Set; import java.util.zip.DataFormatException; +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; +import static com.exactpro.cradle.CradleStorage.DEFAULT_MAX_TEST_EVENT_BATCH_SIZE; import static com.exactpro.cradle.cassandra.TestUtils.createContent; +import static org.assertj.core.api.Assertions.assertThat; public class TestEventEntityTest { private final BookId book = new BookId("Book1"); @@ -65,8 +68,8 @@ public Object[][] events() throws CradleStorageException { TestEventBatchToStore batch = TestEventBatchToStore.builder(1024, storeActionRejectionThreshold) .id(new StoredTestEventId(book, scope, startTimestamp, "BatchId")) .parentId(parentId) + .addTestEvent(prepareSingle().content(createContent(contentLength)).build()) .build(); - batch.addTestEvent(prepareSingle().content(createContent(contentLength)).build()); return new Object[][] { {prepareSingle().content(createContent(contentLength)).build()}, @@ -101,8 +104,20 @@ public void eventEntity(TestEventToStore event) throws CradleStorageException, I RecursiveComparisonConfiguration config = new RecursiveComparisonConfiguration(); config.ignoreFieldsMatchingRegexes("pageId", ".*\\.pageId", "error", ".*\\.error", "recDate", ".*\\.recDate", "lastStartTimestamp", ".*\\.lastStartTimestamp"); config.ignoreAllOverriddenEquals(); - Assertions.assertThat(newEvent) + assertThat(toTestEventToStore(newEvent)) .usingRecursiveComparison(config) .isEqualTo(event); } + + private static TestEventToStore toTestEventToStore(StoredTestEvent storedEvent) throws CradleStorageException { + if (storedEvent.isBatch()) { + return TestEventUtils.toTestEventBatchToStore(storedEvent.asBatch(), + DEFAULT_MAX_TEST_EVENT_BATCH_SIZE, + DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS); + } else if (storedEvent.isSingle()) { + return TestEventUtils.toTestEventSingleToStore(storedEvent.asSingle(), + DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS); + } + throw new IllegalArgumentException("Unsupportable stored test event kind"); + } } \ No newline at end of file diff --git a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/BaseCradleCassandraTest.java b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/BaseCradleCassandraTest.java index b7946315c..9f776440e 100644 --- a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/BaseCradleCassandraTest.java +++ b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/BaseCradleCassandraTest.java @@ -144,14 +144,12 @@ protected MessageToStore generateMessage(String sessionAlias, Direction directio protected TestEventToStore generateTestEvent (String scope, Instant start, long batchDuration, long eventDuration) throws CradleStorageException { StoredTestEventId parentId = new StoredTestEventId(bookId, scope, start, UUID.randomUUID().toString()); StoredTestEventId id = new StoredTestEventId(bookId, scope, start, UUID.randomUUID().toString()); - TestEventBatchToStore batch = new TestEventBatchToStoreBuilder(100*1024, storeActionRejectionThreshold) - .name(EVENT_NAME) + TestEventBatchToStoreBuilder builder = new TestEventBatchToStoreBuilder(100 * 1024, storeActionRejectionThreshold) .id(id) - .parentId(parentId) - .build(); + .parentId(parentId); for (long i = 0; i < batchDuration; i += eventDuration) { - batch.addTestEvent(new TestEventSingleToStoreBuilder(storeActionRejectionThreshold) + builder.addTestEvent(new TestEventSingleToStoreBuilder(storeActionRejectionThreshold) .content(CONTENT.getBytes(StandardCharsets.UTF_8)) .id(bookId, scope, start.plusMillis(i), UUID.randomUUID().toString()) .endTimestamp(start.plusMillis(i + eventDuration)) @@ -161,7 +159,7 @@ protected TestEventToStore generateTestEvent (String scope, Instant start, long .build()); } - return batch; + return builder.build(); } @NotNull diff --git a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/testevents/TestEventIteratorProviderTest.java b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/testevents/TestEventIteratorProviderTest.java index 26d7477aa..e404b6080 100644 --- a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/testevents/TestEventIteratorProviderTest.java +++ b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/integration/testevents/TestEventIteratorProviderTest.java @@ -117,7 +117,6 @@ protected void generateData() throws CradleStorageException, IOException { storedTestEvent = new StoredTestEventSingle(eventToStore.asSingle(), pageId); } - storedData.computeIfAbsent(eventToStore.getScope(), e -> new ArrayList<>()) .add(storedTestEvent); } diff --git a/cradle-cassandra/src/test/resources/log4j2.properties b/cradle-cassandra/src/test/resources/log4j2.properties index dcc0da407..a935f3acb 100644 --- a/cradle-cassandra/src/test/resources/log4j2.properties +++ b/cradle-cassandra/src/test/resources/log4j2.properties @@ -24,7 +24,7 @@ appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{dd MMM yyyy HH:mm:ss,SSS} %-6p [%-15t] %c - %m%n # Root logger level -rootLogger.level = DEBUG +rootLogger.level = INFO # Root logger referring to console appender rootLogger.appenderRef.stdout.ref = ConsoleLogger diff --git a/cradle-core/build.gradle b/cradle-core/build.gradle index e1bf27cce..4ce250fc2 100644 --- a/cradle-core/build.gradle +++ b/cradle-core/build.gradle @@ -14,6 +14,7 @@ dependencies { testImplementation 'org.apache.logging.log4j:log4j-core' testImplementation 'org.testng:testng:7.9.0' testImplementation 'org.assertj:assertj-core:3.25.3' + testImplementation 'commons-codec:commons-codec:1.16.1' } test { diff --git a/cradle-core/src/jmh/java/com/exactpro/cradle/serialization/EventMessageIdSerializerBenchmark.java b/cradle-core/src/jmh/java/com/exactpro/cradle/serialization/EventMessageIdSerializerBenchmark.java new file mode 100644 index 000000000..2841aa5c7 --- /dev/null +++ b/cradle-core/src/jmh/java/com/exactpro/cradle/serialization/EventMessageIdSerializerBenchmark.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.serialization; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.Direction; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.serialization.version2.EventMessageIdSerializer; +import com.exactpro.cradle.testevents.TestEventSingleToStore; +import com.exactpro.cradle.utils.CradleStorageException; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; +import static org.openjdk.jmh.annotations.Mode.Throughput; + +@State(Scope.Benchmark) +public class EventMessageIdSerializerBenchmark { + private static final BookId BOOK_ID = new BookId("benchmark-book"); + private static final String SCOPE = "benchmark-scope"; + private static final String SESSION_ALIAS_PREFIX = "benchmark-alias-"; + private static final String EVENT_ID_PREFIX = "benchmark-event-"; + + @State(Scope.Thread) + public static class EventBatchState { + @Param({"10", "100", "1000"}) + public int size; + @Param({"1", "10"}) + public int aliases; + @Param({"1", "10", "100"}) + public int idsPerDirection; + + private final Collection events = new ArrayList<>(); + @Setup + public void init() throws CradleStorageException { + int seqCounter = 0; + for (int eventIndex = 0; eventIndex < size; eventIndex++) { + Set msgIds = new HashSet<>(); + for (int aliasIndex = 0; aliasIndex < aliases; aliasIndex++) { + for (Direction direction : Direction.values()) { + for (int msgIndex = 0; msgIndex < idsPerDirection; msgIndex++) { + msgIds.add(new StoredMessageId(BOOK_ID, SESSION_ALIAS_PREFIX + aliasIndex, direction, Instant.now(), ++seqCounter)); + } + } + } + events.add( + TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, Instant.now(), EVENT_ID_PREFIX + eventIndex) + .name("benchmark-event") + .messages(msgIds) + .build() + ); + } + } + } + + @State(Scope.Thread) + public static class MessageIdsState { + @Param({"1", "10"}) + public int aliases; + @Param({"1", "10", "100"}) + public int idsPerDirection; + private final Set messageIds = new HashSet<>(); + @Setup + public void init() { + int seqCounter = 0; + for (int aliasIndex = 0; aliasIndex < aliases; aliasIndex++) { + for (Direction direction : Direction.values()) { + for (int msgIndex = 0; msgIndex < idsPerDirection; msgIndex++) { + messageIds.add(new StoredMessageId(BOOK_ID, SESSION_ALIAS_PREFIX + aliasIndex, direction, Instant.now(), ++seqCounter)); + } + } + } + } + } + + @Benchmark + @BenchmarkMode({Throughput}) + public void benchmarkSerializeBatchLinkedMessageIds(EventBatchState state) throws IOException { + com.exactpro.cradle.serialization.version1.EventMessageIdSerializer.serializeBatchLinkedMessageIds(state.events); + } + + @Benchmark + @BenchmarkMode({Throughput}) + public void benchmarkSerializeLinkedMessageIds(MessageIdsState state) throws IOException { + com.exactpro.cradle.serialization.version1.EventMessageIdSerializer.serializeLinkedMessageIds(state.messageIds); + } + + @Benchmark + @BenchmarkMode({Throughput}) + public void benchmarkSerializeBatchLinkedMessageIds2(EventBatchState state) { + EventMessageIdSerializer.serializeBatchLinkedMessageIds(state.events); + } + + @Benchmark + @BenchmarkMode({Throughput}) + public void benchmarkSerializeLinkedMessageIds2(MessageIdsState state) throws IOException { + EventMessageIdSerializer2.serializeLinkedMessageIds(state.messageIds); + } +} diff --git a/cradle-core/src/main/java/com/exactpro/cradle/BookId.java b/cradle-core/src/main/java/com/exactpro/cradle/BookId.java index 447500894..c57827192 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/BookId.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/BookId.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,8 +29,10 @@ public class BookId implements Serializable private static final long serialVersionUID = -8051161407486679704L; private final String name; - public BookId(String name) - { + public BookId(String name) { + if (StringUtils.isEmpty(name)) { + throw new IllegalArgumentException("Book name can't be empty"); + } this.name = StringUtils.lowerCase(name); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/BookInfo.java b/cradle-core/src/main/java/com/exactpro/cradle/BookInfo.java index aa53bcc24..e343bb319 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/BookInfo.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/BookInfo.java @@ -65,7 +65,7 @@ public class BookInfo { private static final long MAX_EPOCH_DAY = getEpochDay(Instant.MAX); private static final IPageInterval EMPTY_PAGE_INTERVAL = new EmptyPageInterval(); - private static final BookId EMPTY_BOOK_ID = new BookId(""); + private static final BookId EMPTY_BOOK_ID = new BookId("th2-internal-empty-book"); static { METRICS.setPageCacheSize(EMPTY_BOOK_ID, HOT, HOT_CACHE_SIZE); diff --git a/cradle-core/src/main/java/com/exactpro/cradle/CradleEntitiesFactory.java b/cradle-core/src/main/java/com/exactpro/cradle/CradleEntitiesFactory.java index 37a11dbf8..76e29db56 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/CradleEntitiesFactory.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/CradleEntitiesFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,9 @@ import com.exactpro.cradle.messages.GroupedMessageBatchToStore; import com.exactpro.cradle.messages.MessageBatchToStore; -import com.exactpro.cradle.testevents.StoredTestEventId; import com.exactpro.cradle.testevents.TestEventBatchToStore; import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; import com.exactpro.cradle.testevents.TestEventSingleToStoreBuilder; -import com.exactpro.cradle.utils.CradleStorageException; /** * Factory to create entities to be used with {@link CradleStorage}. Created objects will conform with particular CradleStorage settings. @@ -55,10 +53,6 @@ public GroupedMessageBatchToStore groupedMessageBatch(String group) { return new GroupedMessageBatchToStore(group, maxMessageBatchSize, storeActionRejectionThreshold); } - public TestEventBatchToStore testEventBatch(StoredTestEventId id, String name, StoredTestEventId parentId) throws CradleStorageException { - return new TestEventBatchToStore(id, name, parentId, maxTestEventBatchSize, storeActionRejectionThreshold); - } - public TestEventBatchToStoreBuilder testEventBatchBuilder() { return new TestEventBatchToStoreBuilder(maxTestEventBatchSize, storeActionRejectionThreshold); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/CradleStorage.java b/cradle-core/src/main/java/com/exactpro/cradle/CradleStorage.java index b7ef08071..7de5845c2 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/CradleStorage.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/CradleStorage.java @@ -35,7 +35,9 @@ import com.exactpro.cradle.resultset.CradleResultSet; import com.exactpro.cradle.testevents.StoredTestEvent; import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.StoredTestEventIdUtils; import com.exactpro.cradle.testevents.TestEventBatchToStore; +import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; import com.exactpro.cradle.testevents.TestEventFilter; import com.exactpro.cradle.testevents.TestEventSingleToStore; import com.exactpro.cradle.testevents.TestEventSingleToStoreBuilder; @@ -70,6 +72,7 @@ import static com.exactpro.cradle.Order.DIRECT; import static com.exactpro.cradle.Order.REVERSE; import static com.exactpro.cradle.resultset.EmptyResultSet.emptyResultSet; +import static com.exactpro.cradle.testevents.StoredTestEventIdUtils.track; /** * Storage which holds information about all data sent or received and test events. @@ -753,8 +756,9 @@ TestEventToStore alignEventTimestampsToPage(TestEventToStore event, PageInfo pag logger.warn("Batch contains events from different pages, aligning event timestamps to first event's page's end ({})", event.getId()); - TestEventBatchToStore newBatch = entitiesFactory.testEventBatch(event.getId(), event.getName(), event.getParentId()); - newBatch.setType(event.getType()); + TestEventBatchToStoreBuilder newBatch = entitiesFactory.testEventBatchBuilder() + .id(event.getId()) + .parentId(event.getParentId()); for (var e : batch.getTestEvents()) { TestEventSingleToStore newEvent = new TestEventSingleToStoreBuilder(storeActionRejectionThreshold) @@ -770,7 +774,7 @@ TestEventToStore alignEventTimestampsToPage(TestEventToStore event, PageInfo pag newBatch.addTestEvent(newEvent); } - return newBatch; + return newBatch.build(); } /** @@ -786,7 +790,7 @@ public final void storeTestEvent(TestEventToStore event) throws IOException, Cra logger.debug("Storing test event {}", id); PageInfo page = findPage(id.getBookId(), id.getStartTimestamp()); - TestEventUtils.validateTestEvent(event, getBookCache().getBook(id.getBookId()), storeActionRejectionThreshold); + TestEventUtils.validateTestEvent(event, getBookCache().getBook(id.getBookId())); final TestEventToStore alignedEvent = alignEventTimestampsToPage(event, page); doStoreTestEvent(alignedEvent, page); @@ -808,15 +812,17 @@ public final void storeTestEvent(TestEventToStore event) throws IOException, Cra */ public final CompletableFuture storeTestEventAsync(TestEventToStore event) throws IOException, CradleStorageException { + track(event, "storing event"); StoredTestEventId id = event.getId(); logger.debug("Storing test event {} asynchronously", id); PageInfo page = findPage(id.getBookId(), id.getStartTimestamp()); - TestEventUtils.validateTestEvent(event, getBookCache().getBook(id.getBookId()), storeActionRejectionThreshold); + TestEventUtils.validateTestEvent(event, getBookCache().getBook(id.getBookId())); final TestEventToStore alignedEvent = alignEventTimestampsToPage(event, page); - + track(event, "aligned event"); CompletableFuture result = doStoreTestEventAsync(alignedEvent, page); result.whenCompleteAsync((r, error) -> { + track(event, "stored event"); if (error != null) logger.error("Error while storing test event " + id + " asynchronously", error); else @@ -827,15 +833,18 @@ public final CompletableFuture storeTestEventAsync(TestEventToStore event) return result; return result.thenComposeAsync(r -> { - logger.debug("Updating parents of test event {} asynchronously", id); - CompletableFuture result2 = doUpdateParentTestEventsAsync(alignedEvent); - result2.whenCompleteAsync((r2, error) -> { - if (error != null) - logger.error("Error while updating parents of test event " + id + " asynchronously", error); - else - logger.debug("Parents of test event {} have been updated asynchronously", alignedEvent.getId()); - }, composingService); - return result2; + try(AutoCloseable ignored = StoredTestEventIdUtils.Statistic.measure("update-parent")) { + logger.debug("Updating parents of test event {} asynchronously", id); + CompletableFuture result2 = doUpdateParentTestEventsAsync(alignedEvent); + result2.whenCompleteAsync((r2, error) -> { + track(event, "updated parent"); + if (error != null) + logger.error("Error while updating parents of test event " + id + " asynchronously", error); + else + logger.debug("Parents of test event {} have been updated asynchronously", alignedEvent.getId()); + }, composingService); + return result2; + } catch (Exception e) { throw new RuntimeException(e); } }, composingService); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/messages/StoredMessageId.java b/cradle-core/src/main/java/com/exactpro/cradle/messages/StoredMessageId.java index f2b000086..977501119 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/messages/StoredMessageId.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/messages/StoredMessageId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ public class StoredMessageId implements Serializable { private final Direction direction; private final Instant timestamp; private final long sequence; + private final int hash; public StoredMessageId(BookId bookId, String sessionAlias, Direction direction, Instant timestamp, long sequence) { this.bookId = bookId; @@ -52,6 +53,7 @@ public StoredMessageId(BookId bookId, String sessionAlias, Direction direction, sequence, bookId, sessionAlias, direction.getLabel())); } this.sequence = sequence; + this.hash = Objects.hash(bookId, sessionAlias, direction, timestamp, sequence); } @@ -97,7 +99,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(bookId, sessionAlias, direction, timestamp, sequence); + return hash; } @@ -110,8 +112,10 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; StoredMessageId other = (StoredMessageId) obj; - return Objects.equals(bookId, other.bookId) && Objects.equals(sessionAlias, other.sessionAlias) - && direction == other.direction && Objects.equals(timestamp, other.timestamp) - && sequence == other.sequence; + return sequence == other.sequence && + Objects.equals(timestamp, other.timestamp) && + Objects.equals(sessionAlias, other.sessionAlias) && + direction == other.direction && + Objects.equals(bookId, other.bookId); } } \ No newline at end of file diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventBatchSerializer.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventBatchSerializer.java index 209aedce9..d8d4d01af 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventBatchSerializer.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventBatchSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.exactpro.cradle.serialization; -import com.exactpro.cradle.testevents.BatchedStoredTestEvent; import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventBatchToStore; import com.exactpro.cradle.testevents.TestEventSingleToStore; import java.nio.ByteBuffer; @@ -28,7 +28,7 @@ import java.util.List; import static com.exactpro.cradle.serialization.EventsSizeCalculator.calculateBatchEventSize; -import static com.exactpro.cradle.serialization.EventsSizeCalculator.calculateEventRecordSize; +import static com.exactpro.cradle.serialization.EventsSizeCalculator.getEventRecordSize; import static com.exactpro.cradle.serialization.Serialization.EventBatchConst.EVENT_BATCH_ENT_MAGIC; import static com.exactpro.cradle.serialization.Serialization.EventBatchConst.EVENT_BATCH_MAGIC; import static com.exactpro.cradle.serialization.Serialization.EventBatchConst.EVENT_BATCH_PROTOCOL_VER; @@ -40,8 +40,8 @@ public class EventBatchSerializer { - public byte[] serializeEventRecord(BatchedStoredTestEvent event) { - ByteBuffer allocate = ByteBuffer.allocate(calculateEventRecordSize(event)); + public byte[] serializeEventRecord(TestEventSingleToStore event) { + ByteBuffer allocate = ByteBuffer.allocate(getEventRecordSize(event)); this.serializeEventRecord(event, allocate); return allocate.array(); } @@ -57,7 +57,7 @@ private void printId(StoredTestEventId id, ByteBuffer buffer) { printString(id_str, buffer); } - public void serializeEventRecord(BatchedStoredTestEvent event, ByteBuffer buffer) { + public void serializeEventRecord(TestEventSingleToStore event, ByteBuffer buffer) { buffer.putShort(EVENT_BATCH_ENT_MAGIC); printId(event.getId(), buffer); @@ -70,7 +70,7 @@ public void serializeEventRecord(BatchedStoredTestEvent event, ByteBuffer buffer } - public SerializedEntityData serializeEventBatch(Collection batch) { + public SerializedEntityData serializeEventBatch(Collection batch) { SerializationBatchSizes sizes = calculateBatchEventSize(batch); ByteBuffer buffer = ByteBuffer.allocate(sizes.total); List serializedEventMetadata = serializeEventBatch(batch, buffer, sizes); @@ -78,13 +78,21 @@ public SerializedEntityData serializeEventBatch(Collec return new SerializedEntityData<>(serializedEventMetadata, buffer.array()); } - public void serializeEventBatch(Collection batch, ByteBuffer buffer) { + public SerializedEntityData serializeEventBatch(TestEventBatchToStore batch) { + SerializationBatchSizes sizes = EventsSizeCalculator.getBatchEventSize(batch); + ByteBuffer buffer = ByteBuffer.allocate(sizes.total); + List serializedEventMetadata = serializeEventBatch(batch.getTestEvents(), buffer, sizes); + + return new SerializedEntityData<>(serializedEventMetadata, buffer.array()); + } + + public void serializeEventBatch(Collection batch, ByteBuffer buffer) { SerializationBatchSizes eventBatchSizes = calculateBatchEventSize(batch); serializeEventBatch(batch, buffer, eventBatchSizes); } public List serializeEventBatch( - Collection batch, ByteBuffer buffer, SerializationBatchSizes eventBatchSizes + Collection batch, ByteBuffer buffer, SerializationBatchSizes eventBatchSizes ) { List serializedEventMetadata = new ArrayList<>(batch.size()); @@ -94,11 +102,11 @@ public List serializeEventBatch( buffer.putInt(batch.size()); int i = 0; - for (BatchedStoredTestEvent event : batch) { + for (TestEventSingleToStore event : batch) { int eventSize = eventBatchSizes.entities[i]; buffer.putInt(eventSize); - this.serializeEventRecord(event, buffer); + serializeEventRecord(event, buffer); serializedEventMetadata.add(new SerializedEntityMetadata(event.getStartTimestamp(), eventSize)); i++; } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventsSizeCalculator.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventsSizeCalculator.java index 682244f8c..7ff2a418d 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventsSizeCalculator.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventsSizeCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,16 @@ package com.exactpro.cradle.serialization; -import com.exactpro.cradle.testevents.BatchedStoredTestEvent; import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventBatchToStore; import com.exactpro.cradle.testevents.TestEventSingle; +import com.exactpro.cradle.testevents.TestEventSingleToStore; import java.nio.charset.StandardCharsets; import java.util.Collection; +import static com.exactpro.cradle.testevents.StoredTestEventIdUtils.logId; + public class EventsSizeCalculator { /* @@ -54,29 +57,36 @@ public class EventsSizeCalculator { private final static int ENTITY_LENGTH_IN_BATCH = 4; - public static int calculateEventRecordSize(TestEventSingle message) { - return EVENT_RECORD_CONST + lenId(message.getId()) + lenStr(message.getName()) + lenStr(message.getType()) - + lenId(message.getParentId()) + (message.getContent() != null ? message.getContent().length : 0); + public static int calculateEventRecordSize(TestEventSingle event) { + return EVENT_RECORD_CONST + lenId(event.getId()) + lenStr(event.getName()) + lenStr(event.getType()) + + lenId(event.getParentId()) + (event.getContent() != null ? event.getContent().length : 0); } + public static int getEventRecordSize(TestEventSingle event) { + if (event.getSize() > 0) { + return event.getSize(); + } + throw new IllegalStateException("Event '" + logId(event) + "' isn't prepared for store"); + } + private static int lenId(StoredTestEventId id) { - return id != null && id.getId() != null ? lenStr(id.getId()) : 0; + return id != null ? lenStr(id.getId()) : 0; } private static int lenStr(String str) { return str != null ? str.getBytes(StandardCharsets.UTF_8).length : 0; } - public static SerializationBatchSizes calculateBatchEventSize(Collection events) { - + public static SerializationBatchSizes calculateBatchEventSize(Collection events) { + SerializationBatchSizes sizes = new SerializationBatchSizes(events.size()); sizes.total = EVENT_BATCH_LEN_CONST; int i = 0; - for (BatchedStoredTestEvent storedMessage : events) { - sizes.entities[i] = EventsSizeCalculator.calculateEventRecordSize(storedMessage); + for (TestEventSingleToStore event : events) { + sizes.entities[i] = getEventRecordSize(event); sizes.total += ENTITY_LENGTH_IN_BATCH + sizes.entities[i]; i++; } @@ -84,7 +94,20 @@ public static SerializationBatchSizes calculateBatchEventSize(Collection 0) { + return event.getSize() + ENTITY_LENGTH_IN_BATCH; + } + throw new IllegalStateException("Event '" + logId(event) + "' isn't prepared for store"); + } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/Serialization.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/Serialization.java index 717b27c09..214ce8303 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/serialization/Serialization.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/Serialization.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,98 @@ public static class EventBatchConst { public static class EventMessageIdsConst { - public static final byte VERSION = 1; + /** + *
+		 * Structure {@link #BATCH_LINKS}:
+		 *  1B: version
+		 *  1B: event type {@link #BATCH_LINKS}
+		 *  4B: number of events for batch
+		 *  session alias to short number mappings:
+		 *    2B: mapping size
+		 *    for each item:
+		 *    	2B + nB: session alias length in bytes
+		 *    	2B: short number
+		 *  for each event:
+		 *    nB: scope length in bytes
+		 *    12B: timestamp
+		 *    2B + nB: id length in bytes
+		 *    4B: number of message ids
+		 *    for each session alias:
+		 *		2B: session alias short number
+		 *		for each direction:
+		 *		  1B: direction
+		 *		  4B: number of message ids related to the direction
+		 *		  for each message id:
+		 *			12B: timestamp
+		 *		    8B: sequence
+		 *		  1B: {@link #END_OF_DATA}
+		 * ---
+		 * Structure {@link #SINGLE_EVENT_LINKS}:
+		 *   1B: version
+		 *   1B: event type {@link #SINGLE_EVENT_LINKS}
+		 *   4B: number of message ids
+		 *   if (ids.size = 1)
+		 *     2B + nB: session alias length in bytes
+		 *     1B: direction
+		 *     12B: timestamp
+		 *     8B: sequence
+		 *   else
+		 *      for each session alias:
+		 *        2B + nB: session alias length in bytes
+		 *        for each direction:
+		 *	  	    1B: direction
+		 *	  	    4B: number of message ids related to the direction
+		 *	  	    for each message id:
+		 *	  	      12B: timestamp
+		 *	  	      8B: sequence
+		 *	  	    1B: {@link #END_OF_DATA}
+  		 * 
+ */ + public static final byte VERSION_1 = 1; + /** + *
+		 * Structure {@link #BATCH_LINKS}:
+		 *  1B: version
+		 *  1B: event type {@link #BATCH_LINKS}
+		 *  2B: number of events for batch
+		 *  session alias to short number mappings:
+		 *    2B: mapping size
+		 *    for each item:
+		 *    	2B + nB: session alias length in bytes
+		 *    	2B: short number
+		 *  for each event:
+		 *    12B: timestamp
+		 *    2B + nB: id length in bytes
+		 *    2B: number of message ids
+		 *    for each message ids:
+		 *		2B: session alias short number
+		 *		1B: direction
+		 *		12B: timestamp
+		 *		8B: sequence
+		 * ---
+		 * Structure {@link #SINGLE_EVENT_LINKS}:
+		 *   1B: version
+		 *   1B: event type {@link #SINGLE_EVENT_LINKS}
+		 *   2B: number of message ids
+		 *   if (ids.size = 1)
+		 *     2B + nB: session alias length in bytes
+		 *     1B: direction
+		 *     12B: timestamp
+		 *     8B: sequence
+		 *   else
+		 *     session alias to short number mappings:
+		 *       2B: mapping size
+		 *       for each item:
+		 *    	   2B + nB: session alias length in bytes
+		 *    	   2B: short number
+		 *     for each message ids:
+		 *		 2B: session alias short number
+		 *		 1B: direction
+		 *		 12B: timestamp
+		 *		 8B: sequence
+		 * 
+ */ + public static final byte VERSION_2 = 2; public static final byte SINGLE_EVENT_LINKS = 1; public static final byte BATCH_LINKS = 2; public static final byte END_OF_DATA = 0; diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/SerializationBatchSizes.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/SerializationBatchSizes.java index 9ee8f6ec5..0f05b8242 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/serialization/SerializationBatchSizes.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/SerializationBatchSizes.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,4 +22,9 @@ public class SerializationBatchSizes { SerializationBatchSizes(int size) { this.entities = new int[size]; } + + SerializationBatchSizes(int total, int[] entities) { + this.total = total; + this.entities = entities; + } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventMessageIdDeserializer.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version1/EventMessageIdDeserializer.java similarity index 94% rename from cradle-core/src/main/java/com/exactpro/cradle/serialization/EventMessageIdDeserializer.java rename to cradle-core/src/main/java/com/exactpro/cradle/serialization/version1/EventMessageIdDeserializer.java index 3033b2ef7..6b3930aa7 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventMessageIdDeserializer.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version1/EventMessageIdDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.exactpro.cradle.serialization; +package com.exactpro.cradle.serialization.version1; import com.exactpro.cradle.BookId; import com.exactpro.cradle.Direction; import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.serialization.SerializationException; import com.exactpro.cradle.testevents.StoredTestEventId; import com.exactpro.cradle.utils.CradleSerializationUtils; @@ -42,9 +43,9 @@ public static Set deserializeLinkedMessageIds(byte[] bytes, Boo DataInputStream dis = new DataInputStream(bais)) { byte version = dis.readByte(); - if (version != VERSION) + if (version != VERSION_1) throw new SerializationException(String.format(NOT_SUPPORTED_PROTOCOL_FORMAT, "linkedMessageIds", - VERSION, version)); + VERSION_1, version)); byte mark = dis.readByte(); if (mark != SINGLE_EVENT_LINKS) throw new IOException("Unexpected data mark. Expected "+SINGLE_EVENT_LINKS+", got "+mark); @@ -80,9 +81,9 @@ public static Map> deserializeBatchLinke DataInputStream dis = new DataInputStream(bais)) { byte version = dis.readByte(); - if (version != VERSION) + if (version != VERSION_1) throw new SerializationException(String.format(NOT_SUPPORTED_PROTOCOL_FORMAT, "batchLinkedMessages", - VERSION, version)); + VERSION_1, version)); byte mark = dis.readByte(); if (mark != BATCH_LINKS) throw new IOException("Unexpected data mark. Expected "+BATCH_LINKS+", got "+mark); diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventMessageIdSerializer.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version1/EventMessageIdSerializer.java similarity index 79% rename from cradle-core/src/main/java/com/exactpro/cradle/serialization/EventMessageIdSerializer.java rename to cradle-core/src/main/java/com/exactpro/cradle/serialization/version1/EventMessageIdSerializer.java index 06c168d57..6e9e0f520 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/serialization/EventMessageIdSerializer.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version1/EventMessageIdSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.exactpro.cradle.serialization; +package com.exactpro.cradle.serialization.version1; import com.exactpro.cradle.Direction; import com.exactpro.cradle.messages.StoredMessageId; import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventSingleToStore; import com.exactpro.cradle.utils.CradleSerializationUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -27,6 +28,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -73,30 +75,31 @@ public static byte[] serializeLinkedMessageIds(Set ids) return result; } - public static byte[] serializeBatchLinkedMessageIds(Map> ids) + public static byte[] serializeBatchLinkedMessageIds(Collection eventsWithAttachedMessages) throws IOException { - if (ids == null || ids.isEmpty()) - return null; + if (eventsWithAttachedMessages == null || eventsWithAttachedMessages.isEmpty()) { + return null; + } byte[] result; try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos)) { - writeIdsStart(ids, dos); + writeIdsStart(eventsWithAttachedMessages, dos); - Map mapping = getSessions(ids); + Map mapping = getSessions(eventsWithAttachedMessages); writeMapping(mapping, dos); - for (Map.Entry> eventMessages : ids.entrySet()) + for (TestEventSingleToStore eventMessages : eventsWithAttachedMessages) { - StoredTestEventId eventId = eventMessages.getKey(); + StoredTestEventId eventId = eventMessages.getId(); CradleSerializationUtils.writeString(eventId.getScope(), dos); CradleSerializationUtils.writeInstant(eventId.getStartTimestamp(), dos); CradleSerializationUtils.writeString(eventId.getId(), dos); - dos.writeInt(eventMessages.getValue().size()); + dos.writeInt(eventMessages.getMessages().size()); - Map, List>> bySession = divideIdsBySession(eventMessages.getValue()); + Map, List>> bySession = divideIdsBySession(eventMessages.getMessages()); for (Map.Entry, List>> sessionIds : bySession.entrySet()) { dos.writeShort(mapping.get(sessionIds.getKey())); @@ -112,16 +115,16 @@ public static byte[] serializeBatchLinkedMessageIds(Map ids, DataOutputStream dos) throws IOException { - dos.writeByte(VERSION); + dos.writeByte(VERSION_1); dos.writeByte(SINGLE_EVENT_LINKS); dos.writeInt(ids.size()); } - private static void writeIdsStart(Map> ids, DataOutputStream dos) throws IOException + private static void writeIdsStart(Collection eventsWithAttachedMessages, DataOutputStream dos) throws IOException { - dos.writeByte(VERSION); + dos.writeByte(VERSION_1); dos.writeByte(BATCH_LINKS); - dos.writeInt(ids.size()); + dos.writeInt(eventsWithAttachedMessages.size()); } private static void writeMapping(Map mapping, DataOutputStream dos) throws IOException @@ -151,10 +154,10 @@ private static Map, List>> d return result; } - private static Map getSessions(Map> ids) + private static Map getSessions(Collection eventsWithAttachedMessages) { Set sessions = new HashSet<>(); - ids.values().forEach(messageIds -> messageIds.forEach(id -> sessions.add(id.getSessionAlias()))); + eventsWithAttachedMessages.forEach(event -> event.getMessages().forEach(id -> sessions.add(id.getSessionAlias()))); Map result = new HashMap<>(); for (String session : sessions) @@ -166,9 +169,9 @@ private static void writeDirectionIds(Pair, List first = firstSecondIds.getLeft(), second = firstSecondIds.getRight(); - if (first != null && first.size() > 0) + if (first != null && !first.isEmpty()) writeDirectionIds(Direction.FIRST, first, dos); - if (second != null && second.size() > 0) + if (second != null && !second.isEmpty()) writeDirectionIds(Direction.SECOND, second, dos); dos.writeByte(END_OF_DATA); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/version2/EventMessageIdDeserializer.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version2/EventMessageIdDeserializer.java new file mode 100644 index 000000000..b4685c2a4 --- /dev/null +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version2/EventMessageIdDeserializer.java @@ -0,0 +1,152 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.serialization.version2; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.Direction; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.serialization.SerializationException; +import com.exactpro.cradle.testevents.StoredTestEventId; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.BATCH_LINKS; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.DIRECTION_FIRST; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.DIRECTION_SECOND; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.SINGLE_EVENT_LINKS; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.VERSION_2; +import static com.exactpro.cradle.serialization.Serialization.NOT_SUPPORTED_PROTOCOL_FORMAT; +import static com.exactpro.cradle.utils.CradleSerializationUtils.readInstant; +import static com.exactpro.cradle.utils.CradleSerializationUtils.readString; + +public class EventMessageIdDeserializer { + + public static Set deserializeLinkedMessageIds(ByteBuffer buffer, + BookId bookId) throws SerializationException { + if (buffer == null) { + return Collections.emptySet(); + } + + byte version = buffer.get(); + if (version != VERSION_2) { + throw new SerializationException(String.format(NOT_SUPPORTED_PROTOCOL_FORMAT, "linkedMessageIds", + VERSION_2, version)); + } + byte mark = buffer.get(); + if (mark != SINGLE_EVENT_LINKS) { + throw new SerializationException("Unexpected data mark. Expected " + SINGLE_EVENT_LINKS + ", got " + mark); + } + + int size = buffer.getShort(); + Set result = new HashSet<>(size); + if (size == 1) { + String sessionAlias = readString(buffer); + Direction direction = readDirection(buffer); + if (direction == null) { + throw new SerializationException("Invalid direction"); + } + Instant timestamp = readInstant(buffer); + result.add(new StoredMessageId(bookId, sessionAlias, direction, timestamp, buffer.getLong())); + } else { + Map mapping = readMapping(buffer); + for (int i = 0; i < size; i++) { + result.add(readMessageIds(bookId, mapping, buffer)); + } + } + return result; + } + + public static Map> deserializeBatchLinkedMessageIds(ByteBuffer buffer, + BookId bookId, + String scope) throws SerializationException { + if (buffer == null) { + return Collections.emptyMap(); + } + + byte version = buffer.get(); + if (version != VERSION_2) { + throw new SerializationException(String.format(NOT_SUPPORTED_PROTOCOL_FORMAT, "batchLinkedMessages", + VERSION_2, version)); + } + byte mark = buffer.get(); + if (mark != BATCH_LINKS) { + throw new SerializationException("Unexpected data mark. Expected " + BATCH_LINKS + ", got " + mark); + } + + int events = buffer.getShort(); + Map> result = new HashMap<>(events); + + Map mapping = readMapping(buffer); + + for (int i = 0; i < events; i++) { + Instant startTimestamp = readInstant(buffer); + String id = readString(buffer); + StoredTestEventId eventId = new StoredTestEventId(bookId, scope, startTimestamp, id); + + int size = buffer.getShort(); + Set eventLinks = new HashSet<>(size); + + for (int j = 0; j < size; j++) { + eventLinks.add(readMessageIds(bookId, mapping, buffer)); + } + + result.put(eventId, eventLinks); + } + return result; + } + + private static Map readMapping(ByteBuffer buffer) { + int size = buffer.getShort(); + Map result = new HashMap<>(size); + for (int i = 0; i < size; i++) { + String sessionAlias = readString(buffer); + int index = buffer.getShort(); + result.put(index, sessionAlias); + } + return result; + } + + private static StoredMessageId readMessageIds(BookId bookId, + Map mapping, + ByteBuffer buffer) throws SerializationException { + int index = buffer.getShort(); + return new StoredMessageId(bookId, + mapping.get(index), + readDirection(buffer), + readInstant(buffer), + buffer.getLong()); + } + + private static Direction readDirection(ByteBuffer buffer) throws SerializationException { + byte direction = buffer.get(); + if (direction == 0) { + return null; + } + if (direction == DIRECTION_FIRST) { + return Direction.FIRST; + } else if (direction == DIRECTION_SECOND) { + return Direction.SECOND; + } + throw new SerializationException("Unknown direction - " + direction); + } +} diff --git a/cradle-core/src/main/java/com/exactpro/cradle/serialization/version2/EventMessageIdSerializer.java b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version2/EventMessageIdSerializer.java new file mode 100644 index 000000000..f3280a02c --- /dev/null +++ b/cradle-core/src/main/java/com/exactpro/cradle/serialization/version2/EventMessageIdSerializer.java @@ -0,0 +1,177 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.serialization.version2; + +import com.exactpro.cradle.Direction; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventSingleToStore; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.BATCH_LINKS; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.DIRECTION_FIRST; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.DIRECTION_SECOND; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.SINGLE_EVENT_LINKS; +import static com.exactpro.cradle.serialization.Serialization.EventMessageIdsConst.VERSION_2; +import static com.exactpro.cradle.utils.CradleSerializationUtils.writeInstant; +import static com.exactpro.cradle.utils.CradleSerializationUtils.writeString; + +public class EventMessageIdSerializer { + + public static ByteBuffer serializeLinkedMessageIds(Set msgIds) { + if (msgIds == null || msgIds.isEmpty()) { + return null; + } + + if (msgIds.size() == 1) { + StoredMessageId msgId = msgIds.iterator().next(); + // 1B: version, 1B: event type, 2B: number of events + // 2B + nB: session alias length in bytes, 1B: direction, 12B: timestamp, 8B: sequence + int size = 27 + msgId.getSessionAlias().getBytes().length; + + ByteBuffer buffer = ByteBuffer.allocate(size); + writeIdsStart((short) msgIds.size(), SINGLE_EVENT_LINKS, buffer); + writeString(msgId.getSessionAlias(), buffer); + buffer.put(msgId.getDirection() == Direction.FIRST ? DIRECTION_FIRST : DIRECTION_SECOND); + writeInstant(msgId.getTimestamp(), buffer); + buffer.putLong(msgId.getSequence()); + buffer.flip(); + return buffer; + } else { + Map aliasMapping = new HashMap<>(); + // 1B: version, 1B: event type, 2B: number of events, 2B: mapping size + final Counter size = new Counter(6); + final Counter counter = new Counter(); + collectMapping(msgIds, aliasMapping, size, counter); + + ByteBuffer buffer = ByteBuffer.allocate(size.num); + writeIdsStart((short) msgIds.size(), SINGLE_EVENT_LINKS, buffer); + writeMapping(aliasMapping, buffer); + writeMessageIds(msgIds, aliasMapping, buffer); + buffer.flip(); + return buffer; + } + } + + /** + * @param events must have attached message ids + */ + public static ByteBuffer serializeBatchLinkedMessageIds(Collection events) { + if (events.isEmpty()) { + return null; + } + + Map aliasMapping = new HashMap<>(); + // 1B: version, 1B: event type, 2B: number of events, 2B: mapping size + final Counter size = new Counter(6); + Counter counter = new Counter(); + for (TestEventSingleToStore event : events) { + if (!event.hasMessages()) { + continue; + } + StoredTestEventId eventId = event.getId(); + Set msgIds = event.getMessages(); + if (msgIds.isEmpty()) { + throw new SecurityException("Event " + eventId + " hasn't got attached messages"); + } + // 12B: timestamp, 2B + nB: id length in bytes, 2B: number of message ids + size.inc(16 + eventId.getId().getBytes().length); + collectMapping(msgIds, aliasMapping, size, counter); + } + + ByteBuffer buffer = ByteBuffer.allocate(size.num); + writeIdsStart((short) events.size(), BATCH_LINKS, buffer); + writeMapping(aliasMapping, buffer); + for (TestEventSingleToStore event : events) { + StoredTestEventId eventId = event.getId(); + Set msgIds = event.getMessages(); + + writeInstant(eventId.getStartTimestamp(), buffer); + writeString(eventId.getId(), buffer); + buffer.putShort((short) msgIds.size()); + writeMessageIds(msgIds, aliasMapping, buffer); + } + buffer.flip(); + return buffer; + } + + private static void collectMapping(Set msgIds, Map aliasMapping, Counter size, Counter counter) { + for (StoredMessageId msgId : msgIds) { + aliasMapping.computeIfAbsent(msgId.getSessionAlias(), alias -> { + // 2B + nB: session alias length in bytes, 2B: short number + size.inc(4 + alias.getBytes().length); + return (short) counter.inc(); + }); + // 2B: session alias short number, 1B: direction, 12B: timestamp, 8B: sequence + size.inc(23); + } + } + + private static void writeMessageIds(Set msgIds, Map aliasMapping, ByteBuffer buffer) { + for (StoredMessageId msgId : msgIds) { + buffer.putShort(aliasMapping.get(msgId.getSessionAlias())); + buffer.put(msgId.getDirection() == Direction.FIRST ? DIRECTION_FIRST : DIRECTION_SECOND); + writeInstant(msgId.getTimestamp(), buffer); + buffer.putLong(msgId.getSequence()); + } + } + + private static void writeIdsStart(short size, byte links, ByteBuffer buffer) { + buffer.put(VERSION_2); + buffer.put(links); + buffer.putShort(size); + } + + private static void writeMapping(Map mapping, ByteBuffer buffer) { + buffer.putShort((short) mapping.size()); + for (Map.Entry entry : mapping.entrySet()) { + writeString(entry.getKey(), buffer); + buffer.putShort(entry.getValue()); + } + } + + private static class Counter { + private int num; + + public Counter(int num) { + this.num = num; + } + + public Counter() { + this(0); + } + + public int inc(int num) { + this.num += num; + return this.num; + } + + public int inc() { + return inc(1); + } + + @Override + public String toString() { + return String.valueOf(num); + } + } +} diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/BatchedStoredTestEvent.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/BatchedStoredTestEvent.java index 91db5e363..6a954829a 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/BatchedStoredTestEvent.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/BatchedStoredTestEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,9 +41,11 @@ public class BatchedStoredTestEvent implements TestEventSingle, Serializable private final transient TestEventBatch batch; private final transient PageId pageId; + + /** size is positive when event stores otherwise negative */ + private final transient int size; - public BatchedStoredTestEvent(TestEventSingle event, TestEventBatch batch, PageId pageId) - { + public BatchedStoredTestEvent(TestEventSingle event, TestEventBatch batch, PageId pageId, int size) { this.id = event.getId(); this.name = event.getName(); this.type = event.getType(); @@ -51,18 +53,12 @@ public BatchedStoredTestEvent(TestEventSingle event, TestEventBatch batch, PageI this.endTimestamp = event.getEndTimestamp(); this.success = event.isSuccess(); - - byte[] eventContent = event.getContent(); - if (eventContent == null) - this.content = null; - else - { - this.content = new byte[eventContent.length]; - System.arraycopy(eventContent, 0, this.content, 0, this.content.length); - } - + + this.content = event.getContent(); + this.batch = batch; this.pageId = pageId; + this.size = size; } protected BatchedStoredTestEvent(StoredTestEventId id, String name, String type, StoredTestEventId parentId, @@ -77,6 +73,7 @@ protected BatchedStoredTestEvent(StoredTestEventId id, String name, String type, this.content = content; this.batch = batch; this.pageId = pageId; + this.size = -1; } @Override @@ -128,24 +125,28 @@ public byte[] getContent() { return content; } - + + @Override + public int getSize() { + return size; + } public PageId getPageId() { return pageId; } - + public StoredTestEventId getBatchId() { return batch.getId(); } - + public boolean hasChildren() { return batch.hasChildren(getId()); } - - public Collection getChildren() + + public Collection getChildren() { return batch.getChildren(getId()); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEvent.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEvent.java index de1345530..0a3a96e16 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEvent.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEvent.java @@ -59,9 +59,9 @@ public static StoredTestEventSingle single(TestEventSingleToStore event, PageId return new StoredTestEventSingle(event, pageId); } - public static StoredTestEventBatch batch(TestEventBatchToStore event, PageId pageId) throws CradleStorageException + public static StoredTestEventBatch batch(TestEventBatchToStore batch, PageId pageId) throws CradleStorageException { - return new StoredTestEventBatch(event, pageId); + return new StoredTestEventBatch(batch, pageId); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java index 886c54667..b8a17741d 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java @@ -16,53 +16,50 @@ package com.exactpro.cradle.testevents; +import com.exactpro.cradle.PageId; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.utils.CradleStorageException; + import java.time.Instant; -import java.util.List; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import com.exactpro.cradle.PageId; -import com.exactpro.cradle.messages.StoredMessageId; -import com.exactpro.cradle.utils.CradleStorageException; - /** * Holds information about batch of test events stored in Cradle. - * Events stored in the batch can refer to each other to form a hierarchy. No references to these events are possible outside of the batch and vice versa. + * Events stored in the batch can refer to each other to form a hierarchy. No references to these events are possible outside the batch and vice versa. * Root events in the batch should reference batch's parent. */ public class StoredTestEventBatch extends StoredTestEvent implements TestEventBatch { - private final Map events; - private final Collection rootEvents; - private final Map> children; + private final Map events; + private final Collection rootEvents; + private final Map> children; private final Map> messages; private final Instant endTimestamp; private final boolean success; private Instant lastStartTimestamp; public StoredTestEventBatch(StoredTestEventId id, String name, String type, StoredTestEventId parentId, - Collection batchEvents, + Collection batchEvents, Map> messages, - PageId pageId, String error, Instant recDate) throws CradleStorageException - { + PageId pageId, String error, Instant recDate) throws CradleStorageException { super(id, name, type, parentId, pageId, error, recDate); - Map allEvents = new LinkedHashMap<>(); - List roots = new ArrayList<>(); - Map> childrenPerEvent = new LinkedHashMap<>(); - Map> batchMessages = new HashMap<>(); + Map allEvents = new LinkedHashMap<>(); + List roots = new ArrayList<>(); + Map> childrenPerEvent = new LinkedHashMap<>(); Instant end = null; boolean success = true; if (batchEvents != null) { - for (BatchedStoredTestEvent event : batchEvents) + for (TestEventSingle event : batchEvents) { StoredTestEventId eventParentId = event.getParentId(); if (eventParentId == null) @@ -70,17 +67,13 @@ public StoredTestEventBatch(StoredTestEventId id, String name, String type, Stor boolean isRoot = Objects.equals(eventParentId, getParentId()); - BatchedStoredTestEvent child = new BatchedStoredTestEvent(event, this, pageId); + BatchedStoredTestEvent child = new BatchedStoredTestEvent(event, this, pageId, event.getSize()); allEvents.put(child.getId(), child); if (!isRoot) childrenPerEvent.computeIfAbsent(eventParentId, k -> new ArrayList<>()).add(child); else roots.add(child); - Set eventMessages = messages != null ? messages.get(child.getId()) : null; - if (eventMessages != null) - batchMessages.put(child.getId(), Set.copyOf(eventMessages)); - Instant eventEnd = child.getEndTimestamp(); if (eventEnd != null) { @@ -96,7 +89,7 @@ public StoredTestEventBatch(StoredTestEventId id, String name, String type, Stor this.events = Collections.unmodifiableMap(allEvents); this.rootEvents = Collections.unmodifiableList(roots); this.children = Collections.unmodifiableMap(childrenPerEvent); - this.messages = Collections.unmodifiableMap(batchMessages); + this.messages = Collections.unmodifiableMap(messages); this.endTimestamp = end; this.success = success; getLastStartTimestamp(); @@ -136,19 +129,19 @@ public int getTestEventsCount() } @Override - public BatchedStoredTestEvent getTestEvent(StoredTestEventId id) + public TestEventSingle getTestEvent(StoredTestEventId id) { return events.get(id); } @Override - public Collection getTestEvents() + public Collection getTestEvents() { return events.values(); } @Override - public Collection getRootTestEvents() + public Collection getRootTestEvents() { return rootEvents; } @@ -166,9 +159,9 @@ public boolean hasChildren(StoredTestEventId parentId) } @Override - public Collection getChildren(StoredTestEventId parentId) + public Collection getChildren(StoredTestEventId parentId) { - Collection result = children.get(parentId); + Collection result = children.get(parentId); return result != null ? Collections.unmodifiableCollection(result) : Collections.emptyList(); } @@ -184,7 +177,7 @@ public Instant getLastStartTimestamp() { if (lastStartTimestamp == null) { lastStartTimestamp = getStartTimestamp(); - for (BatchedStoredTestEvent el : getTestEvents()) { + for (TestEventSingle el : getTestEvents()) { lastStartTimestamp = lastStartTimestamp.isBefore(el.getStartTimestamp()) ? el.getStartTimestamp() : lastStartTimestamp; } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventId.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventId.java index c09baf248..cabd4788d 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventId.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,19 @@ package com.exactpro.cradle.testevents; +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.utils.CradleIdException; +import com.exactpro.cradle.utils.EscapeUtils; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nonnull; import java.io.Serializable; import java.time.Instant; import java.util.List; import java.util.Objects; -import org.apache.commons.lang3.StringUtils; - -import com.exactpro.cradle.BookId; -import com.exactpro.cradle.utils.CradleIdException; -import com.exactpro.cradle.utils.EscapeUtils; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.StringUtils.isEmpty; /** * Holds ID of a test event stored in Cradle @@ -33,20 +36,25 @@ public class StoredTestEventId implements Serializable { private static final long serialVersionUID = 6954746788528942942L; - public static final String ID_PARTS_DELIMITER = EscapeUtils.DELIMITER_STR; - private final BookId bookId; - private final String scope; - private final Instant startTimestamp; - private final String id; + private @Nonnull final BookId bookId; + private @Nonnull final String scope; + private @Nonnull final Instant startTimestamp; + private @Nonnull final String id; + private final int hash; - public StoredTestEventId(BookId bookId, String scope, Instant startTimestamp, String id) - { - this.bookId = bookId; + public StoredTestEventId(@Nonnull BookId bookId, + @Nonnull String scope, + @Nonnull Instant startTimestamp, + @Nonnull String id) { + this.bookId = requireNonNull(bookId, "Book id can't be null"); + this.startTimestamp = requireNonNull(startTimestamp, "Event id must have a scope"); + if (isEmpty(scope)) throw new IllegalArgumentException("Scope can't be null or empty"); this.scope = scope; - this.startTimestamp = startTimestamp; + if (isEmpty(id)) throw new IllegalArgumentException("Id can't be null or empty"); this.id = id; + this.hash = Objects.hash(bookId, scope, startTimestamp, id); } public static StoredTestEventId fromString(String id) throws CradleIdException @@ -61,21 +69,25 @@ public static StoredTestEventId fromString(String id) throws CradleIdException } + @Nonnull public BookId getBookId() { return bookId; } + @Nonnull public String getScope() { return scope; } + @Nonnull public Instant getStartTimestamp() { return startTimestamp; } + @Nonnull public String getId() { return id; @@ -85,17 +97,17 @@ public String getId() @Override public String toString() { - return StringUtils.joinWith(ID_PARTS_DELIMITER, - EscapeUtils.escape(bookId.toString()), - EscapeUtils.escape(scope), - StoredTestEventIdUtils.timestampToString(startTimestamp), + return StringUtils.joinWith(ID_PARTS_DELIMITER, + EscapeUtils.escape(bookId.toString()), + EscapeUtils.escape(scope), + StoredTestEventIdUtils.timestampToString(startTimestamp), EscapeUtils.escape(id)); } @Override public int hashCode() { - return Objects.hash(bookId, scope, startTimestamp, id); + return hash; } @Override @@ -108,9 +120,9 @@ public boolean equals(Object obj) if (getClass() != obj.getClass()) return false; StoredTestEventId other = (StoredTestEventId) obj; - return Objects.equals(bookId, other.bookId) - && Objects.equals(scope, other.scope) + return Objects.equals(id, other.id) && Objects.equals(startTimestamp, other.startTimestamp) - && Objects.equals(id, other.id); + && Objects.equals(scope, other.scope) + && Objects.equals(bookId, other.bookId); } } \ No newline at end of file diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventIdUtils.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventIdUtils.java index f49a20b54..028eac39a 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventIdUtils.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventIdUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,28 @@ package com.exactpro.cradle.testevents; -import java.text.ParseException; -import java.time.Instant; -import java.time.format.DateTimeParseException; -import java.util.List; - import com.exactpro.cradle.BookId; import com.exactpro.cradle.utils.CradleIdException; import com.exactpro.cradle.utils.EscapeUtils; import com.exactpro.cradle.utils.TimeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.ParseException; +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Utilities to parse {@link StoredTestEventId} from its string representation which consists of timestamp:uniqueId */ public class StoredTestEventIdUtils { + private StoredTestEventIdUtils() { } public static List splitParts(String id) throws CradleIdException { List parts; @@ -81,4 +88,78 @@ public static String timestampToString(Instant timestamp) { return TimeUtils.toIdTimestamp(timestamp); } + + public static String logId(TestEvent event) { + return event.getBookId().getName() + ':' + + event.getScope() + ':' + + event.getStartTimestamp() + ':' + + event.getId() + " - " + event.getName(); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(StoredTestEventIdUtils.class); + + public static void track(String id, String text) { +// LOGGER.error("Track: " + text + ' ' + id + ' ' + System.nanoTime()); + } + public static void track(TestEventToStore event, String text) { +// String id; +// if (event.isBatch()) { +// id = event.asBatch().getTestEvents().iterator().next().getId().getId(); +// } else { +// id = event.id.getId(); +// } +// track(id, text); + } + + public static class Statistic { + private static final Map DATA = new ConcurrentHashMap<>(); + + public static AutoCloseable measure(String measureName) { + MeasureData measureData = DATA.computeIfAbsent(measureName, MeasureData::new); + return measureData.measure(); + } + } + + private static class MeasureData { + public static final int NANOS_IN_SECOND = 1_000_000_000; + private final Lock lock = new ReentrantLock(); + private final String name; + private double count = 0; + private long sum = 0; + private long lastPrint = System.nanoTime(); + private MeasureData(String name) { + this.name = name; + } + + public AutoCloseable measure() { + return new Measure(); + } + + public void update(long now, long duration) { + lock.lock(); + try { + count++; + sum += duration; + if (now - lastPrint > NANOS_IN_SECOND) { + LOGGER.error("Track (" + name + ") count: " + count + ", sum: " + sum + + ", rate: " + (count/sum*NANOS_IN_SECOND + " times/sec")); + count = 0; + sum = 0; + lastPrint = now; + } + } finally { + lock.unlock(); + } + } + + private class Measure implements AutoCloseable { + private final long start = System.nanoTime(); + + @Override + public void close() { + long now = System.nanoTime(); + update(now, now - start); + } + } + } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventSingle.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventSingle.java index 44d824f80..1f04dea38 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventSingle.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventSingle.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,16 +39,10 @@ public StoredTestEventSingle(StoredTestEventId id, String name, String type, Sto this.endTimestamp = endTimestamp; this.success = success; - - if (eventContent == null) - this.content = null; - else - { - this.content = new byte[eventContent.length]; - System.arraycopy(eventContent, 0, this.content, 0, this.content.length); - } - - this.messages = eventMessages != null && eventMessages.size() > 0 ? Collections.unmodifiableSet(new HashSet<>(eventMessages)) : null; + + this.content = eventContent; + + this.messages = eventMessages != null && !eventMessages.isEmpty() ? Set.copyOf(eventMessages) : null; } public StoredTestEventSingle(TestEventSingle event, PageId pageId) @@ -87,6 +81,11 @@ public Instant getLastStartTimestamp() { return getStartTimestamp(); } + @Override + public int getSize() { + return -1; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatch.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatch.java index 013748a80..498f9a536 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatch.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatch.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,21 +36,21 @@ public interface TestEventBatch extends TestEvent * @param id of test event to get from batch * @return test event for given ID, if it is present in the batch, null otherwise */ - BatchedStoredTestEvent getTestEvent(StoredTestEventId id); + TestEventSingle getTestEvent(StoredTestEventId id); /** * @return collection of test events stored in the batch */ - Collection getTestEvents(); + Collection getTestEvents(); /** * @return collection of root test events stored in the batch */ - Collection getRootTestEvents(); + Collection getRootTestEvents(); /** * @return map of event IDs stored in the batch and the corresponding message IDs */ Map> getBatchMessages(); boolean hasChildren(StoredTestEventId parentId); - Collection getChildren(StoredTestEventId parentId); + Collection getChildren(StoredTestEventId parentId); Set getMessages(StoredTestEventId eventId); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStore.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStore.java index bf365916f..a1ad7a488 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStore.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,17 @@ package com.exactpro.cradle.testevents; import com.exactpro.cradle.messages.StoredMessageId; -import com.exactpro.cradle.serialization.EventsSizeCalculator; import com.exactpro.cradle.utils.CradleStorageException; +import javax.annotation.Nonnull; import java.time.Instant; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * Holds information about batch of test events prepared to be stored in Cradle @@ -36,190 +35,130 @@ * Root events in the batch should reference batch's parent. */ public class TestEventBatchToStore extends TestEventToStore implements TestEventBatch { - private final Map events = new LinkedHashMap<>(); - private final Collection rootEvents = new ArrayList<>(); - private final Map> children = new HashMap<>(); - private final Map> messages = new HashMap<>(); - private final int maxBatchSize; - private int batchSize = EventsSizeCalculator.EVENT_BATCH_LEN_CONST; - - public TestEventBatchToStore(StoredTestEventId id, String name, StoredTestEventId parentId, int maxBatchSize, long storeActionRejectionThreshold) throws CradleStorageException { - super(id, name, parentId, storeActionRejectionThreshold); - success = true; - this.maxBatchSize = maxBatchSize; + private final Collection eventsWithAttachedMessages; + private final Collection events; + private final int batchSize; + TestEventBatchToStore(@Nonnull StoredTestEventId id, + @Nonnull StoredTestEventId parentId, + Instant endTimestamp, + boolean success, + Collection events, + Collection eventsWithAttachedMessages, + int batchSize) throws CradleStorageException { + super(id, + "", + requireNonNull(parentId, "Parent event id can't be null"), + "", + endTimestamp, + success + ); + if (events == null || events.isEmpty()) { + throw new CradleStorageException("Batch " + id + " is empty"); + } + if (batchSize < 1) { + throw new CradleStorageException("Batch " + id + " size can't be negative " + batchSize); + } + this.events = List.copyOf(events); + this.batchSize = batchSize; + this.eventsWithAttachedMessages = List.copyOf(eventsWithAttachedMessages); } - public static TestEventBatchToStoreBuilder builder(int maxBatchSize, long storeActionRejectionThreshold) { return new TestEventBatchToStoreBuilder(maxBatchSize, storeActionRejectionThreshold); } - @SuppressWarnings("ConstantConditions") - @Override - public Set getMessages() { - if (messages == null) //This is the case when validateTestEvent() is called from super constructor - return null; - Set result = new HashSet<>(); - messages.values().forEach(result::addAll); - return result; + public Collection getEventsWithAttachedMessages() { + return eventsWithAttachedMessages; } - @Override public int getTestEventsCount() { return events.size(); } - @Override - public BatchedStoredTestEvent getTestEvent(StoredTestEventId id) { - return events.get(id); - } - - @Override - public Collection getTestEvents() { - return Collections.unmodifiableCollection(events.values()); - } - - @Override - public Collection getRootTestEvents() { - return Collections.unmodifiableCollection(rootEvents); - } - - @Override - public Map> getBatchMessages() { - return Collections.unmodifiableMap(messages); + /** + * @return size of events currently stored in the batch + */ + public int getBatchSize() { + return batchSize; } + /** + * This method has low performance because it isn't target function for ...ToStore implementation + */ @Override - public boolean hasChildren(StoredTestEventId parentId) { - return children.containsKey(parentId); + public Set getMessages() { + if (eventsWithAttachedMessages.isEmpty()) { + return Collections.emptySet(); + } + return eventsWithAttachedMessages.stream() + .flatMap(event -> event.getMessages().stream()) + .collect(Collectors.toUnmodifiableSet()); } + /** + * This method has low performance because it isn't target function for ...ToStore implementation + */ @Override - public Collection getChildren(StoredTestEventId parentId) { - Collection result = children.get(parentId); - return result != null ? Collections.unmodifiableCollection(result) : Collections.emptyList(); + public TestEventSingle getTestEvent(StoredTestEventId id) { + return events.stream() + .filter(event -> Objects.equals(event.getId(), id)) + .findFirst().orElse(null); } @Override - public Set getMessages(StoredTestEventId eventId) { - Set result = messages.get(eventId); - return result != null ? result : Collections.emptySet(); + public Collection getTestEvents() { + return events; } - /** - * @return size of events currently stored in the batch + * This method has low performance because it isn't target function for ...ToStore implementation */ - public int getBatchSize() { - return batchSize; + @Override + public Collection getRootTestEvents() { + return events.stream() + .filter(event -> Objects.equals(event.getParentId(), parentId)) + .collect(Collectors.toUnmodifiableList()); } /** - * Indicates if the batch cannot hold more test events - * - * @return true if batch capacity is reached and the batch must be flushed to Cradle + * This method has low performance because it isn't target function for ...ToStore implementation */ - public boolean isFull() { - return batchSize >= maxBatchSize; + @Override + public Map> getBatchMessages() { + return eventsWithAttachedMessages.stream() + .collect(Collectors.toUnmodifiableMap( + TestEventSingleToStore::getId, + TestEventSingleToStore::getMessages + )); } /** - * Shows how many bytes the batch can hold till its capacity is reached - * - * @return number of bytes the batch can hold + * This method has low performance because it isn't target function for ...ToStore implementation */ - public int getSpaceLeft() { - int result = maxBatchSize - batchSize; - return Math.max(result, 0); + @Override + public boolean hasChildren(StoredTestEventId parentId) { + return events.stream().anyMatch(event -> !Objects.equals(event.getParentId(), parentId)); } /** - * Shows if batch has enough space to hold given test event - * - * @param event to check against batch capacity - * @return true if batch has enough space to hold given test event + * This method has low performance because it isn't target function for ...ToStore implementation */ - public boolean hasSpace(TestEventSingleToStore event) { - return hasSpace(EventsSizeCalculator.calculateRecordSizeInBatch(event)); - } - - private boolean hasSpace(int eventLen) { - return batchSize + eventLen <= maxBatchSize; + @Override + public Collection getChildren(StoredTestEventId parentId) { + return events.stream() + .filter(event -> !Objects.equals(event.getParentId(), parentId)) + .collect(Collectors.toUnmodifiableList()); } - /** - * Adds test event to the batch. Batch will verify the event to match batch conditions. - * Result of this method should be used for all further operations on the event - * - * @param event to add to the batch - * @return immutable test event object - * @throws CradleStorageException if test event cannot be added to the batch due to verification failure + * This method has low performance because it isn't target function for ...ToStore implementation */ - public BatchedStoredTestEvent addTestEvent(TestEventSingleToStore event) throws CradleStorageException { - int currEventSize = EventsSizeCalculator.calculateRecordSizeInBatch(event); - if (!hasSpace(currEventSize)) - throw new CradleStorageException("Batch has not enough space to hold given test event"); - - checkEvent(event); - - StoredTestEventId parentId = event.getParentId(); - if (parentId == null) - throw new CradleStorageException("Event being added to batch must have a parent. " - + "It can be parent of the batch itself or another event already stored in this batch"); - - boolean isRoot; - if (parentId.equals(getParentId())) //Event references batch's parent, so event is actually the root one among events stored in this batch - isRoot = true; - else if (!events.containsKey(parentId)) { - throw new CradleStorageException("Test event with ID '" + parentId + "' should be parent of the batch itself or " - + "should be stored in this batch to be referenced as a parent"); - } else - isRoot = false; - - updateBatchData(event); - - BatchedStoredTestEvent result = new BatchedStoredTestEvent(event, this, null); - events.put(result.getId(), result); - if (!isRoot) - children.computeIfAbsent(parentId, k -> new ArrayList<>()).add(result); - else - rootEvents.add(result); - - batchSize += currEventSize; - - return result; - } - - private void updateBatchData(TestEventSingle event) throws CradleStorageException { - Instant eventEnd = event.getEndTimestamp(); - if (eventEnd != null) { - if (endTimestamp == null || endTimestamp.isBefore(eventEnd)) - endTimestamp = eventEnd; - } - - if (!event.isSuccess()) - success = false; - - //Not checking messages because event being added is already checked for having the same book as the batch and - //event messages are checked for the same book in TestEventUtils.validateTestEvent() - Set eventMessages = event.getMessages(); - if (eventMessages != null && eventMessages.size() > 0) - messages.put(event.getId(), Collections.unmodifiableSet(new HashSet<>(eventMessages))); - } - - private void checkEvent(TestEventSingle event) throws CradleStorageException { - if (!getBookId().equals(event.getBookId())) - throw new CradleStorageException("Batch contains events of book '" + getBookId() + "', " - + "but in your event it is '" + event.getBookId() + "'"); - if (!getScope().equals(event.getScope())) - throw new CradleStorageException("Batch contains events of scope '" + getScope() + "', " - + "but in your event it is '" + event.getScope() + "'"); - if (event.getStartTimestamp().isBefore(getStartTimestamp())) - throw new CradleStorageException("Start timestamp of event being added is before the batch start timestamp"); - - if (events.containsKey(event.getId())) - throw new CradleStorageException("Test event with ID '" + event.getId() + "' is already present in batch"); + @Override + public Set getMessages(StoredTestEventId eventId) { + TestEventSingleToStore result = eventsWithAttachedMessages.stream() + .filter(event -> Objects.equals(event.getId(), eventId)) + .findFirst().orElse(null); + return result == null ? Collections.emptySet() : result.getMessages(); } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStoreBuilder.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStoreBuilder.java index 92ff22af3..de4d2ea34 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStoreBuilder.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventBatchToStoreBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,79 +17,225 @@ package com.exactpro.cradle.testevents; import com.exactpro.cradle.BookId; +import com.exactpro.cradle.serialization.EventsSizeCalculator; import com.exactpro.cradle.utils.CradleStorageException; import java.time.Instant; -import java.util.UUID; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.exactpro.cradle.serialization.EventsSizeCalculator.getRecordSizeInBatch; /** * Builder for {@link TestEventBatchToStore} object. After calling {@link #build()} method, the builder can be reused to build new test event */ -public class TestEventBatchToStoreBuilder { +public class TestEventBatchToStoreBuilder extends TestEventToStoreBuilder { private final int maxBatchSize; - private StoredTestEventId id; - private String name; - private StoredTestEventId parentId; - private String type; - - private final long storeActionRejectionThreshold; + private final Set eventIds = new HashSet<>(); + private final List eventsWithAttachedMessages = new ArrayList<>(); + private final List events = new ArrayList<>(); + private int batchSize = EventsSizeCalculator.EVENT_BATCH_LEN_CONST; + private boolean success = true; + private Instant endTimestamp = null; public TestEventBatchToStoreBuilder(int maxBatchSize, long storeActionRejectionThreshold) { + super(storeActionRejectionThreshold); this.maxBatchSize = maxBatchSize; - this.storeActionRejectionThreshold = storeActionRejectionThreshold; } public TestEventBatchToStoreBuilder id(StoredTestEventId id) { - this.id = id; + checkEvents(id, events); + super.id(id); return this; } public TestEventBatchToStoreBuilder id(BookId book, String scope, Instant startTimestamp, String id) { - this.id = new StoredTestEventId(book, scope, startTimestamp, id); + super.id(book, scope, startTimestamp, id); return this; } public TestEventBatchToStoreBuilder idRandom(BookId book, String scope) { - this.id = new StoredTestEventId(book, scope, Instant.now(), UUID.randomUUID().toString()); + super.idRandom(book, scope); return this; } - public TestEventBatchToStoreBuilder name(String name) { - this.name = name; + public TestEventBatchToStoreBuilder parentId(StoredTestEventId parentId) { + checkParentEventIds(parentId, eventIds, events); + super.parentId(parentId); return this; } - public TestEventBatchToStoreBuilder parentId(StoredTestEventId parentId) { - this.parentId = parentId; + /** + * Adds test event to the batch. Batch will verify the event to match batch conditions. + * Result of this method should be used for all further operations on the event + * + * @param event to add to the batch + * @return immutable test event object + * @throws CradleStorageException if test event cannot be added to the batch due to verification failure + */ + public TestEventBatchToStoreBuilder addTestEvent(TestEventSingleToStore event) throws CradleStorageException { + int currEventSize = getRecordSizeInBatch(event); + if (!hasSpace(currEventSize)) + throw new CradleStorageException("Batch has not enough space to hold given test event"); + + checkEvent(this.id, event); + + StoredTestEventId parentId = event.getParentId(); + if (parentId == null) { + throw new CradleStorageException("Event being added to batch must have a parent. " + + "It can be parent of the batch itself or another event already stored in this batch"); + } + checkParentEventId(this.parentId, eventIds, parentId); + + if (!event.isSuccess()) { + success = false; + } + Instant endTimestamp = event.getEndTimestamp(); + if (endTimestamp != null && (this.endTimestamp == null || this.endTimestamp.isBefore(endTimestamp))) { + this.endTimestamp = endTimestamp; + } + + if (!eventIds.add(event.getId())) { + throw new CradleStorageException("Test event with ID '" + event.getId() + "' is already present in batch"); + } + events.add(event); + batchSize += currEventSize; + if (event.hasMessages()) { + eventsWithAttachedMessages.add(event); + } return this; } - public TestEventBatchToStoreBuilder type(String type) { - this.type = type; - return this; + /** + * Shows if batch has enough space to hold given test event + * + * @param event to check against batch capacity + * @return true if batch has enough space to hold given test event + */ + public boolean hasSpace(TestEventSingleToStore event) { + return hasSpace(getRecordSizeInBatch(event)); + } + + /** + * @return size of events currently stored in the batch + */ + public int getBatchSize() { + return batchSize; + } + + /** + * Indicates if the batch cannot hold more test events + * + * @return true if batch capacity is reached and the batch must be flushed to Cradle + */ + public boolean isFull() { + return batchSize >= maxBatchSize; } + /** + * Shows how many bytes the batch can hold till its capacity is reached + * + * @return number of bytes the batch can hold + */ + public int getSpaceLeft() { + int result = maxBatchSize - batchSize; + return Math.max(result, 0); + } public TestEventBatchToStore build() throws CradleStorageException { try { - TestEventBatchToStore result = createTestEventToStore(id, name, parentId); - result.setType(type); - return result; + + return new TestEventBatchToStore( + id, + parentId, + endTimestamp, + success, + events, + eventsWithAttachedMessages, + batchSize + ); } finally { reset(); } } + protected void reset() { + super.reset(); + batchSize = EventsSizeCalculator.EVENT_BATCH_LEN_CONST; + success = true; + endTimestamp = null; + eventsWithAttachedMessages.clear(); + eventIds.clear(); + events.clear(); + } - protected TestEventBatchToStore createTestEventToStore(StoredTestEventId id, String name, StoredTestEventId parentId) throws CradleStorageException { - return new TestEventBatchToStore(id, name, parentId, maxBatchSize, storeActionRejectionThreshold); + private boolean hasSpace(int eventLen) { + return batchSize + eventLen <= maxBatchSize; } - protected void reset() { - id = null; - name = null; - parentId = null; - type = null; + private static void checkEvents(StoredTestEventId id, Collection events) { + if (id == null || events == null || events.isEmpty()) { + return; + } + BookId book = id.getBookId(); + String scope = id.getScope(); + Instant startTimestamp = id.getStartTimestamp(); + + for (TestEventSingle event : events) { + checkEvent(event, book, scope, startTimestamp); + } + } + + private static void checkParentEventId(StoredTestEventId batchParentId, + Set knownIds, + StoredTestEventId newParentId) throws CradleStorageException { + if (batchParentId == null || newParentId == null) { + return; + } + if(!(batchParentId.equals(newParentId) || knownIds.contains(newParentId))) { + throw new CradleStorageException("Test event with ID '" + newParentId + "' should be parent of the batch itself or " + + "should be stored in this batch to be referenced as a parent"); + } + } + private static void checkParentEventIds(StoredTestEventId batchParentId, + Set knownIds, + Collection events) { + if (batchParentId == null || events == null || events.isEmpty()) { + return; + } + for (TestEventSingleToStore event : events) { + if(!(batchParentId.equals(event.getParentId()) || knownIds.contains(event.getParentId()))) { + throw new IllegalArgumentException("Test event with ID '" + event.getParentId() + "' should be parent of the batch itself or " + + "should be stored in this batch to be referenced as a parent"); + } + } + } + + private static void checkEvent(TestEventSingle event, BookId book, String scope, Instant startTimestamp) { + if (!book.equals(event.getBookId())) { + throw new IllegalArgumentException("Batch contains events of book '" + book + "', " + + "but in your event it is '" + event.getBookId() + "'"); + } + if (!scope.equals(event.getScope())) { + throw new IllegalArgumentException("Batch contains events of scope '" + scope + "', " + + "but in your event it is '" + event.getScope() + "'"); + } + if (event.getStartTimestamp().isBefore(startTimestamp)) { + throw new IllegalArgumentException("Start timestamp of event (" + event.getStartTimestamp() + + ") is before the batch start timestamp (" + startTimestamp + ')'); + } + } + + private static void checkEvent(StoredTestEventId id, TestEventSingle event) throws CradleStorageException { + if (id == null || event == null) { + return; + } + checkEvent(event, id.getBookId(), id.getScope(), id.getStartTimestamp()); } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingle.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingle.java index 628d0133d..bb30ffd06 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingle.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingle.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,10 @@ /** * Interface for all single (individual) test events */ -public interface TestEventSingle extends TestEvent -{ +public interface TestEventSingle extends TestEvent { + /** + * @return positive value if event prepared to store otherwise negative + */ + int getSize(); byte[] getContent(); } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStore.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStore.java index 1ab93aedf..f13a5f44f 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStore.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,31 +16,45 @@ package com.exactpro.cradle.testevents; -import com.exactpro.cradle.BookId; import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.serialization.EventsSizeCalculator; import com.exactpro.cradle.utils.CradleStorageException; -import com.exactpro.cradle.utils.TestEventUtils; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; import java.util.Set; + /** * Holds information about single (individual) test event prepared to be stored in Cradle */ public class TestEventSingleToStore extends TestEventToStore implements TestEventSingle { - private Set messages; - private byte[] content; - - public TestEventSingleToStore(StoredTestEventId id, String name, StoredTestEventId parentId, long storeActionRejectionThreshold) throws CradleStorageException { - super(id, name, parentId, storeActionRejectionThreshold); + private @Nonnull final Set messages; + private @Nonnull final byte[] content; + private final int size; + + TestEventSingleToStore(@Nonnull StoredTestEventId id, + @Nonnull String name, + @Nullable StoredTestEventId parentId, + @Nonnull String type, + @Nullable Instant endTimestamp, + boolean success, + @Nonnull Set messages, + @Nonnull byte[] content) throws CradleStorageException { + super(id, name, parentId, type, endTimestamp, success); + this.messages = Set.copyOf(requireNonNull(messages, "Messages can't be null")); + this.content = requireNonNull(content, "Content can't be null"); + // Size calculation must be last instruction because function uses other class fields + this.size = EventsSizeCalculator.calculateEventRecordSize(this); } - public static TestEventSingleToStoreBuilder builder(long storeActionRejectionThreshold) { return new TestEventSingleToStoreBuilder(storeActionRejectionThreshold); } + @Nonnull @Override public Set getMessages() { return messages; @@ -51,34 +65,11 @@ public byte[] getContent() { return content; } - public void setContent(byte[] content) { - this.content = content; - } - - - public void setEndTimestamp(Instant endTimestamp) throws CradleStorageException { - this.endTimestamp = endTimestamp; - TestEventUtils.validateTestEventEndDate(this); - } - - public void setSuccess(boolean success) { - this.success = success; - } - - public void setMessages(Set messages) throws CradleStorageException { - validateAttachedMessageIds(messages); - this.messages = messages; + public int getSize() { + return size; } - private void validateAttachedMessageIds(Set ids) throws CradleStorageException { - if (ids == null) - return; - BookId eventBookId = getId().getBookId(); - for (StoredMessageId id : ids) { - BookId messageBookId = id.getBookId(); - if (!eventBookId.equals(messageBookId)) - throw new CradleStorageException("Book of message (" + - messageBookId + ") differs from the event book (" + eventBookId + ")"); - } + public boolean hasMessages() { + return !messages.isEmpty(); } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStoreBuilder.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStoreBuilder.java index ccb781cfa..91f5d45eb 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStoreBuilder.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventSingleToStoreBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,51 +24,70 @@ import java.util.HashSet; import java.util.Set; +import static org.apache.commons.lang3.StringUtils.isEmpty; + /** * Builder for {@link TestEventSingleToStore} object. After calling {@link #build()} method, the builder can be reused to build new test event */ -public class TestEventSingleToStoreBuilder { - private StoredTestEventId id; +public class TestEventSingleToStoreBuilder extends TestEventToStoreBuilder { + static final byte[] EMPTY_CONTENT = new byte[0]; private String name; - private StoredTestEventId parentId; - private String type; + private String type = ""; private Instant endTimestamp; - private boolean success; - private Set messages; - private byte[] content; - - private final long storeActionRejectionThreshold; + private boolean success = true; + private final Set messages = new HashSet<>(); + private byte[] content = EMPTY_CONTENT; public TestEventSingleToStoreBuilder(long storeActionRejectionThreshold) { - this.storeActionRejectionThreshold = storeActionRejectionThreshold; + super(storeActionRejectionThreshold); } public TestEventSingleToStoreBuilder id(StoredTestEventId id) { - this.id = id; + checkMessageIds(id, this.messages); + super.id(id); return this; } public TestEventSingleToStoreBuilder id(BookId book, String scope, Instant startTimestamp, String id) { - this.id = new StoredTestEventId(book, scope, startTimestamp, id); + super.id(book, scope, startTimestamp, id); + return this; + } + + @Override + public TestEventSingleToStoreBuilder idRandom(BookId book, String scope) { + super.idRandom(book, scope); return this; } public TestEventSingleToStoreBuilder name(String name) { + if (isEmpty(name)) { + throw new IllegalArgumentException("Name can't be null or empty"); + } this.name = name; return this; } public TestEventSingleToStoreBuilder parentId(StoredTestEventId parentId) { - this.parentId = parentId; + if (parentId == null) { + return this; + } + super.parentId(parentId); return this; } public TestEventSingleToStoreBuilder type(String type) { + if (type == null) { + return this; + } this.type = type; return this; } public TestEventSingleToStoreBuilder endTimestamp(Instant endTimestamp) { + if (endTimestamp == null) { + return this; + } + checkEndTimestamp(id, endTimestamp); this.endTimestamp = endTimestamp; return this; } @@ -79,18 +98,27 @@ public TestEventSingleToStoreBuilder success(boolean success) { } public TestEventSingleToStoreBuilder messages(Set ids) { - this.messages = ids; + if (ids == null || ids.isEmpty()) { + return this; + } + checkMessageIds(this.id, ids); + this.messages.addAll(ids); return this; } public TestEventSingleToStoreBuilder message(StoredMessageId id) { - if (messages == null) - messages = new HashSet<>(); - messages.add(id); + if (id == null) { + return this; + } + checkMessageId(this.id, id); + this.messages.add(id); return this; } public TestEventSingleToStoreBuilder content(byte[] content) { + if (content == null) { + return this; + } this.content = content; return this; } @@ -98,31 +126,65 @@ public TestEventSingleToStoreBuilder content(byte[] content) { public TestEventSingleToStore build() throws CradleStorageException { try { - TestEventSingleToStore result = createTestEventToStore(id, name, parentId, storeActionRejectionThreshold); - result.setType(type); - result.setEndTimestamp(endTimestamp); - result.setSuccess(success); - result.setMessages(messages); - result.setContent(content); - return result; + return new TestEventSingleToStore( + id, + name, + parentId, + type, + endTimestamp, + success, + messages, + content + ); } finally { reset(); } } - - protected TestEventSingleToStore createTestEventToStore(StoredTestEventId id, String name, StoredTestEventId parentId, long storeActionRejectionThreshold) throws CradleStorageException { - return new TestEventSingleToStore(id, name, parentId, storeActionRejectionThreshold); - } - protected void reset() { + super.reset(); id = null; name = null; parentId = null; - type = null; + type = ""; endTimestamp = null; - success = false; - messages = null; - content = null; + success = true; + messages.clear(); + content = EMPTY_CONTENT; + } + + private static void checkMessageIds(StoredTestEventId id, Set msgIds) { + if (id == null || msgIds == null || msgIds.isEmpty()) { + return; + } + + for (StoredMessageId msgId : msgIds) { + if (!id.getBookId().equals(msgId.getBookId())) { + throw new IllegalStateException("Book of message '" + id + + "' differs from test event book (" + id.getBookId() + ")"); + } + } + } + + private static void checkMessageId(StoredTestEventId id, StoredMessageId msgId) { + if (id == null || msgId == null) { + return; + } + + if (!id.getBookId().equals(msgId.getBookId())) { + throw new IllegalStateException("Book of message '" + id + + "' differs from test event book (" + id.getBookId() + ")"); + } + } + + private static void checkEndTimestamp(StoredTestEventId id, Instant endTimestamp) { + if (id == null || endTimestamp == null) { + return; + } + + if (endTimestamp.isBefore(id.getStartTimestamp())) { + throw new IllegalStateException("Test event cannot end (" + endTimestamp + + ") sooner than it started (" + id.getStartTimestamp() + ')'); + } } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStore.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStore.java index 1005e263f..1aca28cd0 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStore.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ package com.exactpro.cradle.testevents; import com.exactpro.cradle.utils.CradleStorageException; -import com.exactpro.cradle.utils.TestEventUtils; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; @@ -27,18 +28,25 @@ * Holds basic information about test event prepared to be stored in Cradle. Events extend this class with additional data */ public abstract class TestEventToStore implements TestEvent { - protected final StoredTestEventId id; - protected final String name; - protected final StoredTestEventId parentId; - protected String type; - protected Instant endTimestamp; - protected boolean success; - - public TestEventToStore(StoredTestEventId id, String name, StoredTestEventId parentId, long storeActionRejectionThreshold) throws CradleStorageException { - this.id = id; - this.name = name; + protected @Nonnull final StoredTestEventId id; + protected @Nonnull final String name; + protected @Nullable final StoredTestEventId parentId; + protected @Nonnull final String type; + protected @Nullable final Instant endTimestamp; + protected final boolean success; + + TestEventToStore(@Nonnull StoredTestEventId id, + @Nonnull String name, + @Nullable StoredTestEventId parentId, + @Nonnull String type, + @Nullable Instant endTimestamp, + boolean success) throws CradleStorageException { + this.id = requireNonNull(id, "Id can't be null"); + this.name = requireNonNull(name, "Name can't be null"); + this.type = requireNonNull(type, "Type can't be null"); + this.endTimestamp = endTimestamp; this.parentId = parentId; - TestEventUtils.validateTestEvent(this, storeActionRejectionThreshold); + this.success = success; } public static TestEventSingleToStoreBuilder singleBuilder(long storeActionRejectionThreshold) { @@ -57,32 +65,32 @@ public static TestEventBatchToStoreBuilder batchBuilder(int maxBatchSize) { return batchBuilder(maxBatchSize, DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS); } + @Nonnull @Override public StoredTestEventId getId() { return id; } + @Nonnull @Override public String getName() { return name; } + @Nullable @Override public StoredTestEventId getParentId() { return parentId; } + @Nonnull @Override public String getType() { return type; } - public void setType(String type) { - this.type = type; - } - - + @Nullable @Override public Instant getEndTimestamp() { return endTimestamp; @@ -109,4 +117,11 @@ public final TestEventSingleToStore asSingle() { public final TestEventBatchToStore asBatch() { return (TestEventBatchToStore) this; } + + protected static T requireNonNull(T obj, String message) throws CradleStorageException { + if (obj == null) { + throw new CradleStorageException(message); + } + return obj; + } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStoreBuilder.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStoreBuilder.java new file mode 100644 index 000000000..07d48922f --- /dev/null +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/TestEventToStoreBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.testevents; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.utils.CradleStorageException; + +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +import static java.util.Objects.requireNonNull; + +public abstract class TestEventToStoreBuilder { + private static final String BASE_UUID = UUID.randomUUID().toString(); + private static final AtomicLong ID_COUNTER = new AtomicLong(); + + protected StoredTestEventId id; + protected StoredTestEventId parentId; + private final long storeActionRejectionThreshold; + + protected TestEventToStoreBuilder(long storeActionRejectionThreshold) { + this.storeActionRejectionThreshold = storeActionRejectionThreshold; + } + + public TestEventToStoreBuilder id(StoredTestEventId id) { + checkParentEventId(id, this.parentId); + Instant now = Instant.now(); + if (id.getStartTimestamp().isAfter(now.plusMillis(storeActionRejectionThreshold))) { + throw new IllegalArgumentException( + "Event start timestamp (" + id.getStartTimestamp() + + ") is greater than current timestamp ( " + now + " ) plus storeActionRejectionThreshold interval (" + storeActionRejectionThreshold + ")ms"); + } + this.id = requireNonNull(id, "Id can't be null"); + return this; + } + + public TestEventToStoreBuilder id(BookId book, String scope, Instant startTimestamp, String id) { + return id(new StoredTestEventId(book, scope, startTimestamp, id)); + } + + public TestEventToStoreBuilder idRandom(BookId book, String scope) { + return id(new StoredTestEventId(book, scope, Instant.now(), BASE_UUID + ID_COUNTER.incrementAndGet())); + } + + public TestEventToStoreBuilder parentId(StoredTestEventId parentId) { + if (parentId == null) { + return this; + } + checkParentEventId(this.id, parentId); + this.parentId = parentId; + return this; + } + + public StoredTestEventId getId() { + return id; + } + + public StoredTestEventId getParentId() { + return parentId; + } + + public abstract TestEventToStore build() throws CradleStorageException; + + protected void reset() { + id = null; + parentId = null; + } + + private static void checkParentEventId(StoredTestEventId id, StoredTestEventId parentId) { + if (id == null || parentId == null) { + return; + } + if (id.equals(parentId)) throw new IllegalArgumentException("Test event cannot reference itself"); + if (!id.getBookId().equals(parentId.getBookId())) { + throw new IllegalArgumentException("Test event and its parent must be from the same book"); + } + if (!id.getScope().equals(parentId.getScope())) { + throw new IllegalArgumentException("Test event and its parent must be from the same scope"); + } + } +} diff --git a/cradle-core/src/main/java/com/exactpro/cradle/utils/CradleSerializationUtils.java b/cradle-core/src/main/java/com/exactpro/cradle/utils/CradleSerializationUtils.java index 9293a50e9..89c529d0c 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/utils/CradleSerializationUtils.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/utils/CradleSerializationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.time.Instant; public class CradleSerializationUtils @@ -29,6 +30,12 @@ public static void writeString(String s, DataOutputStream dos) throws IOExceptio dos.writeShort(bytes.length); dos.write(bytes); } + + public static void writeString(String s, ByteBuffer buffer) { + byte[] bytes = s.getBytes(); + buffer.putShort((short) bytes.length); + buffer.put(bytes); + } public static String readString(DataInputStream dis) throws IOException { @@ -37,6 +44,13 @@ public static String readString(DataInputStream dis) throws IOException dis.readFully(bytes); return new String(bytes); } + + public static String readString(ByteBuffer buffer) { + int length = buffer.getShort(); + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes); + } public static void writeInstant(Instant i, DataOutputStream dos) throws IOException @@ -44,9 +58,18 @@ public static void writeInstant(Instant i, DataOutputStream dos) throws IOExcept dos.writeLong(i.getEpochSecond()); dos.writeInt(i.getNano()); } + + public static void writeInstant(Instant i, ByteBuffer buffer) { + buffer.putLong(i.getEpochSecond()); + buffer.putInt(i.getNano()); + } public static Instant readInstant(DataInputStream dis) throws IOException { return Instant.ofEpochSecond(dis.readLong(), dis.readInt()); } + + public static Instant readInstant(ByteBuffer buffer) { + return Instant.ofEpochSecond(buffer.getLong(), buffer.getInt()); + } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/utils/TestEventUtils.java b/cradle-core/src/main/java/com/exactpro/cradle/utils/TestEventUtils.java index 77e6663d6..567fa4a3e 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/utils/TestEventUtils.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/utils/TestEventUtils.java @@ -23,25 +23,26 @@ import com.exactpro.cradle.serialization.EventBatchCommonParams; import com.exactpro.cradle.serialization.EventBatchDeserializer; import com.exactpro.cradle.serialization.EventBatchSerializer; -import com.exactpro.cradle.serialization.EventMessageIdDeserializer; -import com.exactpro.cradle.serialization.EventMessageIdSerializer; +import com.exactpro.cradle.serialization.SerializationException; import com.exactpro.cradle.serialization.SerializedEntityData; import com.exactpro.cradle.serialization.SerializedEntityMetadata; +import com.exactpro.cradle.serialization.version2.EventMessageIdSerializer; import com.exactpro.cradle.testevents.BatchedStoredTestEvent; import com.exactpro.cradle.testevents.StoredTestEventId; import com.exactpro.cradle.testevents.TestEvent; import com.exactpro.cradle.testevents.TestEventBatch; +import com.exactpro.cradle.testevents.TestEventBatchToStore; +import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; import com.exactpro.cradle.testevents.TestEventSingle; import com.exactpro.cradle.testevents.TestEventSingleToStore; import com.exactpro.cradle.testevents.TestEventToStore; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.ByteBuffer; -import java.time.Instant; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Set; @@ -58,7 +59,7 @@ public class TestEventUtils { * @param bookInfo bookInfo * @throws CradleStorageException if validation failed */ - public static void validateTestEvent(TestEvent event, BookInfo bookInfo, long storeActionRejectionThreshold) throws CradleStorageException { + public static void validateTestEvent(TestEvent event, BookInfo bookInfo) throws CradleStorageException { if (bookInfo != null && event.getParentId() != null) { PageInfo pageInfo = bookInfo.findPage(event.getParentId().getStartTimestamp()); if (pageInfo == null) { @@ -68,46 +69,6 @@ public static void validateTestEvent(TestEvent event, BookInfo bookInfo, long st bookInfo.getId())); } } - - validateTestEvent(event, storeActionRejectionThreshold); - } - - /** - * Checks that test event has all necessary fields set - * - * @param event to validate - * @throws CradleStorageException if validation failed - */ - public static void validateTestEvent(TestEvent event, long storeActionRejectionThreshold) throws CradleStorageException { - if (event.getId() == null) - throw new CradleStorageException("Test event ID cannot be null"); - - if (event.getId().equals(event.getParentId())) - throw new CradleStorageException("Test event cannot reference itself"); - - if (event instanceof TestEventSingle && StringUtils.isEmpty(event.getName())) - throw new CradleStorageException("Single test event must have a name"); - if (event instanceof TestEventBatch && event.getParentId() == null) - throw new CradleStorageException("Batch must have a parent"); - - if (event.getBookId() == null || StringUtils.isEmpty(event.getBookId().toString())) - throw new CradleStorageException("Test event must have a book"); - if (StringUtils.isEmpty(event.getScope())) - throw new CradleStorageException("Test event must have a scope"); - if (event.getStartTimestamp() == null) - throw new CradleStorageException("Test event must have a start timestamp"); - Instant now = Instant.now(); - if (event.getStartTimestamp().isAfter(now.plusMillis(storeActionRejectionThreshold))) - throw new CradleStorageException( - "Event start timestamp (" + TimeUtils.toLocalTimestamp(event.getStartTimestamp()) + - ") is greater than current timestamp ( " + TimeUtils.toLocalTimestamp(now) + " ) plus storeActionRejectionThreshold interval (" + storeActionRejectionThreshold + ")ms"); - validateTestEventEndDate(event); - if (event.getParentId() != null && !event.getBookId().equals(event.getParentId().getBookId())) - throw new CradleStorageException("Test event and its parent must be from the same book"); - - Set messages = event.getMessages(); - if (messages != null) - validateMessages(messages, event.getBookId()); } /** @@ -124,11 +85,11 @@ public static void validateTestEventEndDate(TestEvent event) throws CradleStorag /** * Serializes test events, skipping non-meaningful or calculable fields * - * @param testEvents to serialize + * @param batch to serialize * @return array of bytes, containing serialized events */ - public static SerializedEntityData serializeTestEvents(Collection testEvents) { - return serializer.serializeEventBatch(testEvents); + public static SerializedEntityData serializeTestEvents(TestEventBatchToStore batch) { + return serializer.serializeEventBatch(batch); } /** @@ -188,38 +149,90 @@ public static byte[] getTestEventContentBytes(ByteBuffer content, boolean compre public static SerializedEntityData getTestEventContent(TestEventToStore event) { if (event.isBatch()) { logger.trace("Serializing children of test event batch '{}'", event.getId()); - return serializeTestEvents(event.asBatch().getTestEvents()); + return serializeTestEvents(event.asBatch()); } return serializeTestEvent(event.asSingle()); } - public static byte[] serializeLinkedMessageIds(TestEventToStore event) throws IOException { - if (event.isBatch()) - return EventMessageIdSerializer.serializeBatchLinkedMessageIds(event.asBatch().getBatchMessages()); + public static ByteBuffer serializeLinkedMessageIds(TestEventToStore event) { + if (event.isBatch()) { + return EventMessageIdSerializer.serializeBatchLinkedMessageIds(event.asBatch().getEventsWithAttachedMessages()); + } return EventMessageIdSerializer.serializeLinkedMessageIds(event.asSingle().getMessages()); } - public static Set deserializeLinkedMessageIds(byte[] bytes, BookId bookId) throws IOException { - return EventMessageIdDeserializer.deserializeLinkedMessageIds(bytes, bookId); + public static Set deserializeLinkedMessageIds(ByteBuffer buffer, + BookId bookId) throws IOException { + if (buffer == null) { + return Collections.emptySet(); + } + byte version = peekVersion(buffer); + switch (version) { + case 1: + return com.exactpro.cradle.serialization.version1.EventMessageIdDeserializer.deserializeLinkedMessageIds(buffer.array(), bookId); + case 2: + return com.exactpro.cradle.serialization.version2.EventMessageIdDeserializer.deserializeLinkedMessageIds(buffer, bookId); + default: + throw new SerializationException("Unsupported version " + version + " buffer: " + buffer); + } } - public static byte[] serializeLinkedMessageIds(Set messageIds) throws IOException { - return EventMessageIdSerializer.serializeLinkedMessageIds(messageIds); + public static Map> deserializeBatchLinkedMessageIds(ByteBuffer buffer, + BookId bookId, + String scope) throws IOException { + if (buffer == null) { + return Collections.emptyMap(); + } + byte version = peekVersion(buffer); + switch (version) { + case 1: + return com.exactpro.cradle.serialization.version1.EventMessageIdDeserializer.deserializeBatchLinkedMessageIds(buffer.array(), bookId); + case 2: + return com.exactpro.cradle.serialization.version2.EventMessageIdDeserializer.deserializeBatchLinkedMessageIds(buffer, bookId, scope); + default: + throw new SerializationException("Unsupported version " + version + " buffer: " + buffer); + } } - public static Map> deserializeBatchLinkedMessageIds(byte[] bytes, BookId bookId) throws IOException { - return EventMessageIdDeserializer.deserializeBatchLinkedMessageIds(bytes, bookId); + public static TestEventSingleToStore toTestEventSingleToStore(TestEventSingle event, long storeActionRejectionThreshold) throws CradleStorageException { + return TestEventSingleToStore.singleBuilder(storeActionRejectionThreshold) + .id(event.getId()) + .parentId(event.getParentId()) + .name(event.getName()) + .type(event.getType()) + .endTimestamp(event.getEndTimestamp()) + .success(event.isSuccess()) + .messages(event.getMessages()) + .content(event.getContent()) + .build(); } - public static byte[] serializeBatchLinkedMessageIds(Map> ids) throws IOException { - return EventMessageIdSerializer.serializeBatchLinkedMessageIds(ids); + public static TestEventBatchToStore toTestEventBatchToStore(TestEventBatch batch, int maxBatchSize, long storeActionRejectionThreshold) throws CradleStorageException { + TestEventBatchToStoreBuilder builder = TestEventBatchToStore.builder(maxBatchSize, storeActionRejectionThreshold) + .id(batch.getId()) + .parentId(batch.getParentId()); + batch.getTestEvents().stream() + .map(event -> { + try { + return toTestEventSingleToStore(event, storeActionRejectionThreshold); + } catch (CradleStorageException e) { + throw new RuntimeException(e); + } + }).forEach(event -> { + try { + builder.addTestEvent(event); + } catch (CradleStorageException e) { + throw new RuntimeException(e); + } + }); + return builder.build(); } - private static void validateMessages(Set messages, BookId book) throws CradleStorageException { - for (StoredMessageId id : messages) { - if (!id.getBookId().equals(book)) - throw new CradleStorageException("Book of message '" + id + "' differs from test event book (" + book + ")"); - } + private static byte peekVersion(ByteBuffer buffer) { + int position = buffer.position(); + byte version = buffer.get(); + buffer.position(position); + return version; } } diff --git a/cradle-core/src/main/java/com/exactpro/cradle/utils/TimeUtils.java b/cradle-core/src/main/java/com/exactpro/cradle/utils/TimeUtils.java index 21d4cac04..238668c98 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/utils/TimeUtils.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/utils/TimeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,9 @@ public static Instant fromIdTimestamp(String timestamp) public static String toIdTimestamp(Instant instant) { - return ID_TIMESTAMP_FORMAT.format(toLocalTimestamp(instant)); + // TODO: test and remove redundant line +// return ID_TIMESTAMP_FORMAT.format(toLocalTimestamp(instant)); + return ID_TIMESTAMP_FORMAT.format(instant); } public static Instant toInstant(LocalDate localDate, LocalTime localTime) diff --git a/cradle-core/src/test/java/com/exactpro/cradle/CradleEntitiesFactoryTest.java b/cradle-core/src/test/java/com/exactpro/cradle/CradleEntitiesFactoryTest.java index c524f4cff..9a5a555ef 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/CradleEntitiesFactoryTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/CradleEntitiesFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,15 @@ import com.exactpro.cradle.serialization.EventsSizeCalculator; import com.exactpro.cradle.serialization.MessagesSizeCalculator; import com.exactpro.cradle.testevents.StoredTestEventId; -import com.exactpro.cradle.testevents.TestEventBatchToStore; -import com.exactpro.cradle.utils.CradleStorageException; +import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.time.Instant; +import static org.testng.Assert.assertEquals; + public class CradleEntitiesFactoryTest { private final int maxMessageBatchSize = 123, maxEventBatchSize = 234; @@ -41,21 +42,19 @@ public void prepare() { @Test public void createMessageBatch() { MessageBatchToStore batch = factory.messageBatch(); - Assert.assertEquals(batch.getSpaceLeft(), maxMessageBatchSize - MessagesSizeCalculator.MESSAGE_BATCH_CONST_VALUE, + assertEquals(batch.getSpaceLeft(), maxMessageBatchSize - MessagesSizeCalculator.MESSAGE_BATCH_CONST_VALUE, "CradleEntitiesFactory creates MessageBatchToStore with maximum size defined in factory constructor"); } @Test - public void createTestEventBatch() throws CradleStorageException { + public void createTestEventBatch() { BookId bookId = new BookId("Book1"); String scope = "Scope1"; Instant timestamp = Instant.EPOCH; - TestEventBatchToStore batch = factory.testEventBatchBuilder() + TestEventBatchToStoreBuilder batchBuilder = factory.testEventBatchBuilder() .id(bookId, scope, timestamp, "test_event1") - .name("test_event") - .parentId(new StoredTestEventId(bookId, scope, timestamp.plusNanos(1), "parent_event1")) - .build(); - Assert.assertEquals(batch.getSpaceLeft(), maxEventBatchSize - + .parentId(new StoredTestEventId(bookId, scope, timestamp.plusNanos(1), "parent_event1")); + assertEquals(batchBuilder.getSpaceLeft(), maxEventBatchSize - EventsSizeCalculator.EVENT_BATCH_LEN_CONST, "CradleEntitiesFactory creates TestEventBatchToStore with maximum size defined in factory constructor"); } diff --git a/cradle-core/src/test/java/com/exactpro/cradle/CradleStorageTest.java b/cradle-core/src/test/java/com/exactpro/cradle/CradleStorageTest.java index 7c4146c95..8e59eaf30 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/CradleStorageTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/CradleStorageTest.java @@ -20,7 +20,6 @@ import com.exactpro.cradle.messages.MessageToStore; import com.exactpro.cradle.messages.MessageToStoreBuilder; import com.exactpro.cradle.messages.StoredMessage; -import com.exactpro.cradle.testevents.BatchedStoredTestEvent; import com.exactpro.cradle.testevents.StoredTestEventId; import com.exactpro.cradle.testevents.TestEventBatchToStore; import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; @@ -193,7 +192,7 @@ public void testMultiPageMessageBatch() throws CradleStorageException { private void verifyEventBatch(TestEventBatchToStore actual, TestEventBatchToStore expected) { - assertEquals(actual.getTestEventsCount(), expected.getTestEventsCount()); + assertEquals(actual.getTestEvents().size(), expected.getTestEvents().size()); assertEquals(actual.getId(), expected.getId()); assertEquals(actual.getName(), expected.getName()); assertEquals(actual.getParentId(), expected.getParentId()); @@ -215,8 +214,8 @@ private TestEventSingleToStore createEvent(String name, Instant start, Instant e } private void verifyTestEvent(TestEventBatchToStore actual, TestEventBatchToStore expected, String name, Instant expectedTimestamp) { - BatchedStoredTestEvent aEvent = actual.getTestEvents().stream().filter((e) -> name.equals(e.getName())).findFirst().get(); - BatchedStoredTestEvent eEvent = expected.getTestEvents().stream().filter((e) -> name.equals(e.getName())).findFirst().get(); + TestEventSingleToStore aEvent = actual.getTestEvents().stream().filter((e) -> name.equals(e.getName())).findFirst().orElseThrow(); + TestEventSingleToStore eEvent = expected.getTestEvents().stream().filter((e) -> name.equals(e.getName())).findFirst().orElseThrow(); StoredTestEventId aId = aEvent.getId(); StoredTestEventId eId = eEvent.getId(); @@ -224,8 +223,9 @@ private void verifyTestEvent(TestEventBatchToStore actual, TestEventBatchToStore assertEquals(aId.getScope(), eId.getScope()); assertEquals(aId.getStartTimestamp(), expectedTimestamp == null ? eId.getStartTimestamp() : expectedTimestamp); - assertEquals(aEvent.getBatchId(), eEvent.getBatchId()); + assertEquals(actual.getId(), expected.getId()); assertEquals(aEvent.getType(), eEvent.getType()); + // FIXME: parent event isn't checked assertEquals(aEvent.getParentId(), aEvent.getParentId()); assertEquals(aEvent.isSuccess(), eEvent.isSuccess()); assertEquals(aEvent.getContent(), eEvent.getContent()); @@ -239,38 +239,36 @@ public void testMultiPageEventBatch() throws CradleStorageException { BookId bookId = new BookId(BOOK); StoredTestEventId batchId = new StoredTestEventId(bookId, "test-scope", PAGE1_START, "batch-id"); - TestEventBatchToStore batch = new TestEventBatchToStoreBuilder(1_000_000, storeActionRejectionThreshold) + TestEventBatchToStoreBuilder batchBuilder = new TestEventBatchToStoreBuilder(1_000_000, storeActionRejectionThreshold) .id(batchId) - .name("test-batch") - .parentId(new StoredTestEventId(bookId, "test-scope", PAGE1_START, "batch-parent-id")) - .type("batch-type") - .build(); + .parentId(new StoredTestEventId(bookId, "test-scope", PAGE1_START, "batch-parent-id")); // page1 - TestEventSingleToStore e1 = createEvent("evt-1", PAGE1_START.plusMillis(10), null, batch.getParentId(), true); - batch.addTestEvent(e1); + TestEventSingleToStore e1 = createEvent("evt-1", PAGE1_START.plusMillis(10), null, batchBuilder.getParentId(), true); + batchBuilder.addTestEvent(e1); TestEventSingleToStore e2 = createEvent("evt-2", PAGE1_START.plusMillis(15), PAGE1_START.plusSeconds(2), e1.getId(), false); - batch.addTestEvent(e2); + batchBuilder.addTestEvent(e2); // page2 - TestEventSingleToStore e3 = createEvent("evt-3", PAGE2_START.plusMillis(20), PAGE3_START.plusSeconds(5), batch.getParentId(), true); - batch.addTestEvent(e3); + TestEventSingleToStore e3 = createEvent("evt-3", PAGE2_START.plusMillis(20), PAGE3_START.plusSeconds(5), batchBuilder.getParentId(), true); + batchBuilder.addTestEvent(e3); TestEventSingleToStore e4 = createEvent("evt-4", PAGE2_START.plusMillis(20), null, e1.getId(), true); - batch.addTestEvent(e4); + batchBuilder.addTestEvent(e4); // page3 - TestEventSingleToStore e5 = createEvent("evt-5", PAGE3_START.plusMillis(30), PAGE3_START.plusSeconds(2), batch.getParentId(), true); - batch.addTestEvent(e5); + TestEventSingleToStore e5 = createEvent("evt-5", PAGE3_START.plusMillis(30), PAGE3_START.plusSeconds(2), batchBuilder.getParentId(), true); + batchBuilder.addTestEvent(e5); TestEventSingleToStore e6 = createEvent("evt-6", PAGE3_START.plusMillis(130), null, e4.getId(), false); - batch.addTestEvent(e6); + batchBuilder.addTestEvent(e6); TestEventSingleToStore e7 = createEvent("evt-7", PAGE3_START.plusMillis(230), null, e5.getId(), true); - batch.addTestEvent(e7); + batchBuilder.addTestEvent(e7); + TestEventBatchToStore batch = batchBuilder.build(); TestEventBatchToStore alignedBatch = (TestEventBatchToStore) storage.alignEventTimestampsToPage(batch, storage.findPage(BOOK_ID, e1.getStartTimestamp())); verifyEventBatch(alignedBatch, batch); Instant end = PAGE2_START.minusNanos(1); @@ -290,38 +288,36 @@ public void testSinglePageEventBatch() throws CradleStorageException { BookId bookId = new BookId(BOOK); StoredTestEventId batchId = new StoredTestEventId(bookId, "test-scope", PAGE1_START, "batch-id"); - TestEventBatchToStore batch = new TestEventBatchToStoreBuilder(1_000_000, storeActionRejectionThreshold) + TestEventBatchToStoreBuilder batchBuilder = new TestEventBatchToStoreBuilder(1_000_000, storeActionRejectionThreshold) .id(batchId) - .name("test-batch") - .parentId(new StoredTestEventId(bookId, "test-scope", PAGE1_START, "batch-parent-id")) - .type("batch-type") - .build(); + .parentId(new StoredTestEventId(bookId, "test-scope", PAGE1_START, "batch-parent-id")); // page1 - TestEventSingleToStore e1 = createEvent("evt-1", PAGE1_START.plusMillis(10), null, batch.getParentId(), true); - batch.addTestEvent(e1); + TestEventSingleToStore e1 = createEvent("evt-1", PAGE1_START.plusMillis(10), null, batchBuilder.getParentId(), true); + batchBuilder.addTestEvent(e1); TestEventSingleToStore e2 = createEvent("evt-2", PAGE1_START.plusMillis(15), PAGE1_START.plusSeconds(2), e1.getId(), false); - batch.addTestEvent(e2); + batchBuilder.addTestEvent(e2); // page2 - TestEventSingleToStore e3 = createEvent("evt-3", PAGE1_START.plusMillis(30), PAGE3_START.plusSeconds(5), batch.getParentId(), true); - batch.addTestEvent(e3); + TestEventSingleToStore e3 = createEvent("evt-3", PAGE1_START.plusMillis(30), PAGE3_START.plusSeconds(5), batchBuilder.getParentId(), true); + batchBuilder.addTestEvent(e3); TestEventSingleToStore e4 = createEvent("evt-4", PAGE1_START.plusMillis(32), null, e1.getId(), true); - batch.addTestEvent(e4); + batchBuilder.addTestEvent(e4); // page3 - TestEventSingleToStore e5 = createEvent("evt-5", PAGE1_START.plusMillis(190), PAGE3_START.plusSeconds(2), batch.getParentId(), true); - batch.addTestEvent(e5); + TestEventSingleToStore e5 = createEvent("evt-5", PAGE1_START.plusMillis(190), PAGE3_START.plusSeconds(2), batchBuilder.getParentId(), true); + batchBuilder.addTestEvent(e5); TestEventSingleToStore e6 = createEvent("evt-6", PAGE1_START.plusMillis(130), null, e4.getId(), false); - batch.addTestEvent(e6); + batchBuilder.addTestEvent(e6); TestEventSingleToStore e7 = createEvent("evt-7", PAGE1_START.plusMillis(230), null, e5.getId(), true); - batch.addTestEvent(e7); + batchBuilder.addTestEvent(e7); + TestEventBatchToStore batch = batchBuilder.build(); TestEventBatchToStore alignedBatch = (TestEventBatchToStore) storage.alignEventTimestampsToPage(batch, storage.findPage(BOOK_ID, e1.getStartTimestamp())); verifyEventBatch(alignedBatch, batch); verifyTestEvent(alignedBatch, batch, "evt-1", null); diff --git a/cradle-core/src/test/java/com/exactpro/cradle/serialization/version1/EventMessageIdCodecTest.java b/cradle-core/src/test/java/com/exactpro/cradle/serialization/version1/EventMessageIdCodecTest.java new file mode 100644 index 000000000..bd92d560a --- /dev/null +++ b/cradle-core/src/test/java/com/exactpro/cradle/serialization/version1/EventMessageIdCodecTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.serialization.version1; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventSingleToStore; +import com.exactpro.cradle.utils.CradleStorageException; +import org.apache.commons.codec.DecoderException; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.Instant; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; +import static com.exactpro.cradle.Direction.FIRST; +import static com.exactpro.cradle.Direction.SECOND; +import static com.exactpro.cradle.serialization.version1.EventMessageIdDeserializer.deserializeBatchLinkedMessageIds; +import static com.exactpro.cradle.serialization.version1.EventMessageIdDeserializer.deserializeLinkedMessageIds; +import static com.exactpro.cradle.serialization.version1.EventMessageIdSerializer.serializeBatchLinkedMessageIds; +import static com.exactpro.cradle.serialization.version1.EventMessageIdSerializer.serializeLinkedMessageIds; +import static org.apache.commons.codec.binary.Hex.decodeHex; +import static org.apache.commons.codec.binary.Hex.encodeHexString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class EventMessageIdCodecTest { + + private static final BookId BOOK_ID = new BookId("test-book"); + private static final String SCOPE = "test-scope"; + public static final String SERIALIZED_EVENTS = "01020000000200020014746573742D73657373696F6E2D616C6961732D3100000" + + "014746573742D73657373696F6E2D616C6961732D320001000A746573742D73636F70650000000065F0EC8000000000000974657" + + "3742D69642D3100000002000001000000010000000065F0EC8000000000000000000000000102000000010000000065F0EC80000" + + "00000000000000000000200000A746573742D73636F70650000000065F0EC80000000000009746573742D69642D3200000002000" + + "101000000010000000065F0EC8000000000000000000000000302000000010000000065F0EC8000000000000000000000000400"; + public static final String SERIALIZED_MESSAGE_IDS = "0101000000040014746573742d73657373696f6e2d616c6961732d310100" + + "0000010000000065f0ec8000000000000000000000000102000000010000000065f0ec8000000000000000000000000200001474" + + "6573742d73657373696f6e2d616c6961732d3201000000010000000065f0ec800000000000000000000000030200000001000000" + + "0065f0ec8000000000000000000000000400"; + public static final String SERIALIZED_MESSAGE_ID = "0101000000010014746573742d73657373696f6e2d616c6961732d3101000" + + "0000065f0ec80000000000000000000000001"; + + @Test + public void testSerializeBatchLinkedMessageIds() throws CradleStorageException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + List source = List.of( + TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, timestamp, "test-id-1") + .name("test-event") + .message(new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1)) + .message(new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2)) + .build(), + TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, timestamp, "test-id-2") + .name("test-event") + .message(new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3)) + .message(new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4)) + .build() + ); + + byte[] bytes = serializeBatchLinkedMessageIds(source); + assertNotNull(bytes); + // Result can't be checked because TestEventSingleToStore class uses hash set to hold StoredMessageId + assertThat(deserializeBatchLinkedMessageIds(bytes, BOOK_ID)).usingRecursiveComparison() + .isEqualTo(source.stream().collect(Collectors.toMap( + TestEventSingleToStore::getId, + TestEventSingleToStore::getMessages + ))); + + } + + @Test + public void testDeserializeBatchLinkedMessageIds() throws DecoderException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Map> target = Map.of( + new StoredTestEventId(BOOK_ID, SCOPE, timestamp, "test-id-1"), Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1), + new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2) + ), + new StoredTestEventId(BOOK_ID, SCOPE, timestamp, "test-id-2"), Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3), + new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4) + ) + ); + + assertThat(deserializeBatchLinkedMessageIds(decodeHex(SERIALIZED_EVENTS), BOOK_ID)) + .usingRecursiveComparison().isEqualTo(target); + } + + @Test + public void testSerializeLinkedMessageIds() throws IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set source = new LinkedHashSet<>(); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1)); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2)); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3)); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4)); + + byte[] bytes = serializeLinkedMessageIds(source); + assertEquals(encodeHexString(bytes), SERIALIZED_MESSAGE_IDS); + } + + @Test + public void testDeserializeLinkedMessageIds() throws DecoderException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set target = Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1), + new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2), + new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3), + new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4) + ); + + assertThat(deserializeLinkedMessageIds(decodeHex(SERIALIZED_MESSAGE_IDS), BOOK_ID)) + .usingRecursiveComparison().isEqualTo(target); + } + + @Test + public void testSerializeLinkedMessageId() throws IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set source = Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1) + ); + + byte[] bytes = serializeLinkedMessageIds(source); + assertEquals(encodeHexString(bytes), SERIALIZED_MESSAGE_ID); + } + + @Test + public void testDeserializeLinkedMessageId() throws DecoderException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set target = Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1) + ); + + assertThat(deserializeLinkedMessageIds(decodeHex(SERIALIZED_MESSAGE_ID), BOOK_ID)) + .usingRecursiveComparison().isEqualTo(target); + } +} \ No newline at end of file diff --git a/cradle-core/src/test/java/com/exactpro/cradle/serialization/version2/EventMessageIdCodecTest.java b/cradle-core/src/test/java/com/exactpro/cradle/serialization/version2/EventMessageIdCodecTest.java new file mode 100644 index 000000000..84c434692 --- /dev/null +++ b/cradle-core/src/test/java/com/exactpro/cradle/serialization/version2/EventMessageIdCodecTest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * 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 com.exactpro.cradle.serialization.version2; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.messages.StoredMessageId; +import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventSingleToStore; +import com.exactpro.cradle.utils.CradleStorageException; +import org.apache.commons.codec.DecoderException; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; +import static com.exactpro.cradle.Direction.FIRST; +import static com.exactpro.cradle.Direction.SECOND; +import static com.exactpro.cradle.serialization.version2.EventMessageIdDeserializer.deserializeBatchLinkedMessageIds; +import static com.exactpro.cradle.serialization.version2.EventMessageIdDeserializer.deserializeLinkedMessageIds; +import static com.exactpro.cradle.serialization.version2.EventMessageIdSerializer.serializeBatchLinkedMessageIds; +import static com.exactpro.cradle.serialization.version2.EventMessageIdSerializer.serializeLinkedMessageIds; +import static org.apache.commons.codec.binary.Hex.decodeHex; +import static org.apache.commons.codec.binary.Hex.encodeHexString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class EventMessageIdCodecTest { + + private static final BookId BOOK_ID = new BookId("test-book"); + private static final String SCOPE = "test-scope"; + public static final String SERIALIZED_EVENTS = "0202000200020014746573742d73657373696f6e2d616c6961732d31000100147" + + "46573742d73657373696f6e2d616c6961732d3200020000000065f0ec80000000000009746573742d69642d31000200010200000" + + "00065f0ec800000000000000000000000020001010000000065f0ec800000000000000000000000010000000065f0ec800000000" + + "00009746573742d69642d3200020002020000000065f0ec800000000000000000000000040002010000000065f0ec80000000000" + + "000000000000003"; + public static final String SERIALIZED_MESSAGE_IDS = "0201000400020014746573742d73657373696f6e2d616c6961732d310001" + + "0014746573742d73657373696f6e2d616c6961732d3200020001010000000065f0ec800000000000000000000000010001020000" + + "000065f0ec800000000000000000000000020002010000000065f0ec800000000000000000000000030002020000000065f0ec80" + + "000000000000000000000004"; + public static final String SERIALIZED_MESSAGE_ID = "020100010014746573742d73657373696f6e2d616c6961732d31010000000" + + "065f0ec80000000000000000000000001"; + + @Test + public void testSerializeBatchLinkedMessageIds() throws CradleStorageException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + List source = List.of( + TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, timestamp, "test-id-1") + .name("test-event") + .message(new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1)) + .message(new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2)) + .build(), + TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(BOOK_ID, SCOPE, timestamp, "test-id-2") + .name("test-event") + .message(new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3)) + .message(new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4)) + .build() + ); + + ByteBuffer buffer = serializeBatchLinkedMessageIds(source); + assertNotNull(buffer); + assertEquals(buffer.position(), 0); + assertEquals(buffer.capacity(), buffer.limit()); + // Result can't be checked because TestEventSingleToStore class uses hash set to hold StoredMessageId + assertThat(deserializeBatchLinkedMessageIds(buffer, BOOK_ID, SCOPE)).usingRecursiveComparison() + .isEqualTo(source.stream().collect(Collectors.toMap( + TestEventSingleToStore::getId, + TestEventSingleToStore::getMessages + ))); + + } + + @Test + public void testDeserializeBatchLinkedMessageIds() throws DecoderException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Map> target = Map.of( + new StoredTestEventId(BOOK_ID, SCOPE, timestamp, "test-id-1"), Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1), + new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2) + ), + new StoredTestEventId(BOOK_ID, SCOPE, timestamp, "test-id-2"), Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3), + new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4) + ) + ); + + assertThat(deserializeBatchLinkedMessageIds(ByteBuffer.wrap(decodeHex(SERIALIZED_EVENTS)), BOOK_ID, SCOPE)) + .usingRecursiveComparison().isEqualTo(target); + } + + @Test + public void testSerializeLinkedMessageIds() { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set source = new LinkedHashSet<>(); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1)); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2)); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3)); + source.add(new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4)); + + ByteBuffer buffer = serializeLinkedMessageIds(source); + assertEquals(buffer.position(), 0); + assertEquals(buffer.capacity(), buffer.limit()); + assertEquals(encodeHexString(buffer.array()), SERIALIZED_MESSAGE_IDS); + } + + @Test + public void testDeserializeLinkedMessageIds() throws DecoderException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set target = Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1), + new StoredMessageId(BOOK_ID, "test-session-alias-1", SECOND, timestamp, 2), + new StoredMessageId(BOOK_ID, "test-session-alias-2", FIRST, timestamp, 3), + new StoredMessageId(BOOK_ID, "test-session-alias-2", SECOND, timestamp, 4) + ); + + assertThat(deserializeLinkedMessageIds(ByteBuffer.wrap(decodeHex(SERIALIZED_MESSAGE_IDS)), BOOK_ID)) + .usingRecursiveComparison().isEqualTo(target); + } + + @Test + public void testSerializeLinkedMessageId() { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set source = Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1) + ); + + ByteBuffer buffer = serializeLinkedMessageIds(source); + assertEquals(buffer.position(), 0); + assertEquals(buffer.capacity(), buffer.limit()); + assertEquals(encodeHexString(buffer.array()), SERIALIZED_MESSAGE_ID); + } + + @Test + public void testDeserializeLinkedMessageId() throws DecoderException, IOException { + Instant timestamp = Instant.parse("2024-03-13T00:00:00Z"); + Set target = Set.of( + new StoredMessageId(BOOK_ID, "test-session-alias-1", FIRST, timestamp, 1) + ); + + assertThat(deserializeLinkedMessageIds(ByteBuffer.wrap(decodeHex(SERIALIZED_MESSAGE_ID)), BOOK_ID)) + .usingRecursiveComparison().isEqualTo(target); + } +} diff --git a/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBatchTest.java b/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBatchTest.java index f573b83e1..6dc2675ee 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBatchTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBatchTest.java @@ -42,6 +42,7 @@ import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import java.util.stream.Stream; import static com.exactpro.cradle.testevents.EventSingleTest.BOOK; @@ -53,6 +54,8 @@ import static com.exactpro.cradle.testevents.EventSingleTest.batchParentId; import static com.exactpro.cradle.testevents.EventSingleTest.validEvent; import static java.time.temporal.ChronoUnit.NANOS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; public class EventBatchTest { private final int MAX_SIZE = 1024; @@ -60,16 +63,14 @@ public class EventBatchTest { private final long storeActionRejectionThreshold = new CoreStorageSettings().calculateStoreActionRejectionThreshold(); private final TestEventSingleToStoreBuilder eventBuilder = new TestEventSingleToStoreBuilder(storeActionRejectionThreshold); - private final TestEventBatchToStoreBuilder batchBuilder = new TestEventBatchToStoreBuilder(MAX_SIZE, storeActionRejectionThreshold); private final StoredTestEventId batchId = new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, UUID.randomUUID().toString()); - private TestEventBatchToStore batch; + private TestEventBatchToStoreBuilder batchBuilder; @BeforeMethod - public void prepareBatch() throws CradleStorageException { - batch = batchBuilder + public void prepareBatch() { + batchBuilder = new TestEventBatchToStoreBuilder(MAX_SIZE, storeActionRejectionThreshold) .id(batchId) - .parentId(batchParentId) - .build(); + .parentId(batchParentId); } @DataProvider(name = "batch invalid events") @@ -100,39 +101,38 @@ public Object[][] batchInvalidEvents() { @Test public void batchFields() throws CradleStorageException { - TestEventBatchToStore event = new TestEventBatchToStoreBuilder(MAX_SIZE, storeActionRejectionThreshold) + TestEventBatchToStoreBuilder eventBuilder = new TestEventBatchToStoreBuilder(MAX_SIZE, storeActionRejectionThreshold) .id(DUMMY_ID) - .name("Name1") - .parentId(batchParentId) - .type("Type1") - .build(); + .parentId(batchParentId); Set messages1 = Collections.singleton(new StoredMessageId(BOOK, "Session1", Direction.FIRST, Instant.EPOCH, 1)); - event.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP.plusMillis(3500), ID_VALUE) + eventBuilder.addTestEvent(this.eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP.plusMillis(3500), ID_VALUE) .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .endTimestamp(START_TIMESTAMP.plusMillis(5000)) .messages(messages1) .success(true) .build()); Set messages2 = Collections.singleton(new StoredMessageId(BOOK, "Session2", Direction.SECOND, Instant.EPOCH, 2)); - event.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, ID_VALUE + "1") + eventBuilder.addTestEvent(this.eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, ID_VALUE + "1") .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .messages(messages2) .success(false) .build()); - event.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, ID_VALUE + "2") + eventBuilder.addTestEvent(this.eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, ID_VALUE + "2") .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .success(false) .build()); - StoredTestEventBatch stored = new StoredTestEventBatch(event, null); + // FIXME: correct + TestEventBatchToStore event = eventBuilder.build(); +// StoredTestEventBatch stored = new StoredTestEventBatch(event, null); - EventTestUtils.assertEvents(stored, event); +// EventTestUtils.assertEvents(stored, event); } @Test @@ -146,24 +146,25 @@ public void passedBatchEvent() throws CradleStorageException { .content("Test content".getBytes()) .build(); - TestEventBatchToStore batch = new TestEventBatchToStore(batchId, null, parentId, MAX_SIZE, storeActionRejectionThreshold); - batch.addTestEvent(event); - TestEventUtils.validateTestEvent(batch, storeActionRejectionThreshold); + TestEventBatchToStoreBuilder batchBuilder = TestEventBatchToStore.batchBuilder(MAX_SIZE, storeActionRejectionThreshold) + .id(batchId) + .parentId(parentId); + batchBuilder.addTestEvent(event); } - @Test(expectedExceptions = {CradleStorageException.class}, expectedExceptionsMessageRegExp = "Batch must have a parent") + @Test(expectedExceptions = {CradleStorageException.class}, expectedExceptionsMessageRegExp = "Parent event id can't be null") public void batchParentMustBeSet() throws CradleStorageException { - batchBuilder.id(DUMMY_ID).build(); + new TestEventBatchToStoreBuilder(MAX_SIZE, storeActionRejectionThreshold).id(DUMMY_ID).build(); } @Test(expectedExceptions = {CradleStorageException.class}, expectedExceptionsMessageRegExp = "Batch has not enough space to hold given test event") public void batchContentIsLimited() throws CradleStorageException { byte[] content = new byte[5000]; for (int i = 0; i <= (MAX_SIZE / content.length) + 1; i++) - batch.addTestEvent(eventBuilder + batchBuilder.addTestEvent(eventBuilder .id(new StoredTestEventId(BOOK, SCOPE, Instant.EPOCH, Integer.toString(i))) .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .content(content) .build()); } @@ -171,18 +172,18 @@ public void batchContentIsLimited() throws CradleStorageException { @Test public void batchCountsSpaceLeft() throws CradleStorageException { byte[] content = new byte[MAX_SIZE / 2]; - long left = batch.getSpaceLeft(); + long left = batchBuilder.getSpaceLeft(); TestEventSingleToStore event = eventBuilder .id(new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "1")) .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .content(content) .build(); - batch.addTestEvent(event); + batchBuilder.addTestEvent(event); - Assert.assertEquals(batch.getSpaceLeft(), left - EventsSizeCalculator.calculateRecordSizeInBatch(event), "Batch counts space left"); + Assert.assertEquals(batchBuilder.getSpaceLeft(), left - EventsSizeCalculator.calculateRecordSizeInBatch(event), "Batch counts space left"); } @Test @@ -192,36 +193,36 @@ public void batchChecksSpaceLeft() throws CradleStorageException { TestEventSingleToStore event = eventBuilder .id(new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "1")) .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .content(content) .build(); - batch.addTestEvent(event); - Assert.assertFalse(batch.hasSpace(event), "Batch shows if it has space to hold given test event"); + batchBuilder.addTestEvent(event); + assertFalse(batchBuilder.hasSpace(event), "Batch shows if it has space to hold given test event"); } @Test(expectedExceptions = {CradleStorageException.class}, expectedExceptionsMessageRegExp = "Test event with ID .* is already present in batch") public void duplicateIds() throws CradleStorageException { StoredTestEventId eventId = new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "AAA"); - batch.addTestEvent(eventBuilder.id(eventId).name(DUMMY_NAME).parentId(batch.getParentId()).build()); - batch.addTestEvent(eventBuilder.id(eventId).name(DUMMY_NAME).parentId(batch.getParentId()).build()); + batchBuilder.addTestEvent(eventBuilder.id(eventId).name(DUMMY_NAME).parentId(batchBuilder.getParentId()).build()); + batchBuilder.addTestEvent(eventBuilder.id(eventId).name(DUMMY_NAME).parentId(batchBuilder.getParentId()).build()); } @Test(expectedExceptions = {CradleStorageException.class}, expectedExceptionsMessageRegExp = ".* '.*:XXX' .* stored in this batch .*") public void externalReferences() throws CradleStorageException { StoredTestEventId parentId = new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "1"); - batch.addTestEvent(eventBuilder.id(parentId) + batchBuilder.addTestEvent(eventBuilder.id(parentId) .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .build()); - batch.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "2") + batchBuilder.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "2") .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .build()); - batch.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "3") + batchBuilder.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "3") .name(DUMMY_NAME) .parentId(parentId) .build()); - batch.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "4") + batchBuilder.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "4") .name(DUMMY_NAME) .parentId(new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "XXX")) .build()); @@ -230,66 +231,44 @@ public void externalReferences() throws CradleStorageException { @Test(expectedExceptions = {CradleStorageException.class}, expectedExceptionsMessageRegExp = ".* stored in this batch .*") public void referenceToBatch() throws CradleStorageException { StoredTestEventId parentId = new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "1"); - batch.addTestEvent(eventBuilder.id(parentId) + batchBuilder.addTestEvent(eventBuilder.id(parentId) .name(DUMMY_NAME) - .parentId(batch.getParentId()) + .parentId(batchBuilder.getParentId()) .build()); - batch.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "2") + batchBuilder.addTestEvent(eventBuilder.id(BOOK, SCOPE, START_TIMESTAMP, "2") .name(DUMMY_NAME) - .parentId(batch.getId()) + .parentId(batchBuilder.getId()) .build()); } @Test - public void childrenAligned() throws CradleStorageException { - BatchedStoredTestEvent parentEvent = batch.addTestEvent(eventBuilder - .id(BOOK, SCOPE, START_TIMESTAMP, "1") - .name(DUMMY_NAME) - .parentId(batch.getParentId()) - .build()), - childEvent = batch.addTestEvent(eventBuilder - .id(BOOK, SCOPE, START_TIMESTAMP, "2") + public void eventsAdded() throws CradleStorageException { + StoredTestEventId parentId = new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "1"); + StoredTestEventId childId = new StoredTestEventId(BOOK, SCOPE, START_TIMESTAMP, "2"); + TestEventBatchToStore batch = this.batchBuilder.addTestEvent(eventBuilder + .id(parentId) .name(DUMMY_NAME) - .parentId(parentEvent.getId()) - .build()); - - Assert.assertTrue(parentEvent.getChildren().contains(childEvent), "Children are aligned with their parent"); - } - - @Test - public void rootIsRoot() throws CradleStorageException { - BatchedStoredTestEvent parentEvent = batch.addTestEvent(eventBuilder - .id(DUMMY_ID) - .name(DUMMY_NAME) - .parentId(batch.getParentId()) - .build()); - Assert.assertTrue(batch.getRootTestEvents().contains(parentEvent), "Root event is listed in roots"); - } - - @Test - public void childIsNotRoot() throws CradleStorageException { - BatchedStoredTestEvent parentEvent = batch.addTestEvent(eventBuilder - .id(BOOK, SCOPE, START_TIMESTAMP, "1") - .name(DUMMY_NAME) - .parentId(batch.getParentId()) - .build()), - childEvent = batch.addTestEvent(eventBuilder - .id(BOOK, SCOPE, START_TIMESTAMP, "2") + .parentId(this.batchBuilder.getParentId()) + .build()) + .addTestEvent(eventBuilder + .id(childId) .name(DUMMY_NAME) - .parentId(parentEvent.getId()) - .build()); + .parentId(parentId) + .build()) + .build(); - Assert.assertFalse(batch.getRootTestEvents().contains(childEvent), "Child event is not listed in roots"); + assertEquals(List.of(parentId, childId), batch.getTestEvents().stream().map(TestEventSingleToStore::getId).collect(Collectors.toList())); } + @Test(dataProvider = "batch invalid events", expectedExceptions = {CradleStorageException.class}) public void batchEventValidation(TestEventSingleToStoreBuilder builder, String errorMessage) throws CradleStorageException { try { var singleEvent = builder.build(); BookInfo bookInfo = createBookInfo(); - TestEventUtils.validateTestEvent(singleEvent, bookInfo, storeActionRejectionThreshold); - batch.addTestEvent(singleEvent); + TestEventUtils.validateTestEvent(singleEvent, bookInfo); + batchBuilder.addTestEvent(singleEvent); Assertions.fail("Invalid message passed validation"); } catch (CradleStorageException e) { TestUtils.handleException(e, errorMessage); @@ -315,28 +294,31 @@ private static BookInfo createBookInfo() { @Test public void batchEventMessagesAreIndependent() throws CradleStorageException { - TestEventSingleToStore event = validEvent().success(true).message(new StoredMessageId(BOOK, "Session1", Direction.FIRST, START_TIMESTAMP, 1)).build(); - BatchedStoredTestEvent stored = batch.addTestEvent(event); + StoredMessageId id = new StoredMessageId(BOOK, "Session1", Direction.FIRST, START_TIMESTAMP, 1); + TestEventSingleToStoreBuilder eventBuilder = validEvent().success(true).message(id); + + TestEventSingleToStore event = eventBuilder.build(); StoredMessageId newMessage = new StoredMessageId(BOOK, "Session2", Direction.SECOND, START_TIMESTAMP, 2); - event.getMessages().add(newMessage); + eventBuilder.message(newMessage); - Assert.assertFalse(stored.getMessages().contains(newMessage), "messages in batched event contain new message"); + assertFalse(event.getMessages().contains(newMessage), "messages in batched event contain new message"); } @Test public void storedBatchIsIndependent() throws CradleStorageException { Instant end = START_TIMESTAMP.plusMillis(5000); - TestEventSingleToStore event = validEvent() + + TestEventSingleToStoreBuilder eventBuilder = validEvent() .success(true) .endTimestamp(end) - .message(new StoredMessageId(BOOK, "Session1", Direction.FIRST, START_TIMESTAMP, 1)) - .build(); - batch.addTestEvent(event); - StoredTestEventBatch stored = StoredTestEvent.batch(batch, null); + .message(new StoredMessageId(BOOK, "Session1", Direction.FIRST, START_TIMESTAMP, 1)); + TestEventSingleToStore event1 = eventBuilder.build(); + batchBuilder.addTestEvent(event1); + TestEventBatchToStore batch = batchBuilder.build(); StoredMessageId newMessage = new StoredMessageId(BOOK, "Session2", Direction.SECOND, START_TIMESTAMP, 2); - event.getMessages().add(newMessage); + eventBuilder.message(newMessage); StoredMessageId newMessage2 = new StoredMessageId(BOOK, "Session3", Direction.FIRST, START_TIMESTAMP, 3); TestEventSingleToStore event2 = validEvent() @@ -345,14 +327,13 @@ public void storedBatchIsIndependent() throws CradleStorageException { .endTimestamp(end.plusMillis(1000)) .message(newMessage2) .build(); - batch.addTestEvent(event2); + batchBuilder.addTestEvent(event2); SoftAssert soft = new SoftAssert(); - soft.assertNull(stored.getTestEvent(event2.getId()), "event added to batch after storing"); - soft.assertFalse(stored.getMessages().contains(newMessage), "messages in stored event contain new message"); - soft.assertFalse(stored.getMessages().contains(newMessage2), "messages in stored event contain new message 2"); - soft.assertTrue(stored.isSuccess()); - soft.assertEquals(stored.getEndTimestamp(), end); + soft.assertEquals(batch.getTestEvents().size(), 1); + soft.assertSame(event1, batch.getTestEvents().iterator().next()); + soft.assertTrue(batch.isSuccess()); + soft.assertEquals(batch.getEndTimestamp(), end); soft.assertAll(); } } diff --git a/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBuilderTest.java b/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBuilderTest.java index 1fbab85bc..f6e97b8f7 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBuilderTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,16 @@ import com.exactpro.cradle.Direction; import com.exactpro.cradle.messages.StoredMessageId; import com.exactpro.cradle.utils.CradleStorageException; -import org.assertj.core.api.Assertions; import org.testng.annotations.Test; import java.time.Instant; +import java.util.List; + +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; public class EventBuilderTest { private final BookId bookId = new BookId("Book1"); @@ -36,7 +42,7 @@ public void singleBuilderIsReset() throws CradleStorageException { TestEventSingleToStoreBuilder builder = new TestEventSingleToStoreBuilder(storeActionRejectionThreshold); builder.id(bookId, "Scope1", Instant.now(), "123") .name("Event1") - .parentId(new StoredTestEventId(bookId, "Scope2", Instant.EPOCH, "234")) + .parentId(new StoredTestEventId(bookId, "Scope1", Instant.EPOCH, "234")) .type("Type1") .success(true) .endTimestamp(Instant.now()) @@ -44,7 +50,7 @@ public void singleBuilderIsReset() throws CradleStorageException { .content("Dummy event".getBytes()) .build(); - Assertions.assertThat(builder) + assertThat(builder) .usingRecursiveComparison() .isEqualTo(new TestEventSingleToStoreBuilder(storeActionRejectionThreshold)); } @@ -53,14 +59,40 @@ public void singleBuilderIsReset() throws CradleStorageException { public void batchBuilderIsReset() throws CradleStorageException { int maxSize = 1024; TestEventBatchToStoreBuilder builder = new TestEventBatchToStoreBuilder(maxSize, storeActionRejectionThreshold); + StoredTestEventId parentId = new StoredTestEventId(bookId, "Scope1", Instant.EPOCH, "234"); builder.id(bookId, "Scope1", Instant.now(), "123") - .name("Event1") - .parentId(new StoredTestEventId(bookId, "Scope2", Instant.EPOCH, "234")) - .type("Type1") + .parentId(parentId) + .addTestEvent(TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(bookId, "Scope1", Instant.now(), "456") + .name("test-event") + .parentId(parentId) + .build()) .build(); - Assertions.assertThat(builder) + assertThat(builder) .usingRecursiveComparison() .isEqualTo(new TestEventBatchToStoreBuilder(maxSize, storeActionRejectionThreshold)); } + + @Test + public void batchBuilderTest() throws CradleStorageException { + int maxSize = 1024; + StoredTestEventId parentId = new StoredTestEventId(bookId, "Scope1", Instant.EPOCH, "234"); + TestEventBatchToStoreBuilder builder = new TestEventBatchToStoreBuilder(maxSize, storeActionRejectionThreshold) + .id(bookId, "Scope1", Instant.now(), "123") + .parentId(parentId); + TestEventSingleToStore event = TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS) + .id(bookId, "Scope1", Instant.now(), "456") + .name("test-event") + .parentId(parentId) + .message(new StoredMessageId(bookId, "session-alias", Direction.SECOND, Instant.now(), 1)) + .build(); + TestEventBatchToStore batch = builder.addTestEvent(event) + .build(); + + assertSame(batch.getParentId(), parentId); + assertTrue(batch.isSuccess()); + assertEquals(batch.getTestEvents(), List.of(event)); + assertEquals(batch.getEventsWithAttachedMessages(), List.of(event)); + } } \ No newline at end of file diff --git a/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventSingleTest.java b/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventSingleTest.java index 7da71fab5..9267aa72f 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventSingleTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/testevents/EventSingleTest.java @@ -117,7 +117,7 @@ public void eventFields() throws CradleStorageException { public void eventValidation(TestEventSingleToStoreBuilder builder, String errorMessage) throws CradleStorageException { try { BookInfo bookInfo = createBookInfo(); - TestEventUtils.validateTestEvent(builder.build(), bookInfo, storeActionRejectionThreshold); + TestEventUtils.validateTestEvent(builder.build(), bookInfo); Assertions.fail("Invalid message passed validation"); } catch (CradleStorageException e) { TestUtils.handleException(e, errorMessage); @@ -143,23 +143,20 @@ private static BookInfo createBookInfo() { @Test public void passedEvent() throws CradleStorageException { - TestEventSingleToStore event = eventBuilder - .id(DUMMY_ID) + eventBuilder.id(DUMMY_ID) .name(DUMMY_NAME) .content("Test content".getBytes()) .build(); - TestEventUtils.validateTestEvent(event, storeActionRejectionThreshold); } @Test public void storedEventMessagesAreIndependent() throws CradleStorageException { - TestEventSingleToStore event = validEvent().message(new StoredMessageId(BOOK, "Session1", Direction.FIRST, START_TIMESTAMP, 1)) - .message(new StoredMessageId(BOOK, "Session2", Direction.SECOND, START_TIMESTAMP, 2)) - .build(); - StoredTestEvent stored = StoredTestEvent.single(event, null); + TestEventSingleToStoreBuilder eventBuilder = validEvent().message(new StoredMessageId(BOOK, "Session1", Direction.FIRST, START_TIMESTAMP, 1)) + .message(new StoredMessageId(BOOK, "Session2", Direction.SECOND, START_TIMESTAMP, 2)); + StoredTestEvent stored = StoredTestEvent.single(eventBuilder.build(), null); StoredMessageId newMessage = new StoredMessageId(BOOK, "Session3", Direction.FIRST, START_TIMESTAMP, 3); - event.getMessages().add(newMessage); + eventBuilder.message(newMessage); Assert.assertFalse(stored.getMessages().contains(newMessage), "messages in stored event contain new message"); } } diff --git a/cradle-core/src/test/java/com/exactpro/cradle/utils/SerializationEventBatchTest.java b/cradle-core/src/test/java/com/exactpro/cradle/utils/SerializationEventBatchTest.java index 157063a8f..e4a09f36f 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/utils/SerializationEventBatchTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/utils/SerializationEventBatchTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ import com.exactpro.cradle.serialization.EventBatchSerializer; import com.exactpro.cradle.serialization.SerializationException; import com.exactpro.cradle.testevents.BatchedStoredTestEvent; -import com.exactpro.cradle.testevents.BatchedStoredTestEventBuilder; import com.exactpro.cradle.testevents.StoredTestEventId; +import com.exactpro.cradle.testevents.TestEventSingleToStore; +import com.exactpro.cradle.testevents.TestEventSingleToStoreBuilder; import org.assertj.core.api.Assertions; import org.testng.Assert; import org.testng.annotations.Test; @@ -34,16 +35,19 @@ import java.util.Collection; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +import static com.exactpro.cradle.CoreStorageSettings.DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS; import static com.exactpro.cradle.TestUtils.generateUnicodeString; import static com.exactpro.cradle.serialization.EventsSizeCalculator.calculateBatchEventSize; import static com.exactpro.cradle.serialization.EventsSizeCalculator.calculateEventRecordSize; +import static com.exactpro.cradle.utils.TestEventUtils.toTestEventSingleToStore; public class SerializationEventBatchTest { @Test - public void checkSize1() throws SerializationException { - BatchedStoredTestEvent build = createBatchedStoredTestEvent("Test even1234567890", createCommonParams()); + public void checkSize1() throws CradleStorageException { + TestEventSingleToStore build = createBatchedStoredTestEvent("Test even1234567890", createCommonParams()); EventBatchSerializer serializer = new EventBatchSerializer(); ByteBuffer buffer = ByteBuffer.allocate(10_000); serializer.serializeEventRecord(build, buffer); @@ -51,8 +55,8 @@ public void checkSize1() throws SerializationException { } @Test - public void checkSize2() throws SerializationException { - Collection build = createBatchEvents(); + public void checkSize2() throws CradleStorageException { + Collection build = createBatchEvents(); EventBatchSerializer serializer = new EventBatchSerializer(); ByteBuffer buffer = ByteBuffer.allocate(10_000); serializer.serializeEventBatch(build, buffer); @@ -61,26 +65,33 @@ public void checkSize2() throws SerializationException { @Test - public void serializeDeserialize() throws SerializationException { + public void serializeDeserialize() throws SerializationException, CradleStorageException { EventBatchCommonParams commonParams = createCommonParams(); - BatchedStoredTestEvent build = createBatchedStoredTestEvent("Test even1234567890", commonParams); + TestEventSingleToStore build = createBatchedStoredTestEvent("Test even1234567890", commonParams); EventBatchSerializer serializer = new EventBatchSerializer(); byte[] serialize = serializer.serializeEventRecord(build); EventBatchDeserializer deserializer = new EventBatchDeserializer(); BatchedStoredTestEvent deserialize = deserializer.deserializeBatchEntry(serialize, commonParams); - Assertions.assertThat(deserialize).usingRecursiveComparison().isEqualTo(build); + Assertions.assertThat(toTestEventSingleToStore(deserialize, DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS)) + .usingRecursiveComparison().isEqualTo(build); } @Test public void serializeDeserialize2() throws Exception { EventBatchCommonParams commonParams = createCommonParams(); - List build = createBatchEvents(commonParams); + List build = createBatchEvents(commonParams); EventBatchSerializer serializer = new EventBatchSerializer(); byte[] serialize = serializer.serializeEventBatch(build).getSerializedData(); EventBatchDeserializer deserializer = new EventBatchDeserializer(); List deserialize = deserializer.deserializeBatchEntries(serialize, commonParams); - Assertions.assertThat(build).usingRecursiveFieldByFieldElementComparator().isEqualTo(deserialize); + Assertions.assertThat(deserialize.stream().map(event -> { + try { + return toTestEventSingleToStore(event, DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS); + } catch (CradleStorageException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList())).usingRecursiveFieldByFieldElementComparator().isEqualTo(build); } @Test @@ -88,38 +99,39 @@ public void serializeDeserialize3UnicodeCharacters() throws Exception { EventBatchCommonParams commonParams = createCommonParams(); String name = generateUnicodeString((1 << 18), 50); String content = generateUnicodeString((1 << 19), 10); - BatchedStoredTestEvent build = createBatchedStoredTestEventWithContent(name, commonParams, content); + TestEventSingleToStore build = createBatchedStoredTestEventWithContent(name, commonParams, content); EventBatchSerializer serializer = new EventBatchSerializer(); byte[] serialized = serializer.serializeEventRecord(build); EventBatchDeserializer deserializer = new EventBatchDeserializer(); BatchedStoredTestEvent deserialized = deserializer.deserializeBatchEntry(serialized, commonParams); - Assertions.assertThat(deserialized).usingRecursiveComparison().isEqualTo(build); + Assertions.assertThat(toTestEventSingleToStore(deserialized, DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS)) + .usingRecursiveComparison().isEqualTo(build); } - static BatchedStoredTestEvent createBatchedStoredTestEvent(String name, EventBatchCommonParams commonParams) { + static TestEventSingleToStore createBatchedStoredTestEvent(String name, EventBatchCommonParams commonParams) throws CradleStorageException { return createBatchedStoredTestEventWithContent(name, commonParams, "Message"); } - static BatchedStoredTestEvent createBatchedStoredTestEventWithContent(String name, EventBatchCommonParams commonParams, String content) { - BatchedStoredTestEventBuilder builder = new BatchedStoredTestEventBuilder(); - builder.setSuccess(true); + static TestEventSingleToStore createBatchedStoredTestEventWithContent(String name, EventBatchCommonParams commonParams, String content) throws CradleStorageException { + TestEventSingleToStoreBuilder builder = TestEventSingleToStore.builder(DEFAULT_BOOK_REFRESH_INTERVAL_MILLIS); + builder.success(true); Instant startTime = Instant.parse("2007-12-03T10:15:30.00Z"); String scope = commonParams.getScope(); - builder.setEndTimestamp(Instant.parse("2007-12-03T10:15:31.00Z")); - builder.setId(new StoredTestEventId(commonParams.getBookId(), scope, startTime, UUID.randomUUID().toString())); - builder.setParentId(new StoredTestEventId(commonParams.getBookId(), scope, startTime, UUID.randomUUID().toString())); - builder.setName(name); - builder.setType(name + " ----"); - builder.setContent(content.repeat(10).getBytes(StandardCharsets.UTF_8)); + builder.endTimestamp(Instant.parse("2007-12-03T10:15:31.00Z")); + builder.id(new StoredTestEventId(commonParams.getBookId(), scope, startTime, UUID.randomUUID().toString())); + builder.parentId(new StoredTestEventId(commonParams.getBookId(), scope, startTime, UUID.randomUUID().toString())); + builder.name(name); + builder.type(name + " ----"); + builder.content(content.repeat(10).getBytes(StandardCharsets.UTF_8)); return builder.build(); } - static List createBatchEvents() { + static List createBatchEvents() throws CradleStorageException { return createBatchEvents(createCommonParams()); } - static List createBatchEvents(EventBatchCommonParams commonParams) { - ArrayList objects = new ArrayList<>(3); + static List createBatchEvents(EventBatchCommonParams commonParams) throws CradleStorageException { + ArrayList objects = new ArrayList<>(3); objects.add(createBatchedStoredTestEvent("batch1", commonParams)); objects.add(createBatchedStoredTestEvent("batch2", commonParams)); objects.add(createBatchedStoredTestEvent("batch3", commonParams)); diff --git a/cradle-core/src/test/java/com/exactpro/cradle/utils/TestEventUtilsTest.java b/cradle-core/src/test/java/com/exactpro/cradle/utils/TestEventUtilsTest.java index 60745e72b..d85179fed 100644 --- a/cradle-core/src/test/java/com/exactpro/cradle/utils/TestEventUtilsTest.java +++ b/cradle-core/src/test/java/com/exactpro/cradle/utils/TestEventUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import com.exactpro.cradle.CoreStorageSettings; import com.exactpro.cradle.testevents.StoredTestEventId; import com.exactpro.cradle.testevents.TestEventBatchToStore; +import com.exactpro.cradle.testevents.TestEventBatchToStoreBuilder; import com.exactpro.cradle.testevents.TestEventSingleToStoreBuilder; import org.testng.annotations.Test; @@ -35,11 +36,9 @@ public void toFromBytes() throws CradleStorageException, IOException { StoredTestEventId batchId = new StoredTestEventId(book, scope, Instant.now(), "BatchID"); long storeActionRejectionThreshold = new CoreStorageSettings().calculateStoreActionRejectionThreshold(); - TestEventBatchToStore batch = new TestEventBatchToStore(batchId, - "Batch", - parentId, - 1024, - storeActionRejectionThreshold); + TestEventBatchToStoreBuilder batch = TestEventBatchToStore.batchBuilder(1024, storeActionRejectionThreshold) + .id(batchId) + .parentId(parentId); batch.addTestEvent(new TestEventSingleToStoreBuilder(storeActionRejectionThreshold) .id(book, scope, Instant.now(), "EventID") @@ -47,7 +46,7 @@ public void toFromBytes() throws CradleStorageException, IOException { .parentId(parentId) .build()); - byte[] bytes = TestEventUtils.serializeTestEvents(batch.getTestEvents()).getSerializedData(); + byte[] bytes = TestEventUtils.serializeTestEvents(batch.build()).getSerializedData(); TestEventUtils.deserializeTestEvents(bytes, batchId); } }