All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.jet.pipeline.test.AssertionSinks Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * 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.hazelcast.jet.pipeline.test;

import com.hazelcast.function.ConsumerEx;
import com.hazelcast.jet.pipeline.Sink;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.hazelcast.jet.core.test.JetAssert.assertEquals;
import static com.hazelcast.jet.core.test.JetAssert.assertTrue;
import static com.hazelcast.jet.impl.util.ExceptionUtil.rethrow;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * Various assertions which can be used to assert items on the output of a
 * pipeline.
 * 

* In this class there are variants that can be used as sinks in the pipeline. * Variants that can be used in-line in a pipeline are in {@link Assertions}. * * @since Jet 3.2 */ public final class AssertionSinks { private AssertionSinks() { } /** * Asserts that the previous stage emitted the exact sequence of expected * items and nothing else. If the assertion fails, the job will fail with an * {@link AssertionError} with the given message. *

* Since Jet jobs are distributed, input from multiple upstream processors * is merged in a non-deterministic way. Therefore, this assertion is usable * only for testing of non-distributed sources. */ @Nonnull public static Sink assertOrdered(@Nullable String message, @Nonnull Collection expected) { final List exp = new ArrayList<>(expected); return assertCollected(received -> assertEquals(message, exp, received)); } /** * Asserts that the previous stage emitted the exact sequence of expected * items and nothing else. If the assertion fails, the job will fail with an * {@link AssertionError}. *

* Since Jet jobs are distributed, input from multiple upstream processors * is merged in a non-deterministic way. Therefore, this assertion is usable * only for testing of non-distributed sources. */ @Nonnull public static Sink assertOrdered(@Nonnull Collection expected) { return assertOrdered(null, expected); } /** * Asserts that the previous stage emitted the expected items in any order, * but nothing else. If the assertion fails, the job will fail with an * {@link AssertionError} with the given message. */ @Nonnull public static Sink assertAnyOrder(@Nullable String message, @Nonnull Collection expected) { Map expBag = toBag(expected); return assertCollected(received -> { String msg = "Expected and received did not match. The items are printed in the format of a map as follows:" + " {=}"; assertEquals(message == null ? msg : message + ", " + msg, expBag, toBag(received)); }); } /** * Asserts that the previous stage emitted the expected items in any order, * but nothing else. If the assertion fails, the job will fail with an * {@link AssertionError}. */ @Nonnull public static Sink assertAnyOrder(@Nonnull Collection expected) { return assertAnyOrder(null, expected); } private static Map toBag(Collection coll) { return coll.stream().collect(Collectors.groupingBy(c -> c, Collectors.counting())); } /** * Asserts that the previous stage emitted all the given items in any order. * If the assertion fails, the job will fail with a {@link AssertionError} with * the given message. */ @Nonnull public static Sink assertContains(@Nullable String message, @Nonnull Collection expected) { final HashSet set = new HashSet<>(expected); return AssertionSinkBuilder.assertionSink("assertContains", () -> set) .receiveFn(HashSet::remove) .completeFn(exp -> assertTrue( message + ", the following items have not been observed: " + exp, exp.isEmpty())) .build(); } /** * Collects all the received items in a list and once the upstream stage is * completed it executes the assertion supplied by {@code assertFn}. If no * items were collected, it will be called with empty list. *

* Not usable in streaming jobs - use {@link #assertCollectedEventually}. * * @param assertFn assertion to execute once all items are received */ @Nonnull public static Sink assertCollected(@Nonnull ConsumerEx> assertFn) { return AssertionSinkBuilder.assertionSink("assertCollected", ArrayList::new) .receiveFn(ArrayList::add) .completeFn(assertFn) .build(); } /** * Collects all the received items into a list and runs the {@code assertFn} * every time a new item is received. An {@link AssertionError} thrown from * the {@code assertFn} will be ignored until {@code timeoutSeconds} have * passed, after which the last {@code AssertionError} will be rethrown. * If {@code assertFn} throws any other exception, it will be rethrown * immediately. *

* When {@code assertFn} completes without any error, the sink will throw * an {@link AssertionCompletedException} to indicate success. Exception is * used to terminate the job so that you can {@code join()} it. This also * requires that there are no other assertions in the job as this one can * complete the job before the other ones succeeded. *

* The assertion can be validated as follows: *

{@code
     * try {
     *     jetInstance.newJob(p).join();
     *     Assert.fail("Job should have completed with an AssertionCompletedException, " +
     *             "but completed normally");
     * } catch (CompletionException e) {
     *     String errorMsg = e.getCause().getMessage();
     *     Assert.assertTrue(
     *             "Job was expected to complete with AssertionCompletedException, but completed with: " + e.getCause(),
     *             errorMsg.contains(AssertionCompletedException.class.getName())
     *     );
     * }
* * @param timeoutSeconds timeout in seconds, after which any assertion error will be propagated * @param assertFn assertion to execute periodically */ @Nonnull public static Sink assertCollectedEventually( int timeoutSeconds, @Nonnull ConsumerEx> assertFn ) { return AssertionSinkBuilder .assertionSink("assertCollectedEventually", () -> new CollectingSinkWithTimer<>(assertFn, timeoutSeconds)) .receiveFn(CollectingSinkWithTimer::receive) .timerFn(CollectingSinkWithTimer::timer) .completeFn(CollectingSinkWithTimer::complete) .build(); } private static final class CollectingSinkWithTimer { private final long start = System.nanoTime(); private final List collected = new ArrayList<>(); private ConsumerEx> assertFn; private long timeoutNanos; private AssertionError lastError; CollectingSinkWithTimer(ConsumerEx> assertFn, int timeoutSeconds) { this.assertFn = assertFn; this.timeoutNanos = SECONDS.toNanos(timeoutSeconds); } void receive(T item) { collected.add(item); try { assertFn.accept(collected); throw new AssertionCompletedException("Assertion passed successfully"); } catch (AssertionError e) { lastError = e; } catch (Exception e) { throw rethrow(e); } } void timer() { if (System.nanoTime() - start > timeoutNanos) { throw new AssertionError(lastError); } } void complete() { assertFn.accept(collected); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy