/* * 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.transforms.windowing; import static org.apache.beam.sdk.testing.WindowFnTestUtils.runWindowFn; import static org.apache.beam.sdk.testing.WindowFnTestUtils.set; import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.beam.sdk.testing.WindowFnTestUtils; import org.apache.beam.sdk.transforms.display.DisplayData; import org.joda.time.Duration; import org.joda.time.Instant; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for Sessions WindowFn. */ @RunWith(JUnit4.class) public class SessionsTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testSimple() throws Exception { Map<IntervalWindow, Set<String>> expected = new HashMap<>(); expected.put(new IntervalWindow(new Instant(0), new Instant(10)), set(0)); expected.put(new IntervalWindow(new Instant(10), new Instant(20)), set(10)); expected.put(new IntervalWindow(new Instant(101), new Instant(111)), set(101)); assertEquals( expected, runWindowFn( Sessions.withGapDuration(new Duration(10)), Arrays.asList(0L, 10L, 101L))); } @Test public void testConsecutive() throws Exception { Map<IntervalWindow, Set<String>> expected = new HashMap<>(); expected.put(new IntervalWindow(new Instant(1), new Instant(19)), set(1, 2, 5, 9)); expected.put(new IntervalWindow(new Instant(100), new Instant(111)), set(100, 101)); assertEquals( expected, runWindowFn( Sessions.withGapDuration(new Duration(10)), Arrays.asList(1L, 2L, 5L, 9L, 100L, 101L))); } @Test public void testMerging() throws Exception { Map<IntervalWindow, Set<String>> expected = new HashMap<>(); expected.put(new IntervalWindow(new Instant(1), new Instant(40)), set(1, 10, 15, 22, 30)); expected.put(new IntervalWindow(new Instant(95), new Instant(111)), set(95, 100, 101)); assertEquals( expected, runWindowFn( Sessions.withGapDuration(new Duration(10)), Arrays.asList(1L, 15L, 30L, 100L, 101L, 95L, 22L, 10L))); } @Test public void testTimeUnit() throws Exception { Map<IntervalWindow, Set<String>> expected = new HashMap<>(); expected.put(new IntervalWindow(new Instant(1), new Instant(2000)), set(1, 2, 1000)); expected.put(new IntervalWindow(new Instant(5000), new Instant(6001)), set(5000, 5001)); expected.put(new IntervalWindow(new Instant(10000), new Instant(11000)), set(10000)); assertEquals( expected, runWindowFn( Sessions.withGapDuration(Duration.standardSeconds(1)), Arrays.asList(1L, 2L, 1000L, 5000L, 5001L, 10000L))); } @Test public void testEquality() { assertTrue( Sessions.withGapDuration(new Duration(10)).isCompatible( Sessions.withGapDuration(new Duration(10)))); assertTrue( Sessions.withGapDuration(new Duration(10)).isCompatible( Sessions.withGapDuration(new Duration(20)))); } @Test public void testVerifyCompatibility() throws IncompatibleWindowException { Sessions.withGapDuration(new Duration(10)) .verifyCompatibility(Sessions.withGapDuration(new Duration(10))); thrown.expect(IncompatibleWindowException.class); Sessions.withGapDuration(new Duration(10)) .verifyCompatibility(FixedWindows.of(new Duration(10))); } /** * Validates that the output timestamp for aggregate data falls within the acceptable range. */ @Test public void testValidOutputTimes() throws Exception { for (long timestamp : Arrays.asList(200, 800, 700)) { WindowFnTestUtils.validateGetOutputTimestamp( Sessions.withGapDuration(Duration.millis(500)), timestamp); } } /** * Test to confirm that {@link Sessions} with the default {@link TimestampCombiner} holds up the * watermark potentially indefinitely. */ @Test public void testInvalidOutputAtEarliest() throws Exception { try { WindowFnTestUtils.<Object, IntervalWindow>validateGetOutputTimestamps( Sessions.withGapDuration(Duration.millis(10)), TimestampCombiner.EARLIEST, ImmutableList.of( (List<Long>) ImmutableList.of(1L, 3L), (List<Long>) ImmutableList.of(0L, 5L, 10L, 15L, 20L))); } catch (AssertionError exc) { assertThat( exc.getMessage(), // These are the non-volatile pieces of the error message that a timestamp // was not greater than what it should be. allOf(containsString("a value greater than"), containsString("was less than"))); } } /** * When a user explicitly requests per-key aggregate values have their derived timestamp to be * the end of the window (instead of the earliest possible), the session here should not hold * each other up, even though they overlap. */ @Test public void testValidOutputAtEndTimes() throws Exception { WindowFnTestUtils.<Object, IntervalWindow>validateGetOutputTimestamps( Sessions.withGapDuration(Duration.millis(10)), TimestampCombiner.END_OF_WINDOW, ImmutableList.of( (List<Long>) ImmutableList.of(1L, 3L), (List<Long>) ImmutableList.of(0L, 5L, 10L, 15L, 20L))); } @Test public void testDisplayData() { Duration gapDuration = Duration.standardMinutes(234); Sessions session = Sessions.withGapDuration(gapDuration); assertThat(DisplayData.from(session), hasDisplayItem("gapDuration", gapDuration)); } }