/** * Copyright (c) 2015 The original author or authors * * 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 org.reveno.atp.acceptance.tests; import com.google.common.io.Files; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import org.reveno.atp.acceptance.api.commands.CreateNewAccountCommand; import org.reveno.atp.acceptance.api.commands.NewOrderCommand; import org.reveno.atp.acceptance.api.transactions.AcceptOrder; import org.reveno.atp.acceptance.api.transactions.CreateAccount; import org.reveno.atp.acceptance.api.transactions.Credit; import org.reveno.atp.acceptance.api.transactions.Debit; import org.reveno.atp.acceptance.handlers.Commands; import org.reveno.atp.acceptance.handlers.Transactions; import org.reveno.atp.acceptance.model.Account; import org.reveno.atp.acceptance.model.Order; import org.reveno.atp.acceptance.model.Order.OrderType; import org.reveno.atp.acceptance.model.immutable.ImmutableAccount; import org.reveno.atp.acceptance.model.immutable.ImmutableOrder; import org.reveno.atp.acceptance.model.mutable.MutableAccount; import org.reveno.atp.acceptance.model.mutable.MutableOrder; import org.reveno.atp.acceptance.views.AccountView; import org.reveno.atp.acceptance.views.OrderView; import org.reveno.atp.api.ChannelOptions; import org.reveno.atp.api.Configuration.CpuConsumption; import org.reveno.atp.api.Configuration.ModelType; import org.reveno.atp.api.Reveno; import org.reveno.atp.api.commands.Result; import org.reveno.atp.api.domain.WriteableRepository; import org.reveno.atp.api.exceptions.FailoverRulesException; import org.reveno.atp.api.transaction.TransactionInterceptor; import org.reveno.atp.api.transaction.TransactionStage; import org.reveno.atp.core.Engine; import org.reveno.atp.test.utils.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; @RunWith(Parameterized.class) public class RevenoBaseTest { @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { { ModelType.IMMUTABLE }, { ModelType.MUTABLE } }); } protected volatile boolean dontDelete = false; @Parameter public ModelType modelType; protected File tempDir; @BeforeClass public static void beforeClass() { System.setProperty("protostuff.runtime.collection_schema_on_repeated_fields", "true"); } @Before public void setUp() { tempDir = Files.createTempDir(); log.info("Dir: " + tempDir); } @After public void tearDown() throws IOException { if (!dontDelete) FileUtils.delete(tempDir); } @Test public void mockTest() { // gradle requires such hack though } protected TestRevenoEngine createEngine() { return createEngine((r) -> {}); } protected TestRevenoEngine createEngine(Consumer<TestRevenoEngine> consumer) { return createEngine(consumer, true); } protected TestRevenoEngine createEngine(Consumer<TestRevenoEngine> consumer, boolean meter) { TestRevenoEngine reveno = new TestRevenoEngine(tempDir); configure(reveno); txPerSecondMeter(reveno); consumer.accept(reveno); setModelType(); return reveno; } protected void setModelType() { if (modelType == ModelType.IMMUTABLE) { Transactions.accountFactory = ImmutableAccount.FACTORY; Transactions.orderFactory = ImmutableOrder.FACTORY; } else { Transactions.accountFactory = MutableAccount.FACTORY; Transactions.orderFactory = MutableOrder.FACTORY; } } protected <T extends Engine> T configure(T reveno) { reveno.config().cpuConsumption(CpuConsumption.LOW); reveno.config().modelType(modelType); reveno.config().journaling().channelOptions(ChannelOptions.BUFFERING_OS); reveno.config().mapCapacity(16); reveno.config().mapLoadFactor(0.5f); reveno.domain().command(CreateNewAccountCommand.class, Long.class, Commands::createAccount); reveno.domain().command(NewOrderCommand.class, Long.class, Commands::newOrder); reveno.domain().command(Credit.class, Commands::credit); reveno.domain().command(Debit.class, Commands::debit); reveno.domain().transactionAction(CreateAccount.class, Transactions::createAccount); reveno.domain().transactionAction(AcceptOrder.class, Transactions::acceptOrder); reveno.domain().transactionAction(Credit.class, Transactions::credit); reveno.domain().transactionAction(Debit.class, Transactions::debit); reveno.domain().viewMapper(Account.class, AccountView.class, (id,e,r) -> new AccountView(id, e.currency(), e.balance(), e.orders(), reveno.query())); reveno.domain().viewMapper(Order.class, OrderView.class, (id,e,r) -> new OrderView(id, e.accountId(), e.size(), e.price(), e.time(), e.positionId(), e.symbol(), e.orderStatus(), e.orderType(), reveno.query())); return reveno; } protected void txPerSecondMeter(TestRevenoEngine reveno) { reveno.interceptors().add(TransactionStage.TRANSACTION, new TransactionInterceptor() { protected long start = -1L; @Override public void intercept(long transactionId, long time, long flag, WriteableRepository repository, TransactionStage stage) { if (flag == 0) { if (start == -1 || transactionId == 1) start = System.currentTimeMillis(); if (transactionId % 10_000_000 == 0) { log.info(String.format("%s tx per second!", ( Math.round((double) 10_000_000 * 1000) / (System.currentTimeMillis() - start)))); start = System.currentTimeMillis(); } } } @Override public void destroy() { } }); } protected int generateAndSendCommands(Reveno reveno, int count) throws InterruptedException, ExecutionException { return generateAndSendCommands(reveno, count, i -> {}); } protected int generateAndSendCommands(Reveno reveno, int count, Consumer<Integer> c) throws InterruptedException, ExecutionException { sendCommandsBatch(reveno, new CreateNewAccountCommand("USD", 1000_000L), count); List<NewOrderCommand> commands = new ArrayList<>(); for (long i = 0; i < count; i++) { commands.add(new NewOrderCommand(i + 1, null, "EUR/USD", 134000, (long)(1000*Math.random()), OrderType.MARKET)); } return sendCommandsBatch(reveno, commands, c); } protected File findFirstFile(String prefix) { return tempDir.listFiles((dir, name) -> name.startsWith(prefix))[0]; } @SuppressWarnings("unchecked") protected <T> T sendCommandSync(Reveno reveno, Object command) throws InterruptedException, ExecutionException { Result<T> result = (Result<T>) reveno.executeCommand(command).get(); if (!result.isSuccess()) throw new RuntimeException(result.getException()); return result.getResult(); } protected int sendCommandsBatch(Reveno reveno, Object command, int n) throws InterruptedException, ExecutionException { int failedToSend = 0; for (int i = 0; i < n - 1; i++) { try { reveno.executeCommand(command); } catch (FailoverRulesException e) { failedToSend++; log.info("Skipping to send command {}, message: {}", i, e.getMessage()); LockSupport.parkNanos(100); } } try { sendCommandSync(reveno, command); } catch (FailoverRulesException e) { failedToSend++; log.info("Skipping to send command {}, message: {}", n, e.getMessage()); } return failedToSend; } protected void sendCommandsBatch(Reveno reveno, List<?> commands) throws InterruptedException, ExecutionException { sendCommandsBatch(reveno, commands, i -> {}); } protected int sendCommandsBatch(Reveno reveno, List<?> commands, Consumer<Integer> c) throws InterruptedException, ExecutionException { AtomicInteger failedToSend = new AtomicInteger(0); List<CompletableFuture<Result<Object>>> futures = new ArrayList<>(commands.size()); for (int i = 0; i < commands.size() - 1; i++) { c.accept(i); try { futures.add(reveno.executeCommand(commands.get(i))); } catch (Throwable e) { failedToSend.incrementAndGet(); log.info("Skipping to send command {}, message: {}", i, e.getMessage()); LockSupport.parkNanos(100); } } try { sendCommandSync(reveno, commands.get(commands.size() - 1)); } catch (Throwable e) { failedToSend.incrementAndGet(); log.info("Skipping to send command {}, message: {}", commands.size(), e.getMessage()); } futures.forEach(f -> { try { if (!f.get().isSuccess()) { failedToSend.incrementAndGet(); } } catch (Throwable ignored) { } }); return failedToSend.get(); } protected <T> Waiter listenFor(Reveno reveno, Class<T> event) { Waiter waiter = new Waiter(1); reveno.events().eventHandler(event, (e,m) -> waiter.countDown()); return waiter; } protected <T> Waiter listenAsyncFor(Reveno reveno, Class<T> event, int n) { return listenAsyncFor(reveno, event, n, (o) -> {}); } protected <T> Waiter listenAsyncFor(Reveno reveno, Class<T> eventType, int n, Consumer<Long> c) { Waiter waiter = new Waiter(n); reveno.events().asyncEventHandler(eventType, (e,m) -> { long count = 0; // since might be N threads executing this, count and countDown operations are not in sync, // thus some numbers are skipped because of contention synchronized (waiter) { waiter.countDown(); count = waiter.getCount(); } c.accept(count); }); return waiter; } protected <T> Waiter listenFor(Reveno reveno, Class<T> event, int n) { return listenFor(reveno, event, n, (a) -> {}); } protected <T> Waiter listenFor(Reveno reveno, Class<T> event, int n, Consumer<Long> c) { Waiter waiter = new Waiter(n); reveno.events().eventHandler(event, (e,m) -> { waiter.countDown(); c.accept(waiter.getCount()); }); return waiter; } protected void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static class Waiter extends CountDownLatch { public Waiter(int count) { super(count); } public boolean isArrived() throws InterruptedException { return await(10, TimeUnit.SECONDS); } public boolean isArrived(int time) throws InterruptedException { return await(time, TimeUnit.SECONDS); } public void awaitSilent() { try { isArrived(); } catch (InterruptedException e) { } } } protected static final Logger log = LoggerFactory.getLogger(RevenoBaseTest.class); }