/* * HA-JDBC: High-Availability JDBC * Copyright (C) 2012 Paul Ferraro * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.hajdbc.balancer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import net.sf.hajdbc.MockDatabase; import net.sf.hajdbc.invocation.Invoker; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author Paul Ferraro */ public abstract class AbstractBalancerTest { final BalancerFactory factory; final MockDatabase[] databases = new MockDatabase[] { new MockDatabase("0", 0), new MockDatabase("1", 1), new MockDatabase("2", 2) }; AbstractBalancerTest(BalancerFactory factory) { this.factory = factory; } @Test public void next() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); this.next(balancer); } protected abstract void next(Balancer<Void, MockDatabase> balancer); @Test public void nextEmptyBalancer() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertNull(balancer.next()); } @Test public void nextZeroWeight() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertSame(this.databases[0], balancer.next()); } @Test public void nextSingleWeight() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertSame(this.databases[1], balancer.next()); int count = 10; CountDownLatch latch = new CountDownLatch(count); WaitingInvoker invoker = new WaitingInvoker(latch); ExecutorService executor = Executors.newFixedThreadPool(count); List<Future<Void>> futures = new ArrayList<>(count); for (int i = 0; i < count; ++i) { futures.add(executor.submit(new InvocationTask(balancer, invoker, this.databases[1]))); } try { // Ensure all invokers are started latch.await(); // Should still use the same database, even under load assertSame(this.databases[1], balancer.next()); // Allow invokers to continue synchronized (invoker) { invoker.notifyAll(); } this.complete(futures); // Should still use the same database, after load assertSame(this.databases[1], balancer.next()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { executor.shutdownNow(); } } @Test public void primary() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertNull(balancer.primary()); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertSame(this.databases[0], balancer.primary()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertSame(this.databases[0], balancer.primary()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); assertSame(this.databases[0], balancer.primary()); } @Test public void backups() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); Iterable<MockDatabase> result = balancer.backups(); assertNotNull(result); Iterator<MockDatabase> backups = result.iterator(); assertFalse(backups.hasNext()); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.backups(); assertNotNull(result); backups = result.iterator(); assertFalse(backups.hasNext()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.backups(); assertNotNull(result); backups = result.iterator(); assertTrue(backups.hasNext()); assertSame(this.databases[1], backups.next()); assertFalse(backups.hasNext()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.backups(); assertNotNull(result); backups = result.iterator(); assertTrue(backups.hasNext()); assertSame(this.databases[1], backups.next()); assertTrue(backups.hasNext()); assertSame(this.databases[2], backups.next()); assertFalse(backups.hasNext()); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#addAll(java.util.Collection)}. */ @Test public void addAll() { Collection<MockDatabase> databases = Arrays.asList(this.databases[1], this.databases[2]); Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); boolean result = balancer.addAll(databases); assertTrue(result); assertCollectionEquals(databases, balancer); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.addAll(databases); assertTrue(result); assertCollectionEquals(Arrays.asList(this.databases), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.addAll(databases); assertTrue(result); assertCollectionEquals(Arrays.asList(this.databases), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.addAll(databases); assertFalse(result); assertCollectionEquals(Arrays.asList(this.databases), balancer); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#removeAll(java.util.Collection)}. */ @Test public void removeAll() { Collection<MockDatabase> databases = Arrays.asList(this.databases[1], this.databases[2]); Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); boolean result = balancer.removeAll(databases); assertFalse(result); assertEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.removeAll(databases); assertFalse(result); assertEquals(Collections.singleton(this.databases[0]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.removeAll(databases); assertTrue(result); assertEquals(Collections.singleton(this.databases[0]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.removeAll(databases); assertTrue(result); assertEquals(Collections.singleton(this.databases[0]), balancer); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#retainAll(java.util.Collection)}. */ @Test public void retainAll() { Collection<MockDatabase> databases = Arrays.asList(this.databases[1], this.databases[2]); Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); boolean result = balancer.retainAll(databases); assertFalse(result); assertCollectionEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.retainAll(databases); assertTrue(result); assertCollectionEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.retainAll(databases); assertTrue(result); assertCollectionEquals(Collections.singleton(this.databases[1]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.retainAll(databases); assertTrue(result); assertCollectionEquals(databases, balancer); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#clear()}. */ @Test public void clear() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); balancer.clear(); assertEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); balancer.clear(); assertEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); balancer.clear(); assertEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); balancer.clear(); assertEquals(Collections.<MockDatabase>emptySet(), balancer); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#remove(java.lang.Object)}. */ @Test public void remove() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); boolean result = balancer.remove(this.databases[1]); assertFalse(result); assertCollectionEquals(Collections.<MockDatabase>emptySet(), balancer); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.remove(this.databases[1]); assertFalse(result); assertCollectionEquals(Collections.singleton(this.databases[0]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.remove(this.databases[1]); assertTrue(result); assertCollectionEquals(Collections.singleton(this.databases[0]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.remove(this.databases[1]); assertTrue(result); assertCollectionEquals(Arrays.asList(this.databases[0], this.databases[2]), balancer); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#add(net.sf.hajdbc.Database)}. */ @Test public void add() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); boolean result = balancer.add(this.databases[1]); assertTrue(result); assertCollectionEquals(Collections.singleton(this.databases[1]), balancer); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.add(this.databases[1]); assertTrue(result); assertCollectionEquals(Arrays.asList(this.databases[0], this.databases[1]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.add(this.databases[1]); assertFalse(result); assertCollectionEquals(Arrays.asList(this.databases[0], this.databases[1]), balancer); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.add(this.databases[1]); assertFalse(result); assertCollectionEquals(Arrays.asList(this.databases), balancer); } /** * Test method for {@link net.sf.hajdbc.balancer.load.LoadBalancer#invoke(net.sf.hajdbc.invocation.Invoker, net.sf.hajdbc.Database, java.lang.Object)}. */ @Test public void invoke() throws Exception { TestInvoker invoker = mock(TestInvoker.class); Object object = new Object(); Object expected = new Object(); Exception expectedException = new Exception(); Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); when(invoker.invoke(this.databases[0], object)).thenReturn(expected); Object result = null; Exception exception = null; try { result = balancer.invoke(invoker, this.databases[0], object); } catch (Exception e) { exception = e; } assertSame(expected, result); assertNull(exception); reset(invoker); when(invoker.invoke(this.databases[0], object)).thenThrow(expectedException); result = null; try { result = balancer.invoke(invoker, this.databases[0], object); } catch (Exception e) { exception = e; } assertNull(result); assertSame(expectedException, exception); } interface TestInvoker extends Invoker<Void, MockDatabase, Object, Object, Exception> {} /** * Test method for {@link net.sf.hajdbc.balancer.AbstractBalancer#iterator()}. */ @Test public void iterator() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); Iterator<MockDatabase> result = balancer.iterator(); assertFalse(result.hasNext()); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); result = balancer.iterator(); assertTrue(result.hasNext()); assertEquals(this.databases[0], result.next()); assertFalse(result.hasNext()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); result = balancer.iterator(); assertTrue(result.hasNext()); assertEquals(this.databases[0], result.next()); assertTrue(result.hasNext()); assertEquals(this.databases[1], result.next()); assertFalse(result.hasNext()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); result = balancer.iterator(); assertTrue(result.hasNext()); assertEquals(this.databases[0], result.next()); assertTrue(result.hasNext()); assertEquals(this.databases[1], result.next()); assertTrue(result.hasNext()); assertEquals(this.databases[2], result.next()); assertFalse(result.hasNext()); } /** * Test method for {@link net.sf.hajdbc.balancer.AbstractBalancer#contains(java.lang.Object)}. */ @Test public void contains() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertFalse(balancer.contains(this.databases[0])); assertFalse(balancer.contains(this.databases[1])); assertFalse(balancer.contains(this.databases[2])); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertTrue(balancer.contains(this.databases[0])); assertFalse(balancer.contains(this.databases[1])); assertFalse(balancer.contains(this.databases[2])); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertTrue(balancer.contains(this.databases[0])); assertTrue(balancer.contains(this.databases[1])); assertFalse(balancer.contains(this.databases[2])); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); assertTrue(balancer.contains(this.databases[0])); assertTrue(balancer.contains(this.databases[1])); assertTrue(balancer.contains(this.databases[2])); } /** * Test method for {@link net.sf.hajdbc.balancer.AbstractBalancer#containsAll(java.util.Collection)}. */ @Test public void containsAll() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertTrue(balancer.containsAll(Collections.emptyList())); assertFalse(balancer.containsAll(Collections.singletonList(this.databases[0]))); assertFalse(balancer.containsAll(Arrays.asList(this.databases[0], this.databases[1]))); assertFalse(balancer.containsAll(Arrays.asList(this.databases))); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertTrue(balancer.containsAll(Collections.emptyList())); assertTrue(balancer.containsAll(Collections.singletonList(this.databases[0]))); assertFalse(balancer.containsAll(Arrays.asList(this.databases[0], this.databases[1]))); assertFalse(balancer.containsAll(Arrays.asList(this.databases))); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertTrue(balancer.containsAll(Collections.emptyList())); assertTrue(balancer.containsAll(Collections.singletonList(this.databases[0]))); assertTrue(balancer.containsAll(Arrays.asList(this.databases[0], this.databases[1]))); assertFalse(balancer.containsAll(Arrays.asList(this.databases))); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); assertTrue(balancer.containsAll(Collections.emptyList())); assertTrue(balancer.containsAll(Collections.singletonList(this.databases[0]))); assertTrue(balancer.containsAll(Arrays.asList(this.databases[0], this.databases[1]))); assertTrue(balancer.containsAll(Arrays.asList(this.databases))); } /** * Test method for {@link net.sf.hajdbc.balancer.AbstractBalancer#isEmpty()}. */ @Test public void isEmpty() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertTrue(balancer.isEmpty()); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertFalse(balancer.isEmpty()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertFalse(balancer.isEmpty()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); assertFalse(balancer.isEmpty()); } /** * Test method for {@link net.sf.hajdbc.balancer.AbstractBalancer#size()}. */ @Test public void size() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertEquals(0, balancer.size()); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertEquals(1, balancer.size()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertEquals(2, balancer.size()); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); assertEquals(3, balancer.size()); } /** * Test method for {@link net.sf.hajdbc.balancer.AbstractBalancer#toArray()}. */ @Test public void toArray() { Balancer<Void, MockDatabase> balancer = this.factory.createBalancer(Collections.<MockDatabase>emptySet()); assertTrue(Arrays.equals(new Object[0], balancer.toArray())); balancer = this.factory.createBalancer(Collections.singleton(this.databases[0])); assertTrue(Arrays.equals(new Object[] { this.databases[0] }, balancer.toArray())); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases[0], this.databases[1]))); assertTrue(Arrays.equals(new Object[] { this.databases[0], this.databases[1] }, balancer.toArray())); balancer = this.factory.createBalancer(new HashSet<>(Arrays.asList(this.databases))); assertTrue(Arrays.equals(this.databases, balancer.toArray())); } private static boolean assertCollectionEquals(Collection<MockDatabase> c1, Collection<MockDatabase> c2) { Iterator<MockDatabase> i1 = c1.iterator(); Iterator<MockDatabase> i2 = c2.iterator(); while (i1.hasNext() && i2.hasNext()) { if (!i1.next().equals(i2.next())) return false; } return !i1.hasNext() && !i2.hasNext(); } void complete(List<Future<Void>> futures) throws InterruptedException { for (Future<Void> future: futures) { try { future.get(); } catch (ExecutionException e) { fail(e.getCause().toString()); } } } class WaitingInvoker implements Invoker<Void, MockDatabase, Void, Void, Exception> { private final CountDownLatch latch; WaitingInvoker(CountDownLatch latch) { this.latch = latch; } @Override public Void invoke(MockDatabase database, Void object) { synchronized (this) { this.latch.countDown(); try { this.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } return null; } } static class InvocationTask implements Callable<Void> { private final Balancer<Void, MockDatabase> balancer; private final WaitingInvoker invoker; private final MockDatabase database; InvocationTask(Balancer<Void, MockDatabase> balancer, WaitingInvoker invoker, MockDatabase database) { this.balancer = balancer; this.invoker = invoker; this.database = database; } @Override public Void call() throws Exception { this.balancer.invoke(this.invoker, this.database, null); return null; } } }