/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.beam.sdk.testing; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.fasterxml.jackson.annotation.JsonCreator; import com.google.common.base.Throwables; import com.google.common.collect.Iterables; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.Collections; import java.util.regex.Pattern; import org.apache.beam.sdk.Pipeline; import org.apache.beam.sdk.coders.AtomicCoder; import org.apache.beam.sdk.coders.CoderException; import org.apache.beam.sdk.coders.SerializableCoder; import org.apache.beam.sdk.coders.VarLongCoder; import org.apache.beam.sdk.io.GenerateSequence; import org.apache.beam.sdk.testing.PAssert.PCollectionContentsAssert.MatcherCheckerFn; import org.apache.beam.sdk.transforms.Create; import org.apache.beam.sdk.transforms.SerializableFunction; import org.apache.beam.sdk.transforms.Sum; import org.apache.beam.sdk.transforms.windowing.FixedWindows; import org.apache.beam.sdk.transforms.windowing.GlobalWindow; import org.apache.beam.sdk.transforms.windowing.IntervalWindow; import org.apache.beam.sdk.transforms.windowing.SlidingWindows; import org.apache.beam.sdk.transforms.windowing.Window; import org.apache.beam.sdk.util.CoderUtils; import org.apache.beam.sdk.util.common.ElementByteSizeObserver; import org.apache.beam.sdk.values.KV; import org.apache.beam.sdk.values.PCollection; import org.apache.beam.sdk.values.TimestampedValue; import org.joda.time.Duration; import org.joda.time.Instant; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Test case for {@link PAssert}. */ @RunWith(JUnit4.class) public class PAssertTest implements Serializable { @Rule public final transient TestPipeline pipeline = TestPipeline.create(); @Rule public transient ExpectedException thrown = ExpectedException.none(); private static class NotSerializableObject { @Override public boolean equals(Object other) { return (other instanceof NotSerializableObject); } @Override public int hashCode() { return 73; } } private static class NotSerializableObjectCoder extends AtomicCoder<NotSerializableObject> { private NotSerializableObjectCoder() { } private static final NotSerializableObjectCoder INSTANCE = new NotSerializableObjectCoder(); @JsonCreator public static NotSerializableObjectCoder of() { return INSTANCE; } @Override public void encode(NotSerializableObject value, OutputStream outStream) throws CoderException, IOException { } @Override public NotSerializableObject decode(InputStream inStream) throws CoderException, IOException { return new NotSerializableObject(); } @Override public boolean isRegisterByteSizeObserverCheap(NotSerializableObject value) { return true; } @Override public void registerByteSizeObserver( NotSerializableObject value, ElementByteSizeObserver observer) throws Exception { observer.update(0L); } } @Test public void testFailureEncodedDecoded() throws IOException { AssertionError error = null; try { assertEquals(0, 1); } catch (AssertionError e) { error = e; } SuccessOrFailure failure = SuccessOrFailure.failure( new PAssert.PAssertionSite(error.getMessage(), error.getStackTrace())); SerializableCoder<SuccessOrFailure> coder = SerializableCoder.of(SuccessOrFailure.class); byte[] encoded = CoderUtils.encodeToByteArray(coder, failure); SuccessOrFailure res = CoderUtils.decodeFromByteArray(coder, encoded); // Should compare strings, because throwables are not directly comparable. assertEquals("Encode-decode failed SuccessOrFailure", failure.assertionError().toString(), res.assertionError().toString()); String resultStacktrace = Throwables.getStackTraceAsString(res.assertionError()); String failureStacktrace = Throwables.getStackTraceAsString(failure.assertionError()); assertThat(resultStacktrace, is(failureStacktrace)); } @Test public void testSuccessEncodedDecoded() throws IOException { SuccessOrFailure success = SuccessOrFailure.success(); SerializableCoder<SuccessOrFailure> coder = SerializableCoder.of(SuccessOrFailure.class); byte[] encoded = CoderUtils.encodeToByteArray(coder, success); SuccessOrFailure res = CoderUtils.decodeFromByteArray(coder, encoded); assertEquals("Encode-decode successful SuccessOrFailure", success.isSuccess(), res.isSuccess()); assertEquals("Encode-decode successful SuccessOrFailure", success.assertionError(), res.assertionError()); } /** * A {@link PAssert} about the contents of a {@link PCollection} * must not require the contents of the {@link PCollection} to be * serializable. */ @Test @Category(ValidatesRunner.class) public void testContainsInAnyOrderNotSerializable() throws Exception { PCollection<NotSerializableObject> pcollection = pipeline .apply(Create.of( new NotSerializableObject(), new NotSerializableObject()) .withCoder(NotSerializableObjectCoder.of())); PAssert.that(pcollection).containsInAnyOrder( new NotSerializableObject(), new NotSerializableObject()); pipeline.run(); } /** * A {@link PAssert} about the contents of a {@link PCollection} * is allows to be verified by an arbitrary {@link SerializableFunction}, * though. */ @Test @Category(ValidatesRunner.class) public void testSerializablePredicate() throws Exception { PCollection<NotSerializableObject> pcollection = pipeline .apply(Create.of( new NotSerializableObject(), new NotSerializableObject()) .withCoder(NotSerializableObjectCoder.of())); PAssert.that(pcollection).satisfies( new SerializableFunction<Iterable<NotSerializableObject>, Void>() { @Override public Void apply(Iterable<NotSerializableObject> contents) { return null; // no problem! } }); pipeline.run(); } /** * A {@link PAssert} about the contents of a {@link PCollection} * is allows to be verified by an arbitrary {@link SerializableFunction}, * though. */ @Test @Category(ValidatesRunner.class) public void testWindowedSerializablePredicate() throws Exception { PCollection<NotSerializableObject> pcollection = pipeline .apply(Create.timestamped( TimestampedValue.of(new NotSerializableObject(), new Instant(250L)), TimestampedValue.of(new NotSerializableObject(), new Instant(500L))) .withCoder(NotSerializableObjectCoder.of())) .apply(Window.<NotSerializableObject>into(FixedWindows.of(Duration.millis(300L)))); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(0L), new Instant(300L))) .satisfies(new SerializableFunction<Iterable<NotSerializableObject>, Void>() { @Override public Void apply(Iterable<NotSerializableObject> contents) { assertThat(Iterables.isEmpty(contents), is(false)); return null; // no problem! } }); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(300L), new Instant(600L))) .satisfies(new SerializableFunction<Iterable<NotSerializableObject>, Void>() { @Override public Void apply(Iterable<NotSerializableObject> contents) { assertThat(Iterables.isEmpty(contents), is(false)); return null; // no problem! } }); pipeline.run(); } /** * Test that we throw an error at pipeline construction time when the user mistakenly uses * {@code PAssert.thatSingleton().equals()} instead of the test method {@code .isEqualTo}. */ @SuppressWarnings("deprecation") // test of deprecated function @Test public void testPAssertEqualsSingletonUnsupported() throws Exception { thrown.expect(UnsupportedOperationException.class); thrown.expectMessage("isEqualTo"); PCollection<Integer> pcollection = pipeline.apply(Create.of(42)); PAssert.thatSingleton(pcollection).equals(42); } /** * Test that we throw an error at pipeline construction time when the user mistakenly uses * {@code PAssert.that().equals()} instead of the test method {@code .containsInAnyOrder}. */ @SuppressWarnings("deprecation") // test of deprecated function @Test public void testPAssertEqualsIterableUnsupported() throws Exception { thrown.expect(UnsupportedOperationException.class); thrown.expectMessage("containsInAnyOrder"); PCollection<Integer> pcollection = pipeline.apply(Create.of(42)); PAssert.that(pcollection).equals(42); } /** * Test that {@code PAssert.thatSingleton().hashCode()} is unsupported. * See {@link #testPAssertEqualsSingletonUnsupported}. */ @SuppressWarnings("deprecation") // test of deprecated function @Test public void testPAssertHashCodeSingletonUnsupported() throws Exception { thrown.expect(UnsupportedOperationException.class); thrown.expectMessage(".hashCode() is not supported."); PCollection<Integer> pcollection = pipeline.apply(Create.of(42)); PAssert.thatSingleton(pcollection).hashCode(); } /** * Test that {@code PAssert.thatIterable().hashCode()} is unsupported. * See {@link #testPAssertEqualsIterableUnsupported}. */ @SuppressWarnings("deprecation") // test of deprecated function @Test public void testPAssertHashCodeIterableUnsupported() throws Exception { thrown.expect(UnsupportedOperationException.class); thrown.expectMessage(".hashCode() is not supported."); PCollection<Integer> pcollection = pipeline.apply(Create.of(42)); PAssert.that(pcollection).hashCode(); } /** * Basic test for {@code isEqualTo}. */ @Test @Category(ValidatesRunner.class) public void testIsEqualTo() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.of(43)); PAssert.thatSingleton(pcollection).isEqualTo(43); pipeline.run(); } /** * Basic test for {@code isEqualTo}. */ @Test @Category(ValidatesRunner.class) public void testWindowedIsEqualTo() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.timestamped(TimestampedValue.of(43, new Instant(250L)), TimestampedValue.of(22, new Instant(-250L)))) .apply(Window.<Integer>into(FixedWindows.of(Duration.millis(500L)))); PAssert.thatSingleton(pcollection) .inOnlyPane(new IntervalWindow(new Instant(0L), new Instant(500L))) .isEqualTo(43); PAssert.thatSingleton(pcollection) .inOnlyPane(new IntervalWindow(new Instant(-500L), new Instant(0L))) .isEqualTo(22); pipeline.run(); } /** * Basic test for {@code notEqualTo}. */ @Test @Category(ValidatesRunner.class) public void testNotEqualTo() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.of(43)); PAssert.thatSingleton(pcollection).notEqualTo(42); pipeline.run(); } /** * Test that we throw an error for false assertion on singleton. */ @Test @Category(ValidatesRunner.class) public void testPAssertEqualsSingletonFalse() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.of(42)); PAssert.thatSingleton("The value was not equal to 44", pcollection).isEqualTo(44); Throwable thrown = runExpectingAssertionFailure(pipeline); String message = thrown.getMessage(); assertThat(message, containsString("The value was not equal to 44")); assertThat(message, containsString("Expected: <44>")); assertThat(message, containsString("but: was <42>")); } /** * Test that we throw an error for false assertion on singleton. */ @Test @Category(ValidatesRunner.class) public void testPAssertEqualsSingletonFalseDefaultReasonString() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.of(42)); PAssert.thatSingleton(pcollection).isEqualTo(44); Throwable thrown = runExpectingAssertionFailure(pipeline); String message = thrown.getMessage(); assertThat(message, containsString("Create.Values/Read(CreateSource).out")); assertThat(message, containsString("Expected: <44>")); assertThat(message, containsString("but: was <42>")); } /** * Tests that {@code containsInAnyOrder} is actually order-independent. */ @Test @Category(ValidatesRunner.class) public void testContainsInAnyOrder() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.of(1, 2, 3, 4)); PAssert.that(pcollection).containsInAnyOrder(2, 1, 4, 3); pipeline.run(); } /** * Tests that {@code containsInAnyOrder} is actually order-independent. */ @Test @Category(ValidatesRunner.class) public void testGlobalWindowContainsInAnyOrder() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.of(1, 2, 3, 4)); PAssert.that(pcollection).inWindow(GlobalWindow.INSTANCE).containsInAnyOrder(2, 1, 4, 3); pipeline.run(); } /** * Tests that windowed {@code containsInAnyOrder} is actually order-independent. */ @Test @Category(ValidatesRunner.class) public void testWindowedContainsInAnyOrder() throws Exception { PCollection<Integer> pcollection = pipeline.apply(Create.timestamped(TimestampedValue.of(1, new Instant(100L)), TimestampedValue.of(2, new Instant(200L)), TimestampedValue.of(3, new Instant(300L)), TimestampedValue.of(4, new Instant(400L)))) .apply(Window.<Integer>into(SlidingWindows.of(Duration.millis(200L)) .every(Duration.millis(100L)) .withOffset(Duration.millis(50L)))); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(-50L), new Instant(150L))).containsInAnyOrder(1); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(50L), new Instant(250L))) .containsInAnyOrder(2, 1); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(150L), new Instant(350L))) .containsInAnyOrder(2, 3); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(250L), new Instant(450L))) .containsInAnyOrder(4, 3); PAssert.that(pcollection) .inWindow(new IntervalWindow(new Instant(350L), new Instant(550L))) .containsInAnyOrder(4); pipeline.run(); } @Test @Category(ValidatesRunner.class) public void testEmpty() { PCollection<Long> vals = pipeline.apply(Create.empty(VarLongCoder.of())); PAssert.that(vals).empty(); pipeline.run(); } /** * Tests that {@code containsInAnyOrder} fails when and how it should. */ @Test @Category(ValidatesRunner.class) public void testContainsInAnyOrderFalse() throws Exception { PCollection<Integer> pcollection = pipeline .apply(Create.of(1, 2, 3, 4)); PAssert.that(pcollection).containsInAnyOrder(2, 1, 4, 3, 7); Throwable exc = runExpectingAssertionFailure(pipeline); Pattern expectedPattern = Pattern.compile( "Expected: iterable over \\[((<4>|<7>|<3>|<2>|<1>)(, )?){5}\\] in any order"); // A loose pattern, but should get the job done. assertTrue( "Expected error message from PAssert with substring matching " + expectedPattern + " but the message was \"" + exc.getMessage() + "\"", expectedPattern.matcher(exc.getMessage()).find()); } @Test @Category(ValidatesRunner.class) public void testEmptyFalse() throws Exception { PCollection<Long> vals = pipeline.apply(GenerateSequence.from(0).to(5)); PAssert.that("Vals should have been empty", vals).empty(); Throwable thrown = runExpectingAssertionFailure(pipeline); String message = thrown.getMessage(); assertThat(message, containsString("Vals should have been empty")); assertThat(message, containsString("Expected: iterable over [] in any order")); } @Test @Category(ValidatesRunner.class) public void testEmptyFalseDefaultReasonString() throws Exception { PCollection<Long> vals = pipeline.apply(GenerateSequence.from(0).to(5)); PAssert.that(vals).empty(); Throwable thrown = runExpectingAssertionFailure(pipeline); String message = thrown.getMessage(); assertThat(message, containsString("GenerateSequence/Read(BoundedCountingSource).out")); assertThat(message, containsString("Expected: iterable over [] in any order")); } @Test public void testAssertionSiteIsCaptured() { // This check should return a failure. SuccessOrFailure res = PAssert.doChecks( PAssert.PAssertionSite.capture("Captured assertion message."), new Integer(10), new MatcherCheckerFn(SerializableMatchers.contains(new Integer(11)))); String stacktrace = Throwables.getStackTraceAsString(res.assertionError()); assertEquals(res.isSuccess(), false); assertThat(stacktrace, containsString("PAssertionSite.capture")); } @Test @Category(ValidatesRunner.class) public void testAssertionSiteIsCapturedWithMessage() throws Exception { PCollection<Long> vals = pipeline.apply(GenerateSequence.from(0).to(5)); assertThatCollectionIsEmptyWithMessage(vals); Throwable thrown = runExpectingAssertionFailure(pipeline); assertThat( thrown.getMessage(), containsString("Should be empty")); assertThat( thrown.getMessage(), containsString("Expected: iterable over [] in any order")); String stacktrace = Throwables.getStackTraceAsString(thrown); assertThat(stacktrace, containsString("testAssertionSiteIsCapturedWithMessage")); assertThat(stacktrace, containsString("assertThatCollectionIsEmptyWithMessage")); } @Test @Category(ValidatesRunner.class) public void testAssertionSiteIsCapturedWithoutMessage() throws Exception { PCollection<Long> vals = pipeline.apply(GenerateSequence.from(0).to(5)); assertThatCollectionIsEmptyWithoutMessage(vals); Throwable thrown = runExpectingAssertionFailure(pipeline); assertThat( thrown.getMessage(), containsString("Expected: iterable over [] in any order")); String stacktrace = Throwables.getStackTraceAsString(thrown); assertThat(stacktrace, containsString("testAssertionSiteIsCapturedWithoutMessage")); assertThat(stacktrace, containsString("assertThatCollectionIsEmptyWithoutMessage")); } private static void assertThatCollectionIsEmptyWithMessage(PCollection<Long> vals) { PAssert.that("Should be empty", vals).empty(); } private static void assertThatCollectionIsEmptyWithoutMessage(PCollection<Long> vals) { PAssert.that(vals).empty(); } private static Throwable runExpectingAssertionFailure(Pipeline pipeline) { // We cannot use thrown.expect(AssertionError.class) because the AssertionError // is first caught by JUnit and causes a test failure. try { pipeline.run(); } catch (Throwable exc) { return exc; } fail("assertion should have failed"); throw new RuntimeException("unreachable"); } @Test public void countAssertsSucceeds() { PCollection<Integer> create = pipeline.apply("FirstCreate", Create.of(1, 2, 3)); PAssert.that(create).containsInAnyOrder(1, 2, 3); PAssert.thatSingleton(create.apply(Sum.integersGlobally())).isEqualTo(6); PAssert.thatMap(pipeline.apply("CreateMap", Create.of(KV.of(1, 2)))) .isEqualTo(Collections.singletonMap(1, 2)); assertThat(PAssert.countAsserts(pipeline), equalTo(3)); } @Test public void countAssertsMultipleCallsIndependent() { PCollection<Integer> create = pipeline.apply("FirstCreate", Create.of(1, 2, 3)); PAssert.that(create).containsInAnyOrder(1, 2, 3); PAssert.thatSingleton(create.apply(Sum.integersGlobally())).isEqualTo(6); assertThat(PAssert.countAsserts(pipeline), equalTo(2)); PAssert.thatMap(pipeline.apply("CreateMap", Create.of(KV.of(1, 2)))) .isEqualTo(Collections.singletonMap(1, 2)); assertThat(PAssert.countAsserts(pipeline), equalTo(3)); } }