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))
+ );
+ }
}