You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by wf...@apache.org on 2014/06/20 00:12:21 UTC

git commit: Add test coverage for MesosLog.

Repository: incubator-aurora
Updated Branches:
  refs/heads/master 2983fe25d -> b792fc1ac


Add test coverage for MesosLog.

Reviewed at https://reviews.apache.org/r/22792/


Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/b792fc1a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/b792fc1a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/b792fc1a

Branch: refs/heads/master
Commit: b792fc1ac0ece759e11e0dc648f8ad2d29a7adea
Parents: 2983fe2
Author: Bill Farner <wf...@apache.org>
Authored: Thu Jun 19 15:08:27 2014 -0700
Committer: Bill Farner <wf...@apache.org>
Committed: Thu Jun 19 15:08:27 2014 -0700

----------------------------------------------------------------------
 .../aurora/scheduler/log/mesos/MesosLog.java    |   9 +-
 .../scheduler/log/mesos/MesosLogTest.java       | 242 +++++++++++++++++--
 2 files changed, 224 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b792fc1a/src/main/java/org/apache/aurora/scheduler/log/mesos/MesosLog.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/log/mesos/MesosLog.java b/src/main/java/org/apache/aurora/scheduler/log/mesos/MesosLog.java
index b8a94ff..ee6149c 100644
--- a/src/main/java/org/apache/aurora/scheduler/log/mesos/MesosLog.java
+++ b/src/main/java/org/apache/aurora/scheduler/log/mesos/MesosLog.java
@@ -341,8 +341,7 @@ public class MesosLog implements org.apache.aurora.scheduler.log.Log {
       });
     }
 
-    @VisibleForTesting
-    interface Mutation<T> {
+    private interface Mutation<T> {
       T apply(WriterInterface writer) throws TimeoutException, Log.WriterFailedException;
     }
 
@@ -354,8 +353,7 @@ public class MesosLog implements org.apache.aurora.scheduler.log.Log {
       throw new StreamAccessException(message, cause);
     }
 
-    @VisibleForTesting
-    synchronized <T> T mutate(OpStats stats, Mutation<T> mutation) {
+    private synchronized <T> T mutate(OpStats stats, Mutation<T> mutation) {
       if (writer == null) {
         throw new IllegalStateException("The log has encountered an error and cannot be used.");
       }
@@ -379,7 +377,8 @@ public class MesosLog implements org.apache.aurora.scheduler.log.Log {
       return LogPosition.wrap(reader.ending());
     }
 
-    private static class LogPosition implements org.apache.aurora.scheduler.log.Log.Position {
+    @VisibleForTesting
+    static class LogPosition implements org.apache.aurora.scheduler.log.Log.Position {
       private final Log.Position underlying;
 
       LogPosition(Log.Position underlying) {

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b792fc1a/src/test/java/org/apache/aurora/scheduler/log/mesos/MesosLogTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/log/mesos/MesosLogTest.java b/src/test/java/org/apache/aurora/scheduler/log/mesos/MesosLogTest.java
index 23c1d7a..35ccd14 100644
--- a/src/test/java/org/apache/aurora/scheduler/log/mesos/MesosLogTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/log/mesos/MesosLogTest.java
@@ -13,9 +13,20 @@
  */
 package org.apache.aurora.scheduler.log.mesos;
 
+import java.lang.reflect.Constructor;
+import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.TimeoutException;
 
-import com.google.inject.util.Providers;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.TypeLiteral;
 import com.twitter.common.application.Lifecycle;
 import com.twitter.common.base.Command;
 import com.twitter.common.quantity.Amount;
@@ -27,40 +38,57 @@ import org.apache.aurora.scheduler.log.mesos.LogInterface.ReaderInterface;
 import org.apache.aurora.scheduler.log.mesos.LogInterface.WriterInterface;
 import org.apache.mesos.Log;
 import org.easymock.EasyMock;
+import org.easymock.IExpectationSetters;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.apache.aurora.scheduler.log.mesos.MesosLog.LogStream.LogPosition;
+import static org.apache.mesos.Log.Position;
 import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 public class MesosLogTest extends EasyMockTest {
 
   private static final Amount<Long, Time> READ_TIMEOUT = Amount.of(5L, Time.SECONDS);
   private static final Amount<Long, Time> WRITE_TIMEOUT = Amount.of(3L, Time.SECONDS);
-  private static final byte[] DUMMY_CONTENT = "test data".getBytes();
+  private static final String DUMMY_CONTENT = "test data";
 
   private Command shutdownHooks;
-  private MesosLog.LogStream logStream;
-  private MesosLog.LogStream.Mutation<String> dummyMutation;
-  private MesosLog.LogStream.OpStats stats;
+  private LogInterface backingLog;
+  private ReaderInterface logReader;
+  private WriterInterface logWriter;
+  private org.apache.aurora.scheduler.log.Log.Stream logStream;
 
   @Before
   public void setUp() {
     shutdownHooks = createMock(Command.class);
-    dummyMutation = createMock(new Clazz<MesosLog.LogStream.Mutation<String>>() { });
-    stats = new MesosLog.LogStream.OpStats("test");
-    logStream = new MesosLog.LogStream(
-        createMock(LogInterface.class),
-        createMock(ReaderInterface.class),
-        READ_TIMEOUT,
-        Providers.of(createMock(WriterInterface.class)),
-        WRITE_TIMEOUT,
-        DUMMY_CONTENT,
-        new Lifecycle(shutdownHooks, null));
+    backingLog = createMock(LogInterface.class);
+    logReader = createMock(ReaderInterface.class);
+    logWriter = createMock(WriterInterface.class);
+
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(LogInterface.class).toInstance(backingLog);
+        bind(ReaderInterface.class).toInstance(logReader);
+        bind(new TypeLiteral<Amount<Long, Time>>() { }).annotatedWith(MesosLog.ReadTimeout.class)
+            .toInstance(READ_TIMEOUT);
+        bind(WriterInterface.class).toInstance(logWriter);
+        bind(new TypeLiteral<Amount<Long, Time>>() { }).annotatedWith(MesosLog.WriteTimeout.class)
+            .toInstance(WRITE_TIMEOUT);
+        bind(byte[].class).annotatedWith(MesosLog.NoopEntry.class)
+            .toInstance(DUMMY_CONTENT.getBytes(Charsets.UTF_8));
+        bind(Lifecycle.class).toInstance(new Lifecycle(shutdownHooks, null));
+      }
+    });
+
+    MesosLog log = injector.getInstance(MesosLog.class);
+    logStream = log.open();
   }
 
   @Test
-  public void testLogStreamTimeout() throws TimeoutException, Log.WriterFailedException {
+  public void testLogStreamTimeout() throws Exception {
     try {
       testMutationFailure(new TimeoutException("Task timed out"));
       fail();
@@ -72,7 +100,7 @@ public class MesosLogTest extends EasyMockTest {
   }
 
   @Test
-  public void testLogStreamWriteFailure() throws TimeoutException, Log.WriterFailedException {
+  public void testLogStreamWriteFailure() throws Exception {
     try {
       testMutationFailure(new Log.WriterFailedException("Failed to write to log"));
       fail();
@@ -83,20 +111,190 @@ public class MesosLogTest extends EasyMockTest {
     expectStreamUnusable();
   }
 
-  private void testMutationFailure(Exception e) throws TimeoutException, Log.WriterFailedException {
+  private void testMutationFailure(Exception e) throws Exception {
+    String data = "hello";
+    expectWrite(data).andThrow(e);
     shutdownHooks.execute();
-    expect(dummyMutation.apply(EasyMock.<WriterInterface>anyObject())).andThrow(e);
 
     control.replay();
-    logStream.mutate(stats, dummyMutation);
+    logStream.append(data.getBytes(Charsets.UTF_8));
   }
 
-  private void expectStreamUnusable() {
+  private void expectStreamUnusable() throws Exception {
     try {
-      logStream.mutate(stats, dummyMutation);
+      logStream.append("nothing".getBytes(Charsets.UTF_8));
       fail();
     } catch (IllegalStateException e) {
       // Expected.
     }
   }
+
+  private static Position makePosition(long value) throws Exception {
+    // The only way to create a Position instance is through a private constructor (MESOS-1519).
+    Constructor<Position> positionConstructor = Position.class.getDeclaredConstructor(long.class);
+    positionConstructor.setAccessible(true);
+    return positionConstructor.newInstance(value);
+  }
+
+  private static Log.Entry makeEntry(Position position, String data) throws Exception {
+    // The only way to create an Entry instance is through a private constructor (MESOS-1519).
+    Constructor<Log.Entry> entryConstructor =
+        Log.Entry.class.getDeclaredConstructor(Position.class, byte[].class);
+    entryConstructor.setAccessible(true);
+    return entryConstructor.newInstance(position, data.getBytes(Charsets.UTF_8));
+  }
+
+  private IExpectationSetters<Position> expectWrite(String content) throws Exception {
+    return expect(
+        logWriter.append(EasyMock.aryEq(content.getBytes(Charsets.UTF_8)),
+            // Cast is needed to prevent NullPointerException on unboxing.
+            EasyMock.eq((long) WRITE_TIMEOUT.getValue()),
+            EasyMock.eq(WRITE_TIMEOUT.getUnit().getTimeUnit())));
+  }
+
+  private Position expectWrite(String content, long resultingPosition) throws Exception {
+    Position position = makePosition(resultingPosition);
+    expectWrite(content).andReturn(position);
+    return position;
+  }
+
+  private void expectDiscoverEntryRange(Position beginning, Position end) {
+    expect(logReader.beginning()).andReturn(beginning);
+    expect(logReader.ending()).andReturn(end);
+  }
+
+  private void expectSetPosition(Position position) {
+    expect(backingLog.position(EasyMock.aryEq(position.identity()))).andReturn(position);
+  }
+
+  private IExpectationSetters<List<Log.Entry>> expectRead(Position position) throws Exception {
+    expectSetPosition(position);
+    return expect(logReader.read(
+        position,
+        position,
+        READ_TIMEOUT.getValue(),
+        READ_TIMEOUT.getUnit().getTimeUnit()));
+  }
+
+  private void expectRead(Position position, String dataReturned) throws Exception {
+    expectRead(position).andReturn(ImmutableList.of(makeEntry(position, dataReturned)));
+  }
+
+  private List<String> readAll() {
+    List<byte[]> entryBytes = FluentIterable.from(ImmutableList.copyOf(logStream.readAll()))
+        .transform(new Function<org.apache.aurora.scheduler.log.Log.Entry, byte[]>() {
+          @Override
+          public byte[] apply(org.apache.aurora.scheduler.log.Log.Entry entry) {
+            return entry.contents();
+          }
+        })
+        .toList();
+    return FluentIterable.from(entryBytes)
+        .transform(new Function<byte[], String>() {
+          @Override
+          public String apply(byte[] data) {
+            return new String(data, Charsets.UTF_8);
+          }
+        })
+        .toList();
+  }
+
+  @Test
+  public void testLogRead() throws Exception {
+    Position beginning = makePosition(1);
+    Position middle = makePosition(2);
+    Position end = expectWrite(DUMMY_CONTENT, 3);
+    expectDiscoverEntryRange(beginning, end);
+    String beginningData = "beginningData";
+    String middleData = "middleData";
+    expectRead(beginning, beginningData);
+    expectRead(middle, middleData);
+    expectRead(end, DUMMY_CONTENT);
+    String newData = "newly appended data";
+    expectWrite(newData, 4);
+
+    control.replay();
+
+    assertEquals(ImmutableList.of(beginningData, middleData, DUMMY_CONTENT), readAll());
+    logStream.append(newData.getBytes());
+
+  }
+
+  @Test(expected = StreamAccessException.class)
+  public void testInitialAppendFails() throws Exception {
+    expectWrite(DUMMY_CONTENT).andThrow(new Log.WriterFailedException("injected"));
+    shutdownHooks.execute();
+
+    control.replay();
+
+    readAll();
+  }
+
+  @Test(expected = StreamAccessException.class)
+  public void testReadTimeout() throws Exception {
+    Position beginning = makePosition(1);
+    Position end = expectWrite(DUMMY_CONTENT, 3);
+    expectDiscoverEntryRange(beginning, end);
+    expectRead(beginning).andThrow(new TimeoutException("injected"));
+
+    control.replay();
+
+    readAll();
+  }
+
+  @Test(expected = StreamAccessException.class)
+  public void testLogError() throws Exception {
+    Position beginning = makePosition(1);
+    Position end = expectWrite(DUMMY_CONTENT, 3);
+    expectDiscoverEntryRange(beginning, end);
+    expectRead(beginning).andThrow(new Log.OperationFailedException("injected"));
+
+    control.replay();
+
+    readAll();
+  }
+
+  @Test
+  public void testTruncate() throws Exception {
+    Position truncate = makePosition(5);
+    expect(logWriter.truncate(
+        truncate,
+        WRITE_TIMEOUT.getValue(),
+        WRITE_TIMEOUT.getUnit().getTimeUnit()))
+        .andReturn(truncate);
+
+    control.replay();
+
+    logStream.truncateBefore(new LogPosition(truncate));
+  }
+
+  @Test(expected = NoSuchElementException.class)
+  public void testIteratorUsage() throws Exception {
+    Position beginning = makePosition(1);
+    Position middle = makePosition(2);
+    Position end = expectWrite(DUMMY_CONTENT, 3);
+    expectDiscoverEntryRange(beginning, end);
+    // SKipped entries.
+    expectRead(beginning).andReturn(ImmutableList.<Log.Entry>of());
+    expectRead(middle).andReturn(ImmutableList.<Log.Entry>of());
+    expectRead(end).andReturn(ImmutableList.<Log.Entry>of());
+
+    control.replay();
+
+    // So close!  The implementation requires that hasNext() is called first.
+    logStream.readAll().next();
+  }
+
+  @Test
+  public void testSortOrder() throws Exception {
+    control.replay();
+
+    LogPosition a = new LogPosition(makePosition(5));
+    LogPosition b = new LogPosition(makePosition(10));
+    LogPosition c = new LogPosition(makePosition(3));
+    assertEquals(
+        ImmutableList.of(c, a, b),
+        ImmutableList.copyOf(ImmutableSortedSet.of(a, b, c))
+    );
+  }
 }