package com.linkedin.parseq.batching; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.util.concurrent.TimeUnit; import org.testng.annotations.Test; import com.linkedin.parseq.BaseEngineTest; import com.linkedin.parseq.EngineBuilder; import com.linkedin.parseq.Task; public class TestBatchingSupport extends BaseEngineTest { private final BatchingSupport _batchingSupport = new BatchingSupport(); @Override protected void customizeEngine(EngineBuilder engineBuilder) { engineBuilder.setPlanDeactivationListener(_batchingSupport); } @Test public void testBatchInvoked() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> promise.done(String.valueOf(key)), key -> 0); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0), strategy.batchable(1)) .map("concat", (s0, s1) -> s0 + s1); String result = runAndWait("TestBatchingSupport.testBatchInvoked", task); assertEquals(result, "01"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 0); } @Test public void testSingletonsInvoked() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> promise.done(String.valueOf(key)), key -> key); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0), strategy.batchable(1)) .map("concat", (s0, s1) -> s0 + s1); String result = runAndWait("TestBatchingSupport.testSingletonsInvoked", task); assertEquals(result, "01"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertEquals(strategy.getExecutedBatches().size(), 0); assertEquals(strategy.getExecutedSingletons().size(), 2); } @Test public void testBatchAndSingleton() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> promise.done(String.valueOf(key)), key -> key % 2); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0), strategy.batchable(1), strategy.batchable(2)) .map("concat", (s0, s1, s2) -> s0 + s1 + s2); String result = runAndWait("TestBatchingSupport.testBatchAndSingleton", task); assertEquals(result, "012"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 1); } @Test public void testBatchAndFailedSingleton() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> { if (key % 2 == 0) { promise.done(String.valueOf(key)); } else { promise.fail(new Exception()); } }, key -> key % 2); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0), strategy.batchable(1).recover(e -> "failed"), strategy.batchable(2)) .map("concat", (s0, s1, s2) -> s0 + s1 + s2); String result = runAndWait("TestBatchingSupport.testBatchAndFailedSingleton", task); assertEquals(result, "0failed2"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 1); } @Test public void testFailedBatchAndSingleton() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> { if (key % 2 == 1) { promise.done(String.valueOf(key)); } else { promise.fail(new Exception()); } }, key -> key % 2); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0).recover(e -> "failed"), strategy.batchable(1), strategy.batchable(2).recover(e -> "failed")) .map("concat", (s0, s1, s2) -> s0 + s1 + s2); String result = runAndWait("TestBatchingSupport.testFailedBatchAndSingleton", task); assertEquals(result, "failed1failed"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 1); } @Test public void testClassifyFailure() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> promise.done(String.valueOf(key)), key -> key / key); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0).recover(e -> "failed"), strategy.batchable(1).recover(e -> "failed")) .map("concat", (s0, s1) -> s0 + s1); String result = runAndWait("TestBatchingSupport.testClassifyFailure", task); assertEquals(result, "failed1"); assertEquals(strategy.getExecutedBatches().size(), 0); assertEquals(strategy.getExecutedSingletons().size(), 1); } @Test public void testExecuteBatchFailure() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<Integer, Integer, String>((key, promise) -> promise.done(String.valueOf(key)), key -> key % 2) { @Override public void executeBatch(Integer group, Batch<Integer, String> batch) { throw new RuntimeException(); } }; _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0).recover(e -> "failed"), strategy.batchable(1).recover(e -> "failed"), strategy.batchable(2).recover(e -> "failed")) .map("concat", (s0, s1, s2) -> s0 + s1 + s2); String result = runAndWait("TestBatchingSupport.testExecuteBatchFailure", task); assertEquals(result, "failedfailedfailed"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 0); assertEquals(strategy.getExecutedSingletons().size(), 0); } @Test public void testNothingToDoForStrategy() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> promise.done(String.valueOf(key)), key -> 0); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(Task.value("0"), Task.value("1")) .map("concat", (s0, s1) -> s0 + s1); String result = runAndWait("TestBatchingSupport.testNothingToDoForStrategy", task); assertEquals(result, "01"); assertEquals(strategy.getClassifiedKeys().size(), 0); assertEquals(strategy.getExecutedBatches().size(), 0); assertEquals(strategy.getExecutedSingletons().size(), 0); } @Test public void testDeduplication() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<>((key, promise) -> promise.done(String.valueOf(key)), key -> key % 2); _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0), strategy.batchable(1), strategy.batchable(2), strategy.batchable(0), strategy.batchable(1), strategy.batchable(2)) .map("concat", (s0, s1, s2, s3, s4, s5) -> s0 + s1 + s2 + s3 + s4 + s5); String result = runAndWait("TestBatchingSupport.testDeduplication", task); assertEquals(result, "012012"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 1); } @Test public void testBatchWithTimeoutAndSingleton() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<Integer, Integer, String>((key, promise) -> promise.done(String.valueOf(key)), key -> key % 2) { @Override public void executeBatch(final Integer group, final Batch<Integer, String> batch) { getScheduler().schedule(() -> { super.executeBatch(group, batch); }, 250, TimeUnit.MILLISECONDS); } }; _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0).withTimeout(10, TimeUnit.MILLISECONDS).recover("toExceptionName", e -> e.getClass().getName()), strategy.batchable(1), strategy.batchable(2)) .map("concat", (s0, s1, s2) -> s0 + s1 + s2); String result = runAndWait("TestBatchingSupport.testBatchWithTimeoutAndSingleton", task); assertEquals(result, "java.util.concurrent.TimeoutException12"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 1); } @Test public void testBatchAndSingletonWithTimeout() { RecordingStrategy<Integer, Integer, String> strategy = new RecordingStrategy<Integer, Integer, String>((key, promise) -> promise.done(String.valueOf(key)), key -> key % 2) { @Override public void executeBatch(final Integer group, final Batch<Integer, String> batch) { getScheduler().schedule(() -> { super.executeBatch(group, batch); }, 250, TimeUnit.MILLISECONDS); } }; _batchingSupport.registerStrategy(strategy); Task<String> task = Task.par(strategy.batchable(0), strategy.batchable(1).withTimeout(10, TimeUnit.MILLISECONDS).recover("toExceptionName", e -> e.getClass().getName()), strategy.batchable(2)) .map("concat", (s0, s1, s2) -> s0 + s1 + s2); String result = runAndWait("TestBatchingSupport.testBatchAndSingletonWithTimeout", task); assertEquals(result, "0java.util.concurrent.TimeoutException2"); assertTrue(strategy.getClassifiedKeys().contains(0)); assertTrue(strategy.getClassifiedKeys().contains(1)); assertTrue(strategy.getClassifiedKeys().contains(2)); assertEquals(strategy.getExecutedBatches().size(), 1); assertEquals(strategy.getExecutedSingletons().size(), 1); } }