Skip to content

Commit

Permalink
Test: Local Snapshot unit tests (iotaledger#1364)
Browse files Browse the repository at this point in the history
* Added LS tests, modified javadoc

* Fixed 2 broken tests, worked around cached buildin snapshot breaking out testnet config test

* Added codacy fixes

* Updated method names

* Removed javadoc on favor of PR iotaledger#1159

* Changed comments to @VisibleForTesting

* Typos fixed

* Added import statement, how did that not error?

* Added comments, removing throws
  • Loading branch information
Brord van Wierst authored and Gal Rogozinski committed May 12, 2019
1 parent 9d1076a commit 2b40917
Show file tree
Hide file tree
Showing 11 changed files with 658 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.iota.iri.service.snapshot;

import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.controllers.MilestoneViewModel;
import com.iota.iri.model.Hash;
import com.iota.iri.service.milestone.LatestMilestoneTracker;
import com.iota.iri.service.transactionpruning.TransactionPruner;
import com.iota.iri.storage.Tangle;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.iota.iri.service.snapshot.impl;

import com.google.common.annotations.VisibleForTesting;
import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.service.milestone.LatestMilestoneTracker;
import com.iota.iri.service.snapshot.LocalSnapshotManager;
Expand Down Expand Up @@ -31,7 +32,8 @@ public class LocalSnapshotManagerImpl implements LocalSnapshotManager {
* To prevent jumping back and forth in and out of sync, there is a buffer in between.
* Only when the latest milestone and latest snapshot differ more than this number, we fall out of sync
*/
private static final int LOCAL_SNAPSHOT_SYNC_BUFFER = 5;
@VisibleForTesting
static final int LOCAL_SNAPSHOT_SYNC_BUFFER = 5;

/**
* Logger for this class allowing us to dump debug and status messages.
Expand Down Expand Up @@ -128,7 +130,8 @@ public void shutdown() {
*
* @param latestMilestoneTracker tracker for the milestones to determine when a new local snapshot is due
*/
private void monitorThread(LatestMilestoneTracker latestMilestoneTracker) {
@VisibleForTesting
void monitorThread(LatestMilestoneTracker latestMilestoneTracker) {
while (!Thread.currentThread().isInterrupted()) {
int localSnapshotInterval = getSnapshotInterval(isInSync(latestMilestoneTracker));

Expand All @@ -154,38 +157,42 @@ private void monitorThread(LatestMilestoneTracker latestMilestoneTracker) {
* @param inSync if this node is in sync
* @return the current interval in which we take local snapshots
*/
private int getSnapshotInterval(boolean inSync) {
@VisibleForTesting
int getSnapshotInterval(boolean inSync) {
return inSync
? config.getLocalSnapshotsIntervalSynced()
: config.getLocalSnapshotsIntervalUnsynced();
}

/**
* A node is defined in sync when the latest snapshot milestone index and the latest milestone index are equal.
* In order to prevent a bounce between in and out of sync, a buffer is added when a node became in sync.
* A node is defined in sync when the latest snapshot milestone index and the
* latest milestone index are equal. In order to prevent a bounce between in and
* out of sync, a buffer is added when a node became in sync.
*
* This will always return false if we are not done scanning milestone candidates during initialization.
* This will always return false if we are not done scanning milestone
* candidates during initialization.
*
* @param latestMilestoneTracker tracker we use to determine milestones
* @return <code>true</code> if we are in sync, otherwise <code>false</code>
*/
private boolean isInSync(LatestMilestoneTracker latestMilestoneTracker) {
@VisibleForTesting
boolean isInSync(LatestMilestoneTracker latestMilestoneTracker) {
if (!latestMilestoneTracker.isInitialScanComplete()) {
return false;
}

int latestIndex = latestMilestoneTracker.getLatestMilestoneIndex();
int latestSnapshot = snapshotProvider.getLatestSnapshot().getIndex();

// If we are out of sync, only a full sync will get us in
if (!isInSync && latestIndex == latestSnapshot) {
isInSync = true;
// When we are in sync, only dropping below the buffer gets us out of sync

// When we are in sync, only dropping below the buffer gets us out of sync
} else if (latestSnapshot < latestIndex - LOCAL_SNAPSHOT_SYNC_BUFFER) {
isInSync = false;
}

return isInSync;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.iota.iri.service.snapshot.impl;

import com.google.common.annotations.VisibleForTesting;
import com.iota.iri.SignedFiles;
import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.model.Hash;
Expand Down Expand Up @@ -62,7 +63,8 @@ public class SnapshotProviderImpl implements SnapshotProvider {
* snapshot multiple times while creating their own version of the LocalSnapshotManager, we cache the instance
* here so they don't have to rebuild it from the scratch every time (massively speeds up the unit tests).
*/
private static SnapshotImpl builtinSnapshot = null;
@VisibleForTesting
static SnapshotImpl builtinSnapshot = null;

/**
* Holds Snapshot related configuration parameters.
Expand Down Expand Up @@ -340,7 +342,7 @@ private SnapshotState readSnapshotStatefromFile(String snapshotStateFilePath) th
private SnapshotState readSnapshotStateFromJAR(String snapshotStateFilePath) throws SnapshotException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(SnapshotProviderImpl.class.getResourceAsStream(snapshotStateFilePath))))) {
return readSnapshotState(reader);
} catch (IOException e) {
} catch (NullPointerException | IOException e) {
throw new SnapshotException("failed to read the snapshot file from JAR at " + snapshotStateFilePath, e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.model.Hash;
import com.iota.iri.model.HashFactory;
import com.iota.iri.service.snapshot.SnapshotException;
import com.iota.iri.service.snapshot.SnapshotState;
import com.iota.iri.service.snapshot.SnapshotStateDiff;
import com.iota.iri.utils.IotaIOUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.iota.iri.service.snapshot.impl;

import static org.junit.Assert.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.any;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.exceptions.base.MockitoAssertionError;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.service.milestone.LatestMilestoneTracker;
import com.iota.iri.service.snapshot.SnapshotException;
import com.iota.iri.service.snapshot.SnapshotProvider;
import com.iota.iri.service.snapshot.SnapshotService;
import com.iota.iri.service.transactionpruning.TransactionPruner;
import com.iota.iri.utils.thread.ThreadUtils;

public class LocalSnapshotManagerImplTest {

private static final int BUFFER = LocalSnapshotManagerImpl.LOCAL_SNAPSHOT_SYNC_BUFFER;

private static final int DELAY_SYNC = 5;
private static final int DELAY_UNSYNC = 1;
private static final int SNAPSHOT_DEPTH = 5;

@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();

@Mock
private static SnapshotConfig config;


@Mock(answer = Answers.RETURNS_DEEP_STUBS)
SnapshotProvider snapshotProvider;

@Mock
SnapshotService snapshotService;

@Mock
TransactionPruner transactionPruner;

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
LatestMilestoneTracker milestoneTracker;

private LocalSnapshotManagerImpl lsManager;

@Before
public void setUp() throws Exception {
this.lsManager = new LocalSnapshotManagerImpl();

lsManager.init(snapshotProvider, snapshotService, transactionPruner, config);
when(snapshotProvider.getLatestSnapshot().getIndex()).thenReturn(-5, -1, 10, 998, 999, 1999, 2000);

when(config.getLocalSnapshotsIntervalSynced()).thenReturn(DELAY_SYNC);
when(config.getLocalSnapshotsIntervalUnsynced()).thenReturn(DELAY_UNSYNC);
when(config.getLocalSnapshotsDepth()).thenReturn(SNAPSHOT_DEPTH);
}

@After
public void tearDown() {
lsManager.shutdown();
}

@Test
public synchronized void takeLocalSnapshot() throws SnapshotException {
// Always return true
when(milestoneTracker.isInitialScanComplete()).thenReturn(true);

// When we call it, we are in sync
when(milestoneTracker.getLatestMilestoneIndex()).thenReturn(-5);

// We are more then the depth ahead
when(snapshotProvider.getLatestSnapshot().getIndex()).thenReturn(100);
when(snapshotProvider.getInitialSnapshot().getIndex()).thenReturn(100 - SNAPSHOT_DEPTH - DELAY_SYNC - 1);

// Run in separate thread to allow us to time-out
Thread t = new Thread(() -> lsManager.monitorThread(milestoneTracker));

t.start();
// We should finish directly, margin for slower computers
ThreadUtils.sleep(100);

// Cancel the thread
t.interrupt();

// Verify we took a snapshot
try {
verify(snapshotService, times(1)).takeLocalSnapshot(any(), any());
} catch (MockitoAssertionError e) {
throw new MockitoAssertionError("A snapshot should have been taken when we are below SNAPSHOT_DEPTH");
}
}

@Test
public void isInSyncTestScanIncomplete() {
when(milestoneTracker.isInitialScanComplete()).thenReturn(false);

assertFalse("We should be out of sync when he havent finished initial scans", lsManager.isInSync(milestoneTracker));
}

@Test
public void isInSyncTestScanComplete() {
// Always return true
when(milestoneTracker.isInitialScanComplete()).thenReturn(true);

// We don't really support -1 indexes, but if this breaks, it is a good indication to be careful going further
when(milestoneTracker.getLatestMilestoneIndex()).thenReturn(-1, 5, 10, 998 + BUFFER - 1, 2000);

// snapshotProvider & milestoneTracker
// -5 & -1 -> not in sync
assertFalse("Previous out of sync and not equal index should not be in sync", lsManager.isInSync(milestoneTracker));

// -1 and 5 -> not in sync
assertFalse("Previous out of sync and not equal index should not be in sync", lsManager.isInSync(milestoneTracker));

// 10 and 10 -> in sync
assertTrue("Equal index should be in sync", lsManager.isInSync(milestoneTracker));

// 998 and 1002 -> in sync since sync gap = 5
assertTrue("Index difference less than the buffer still should be in sync", lsManager.isInSync(milestoneTracker));

// 999 and 2000 -> out of sync again, bigger gap than 5
assertFalse("Index difference more than the buffer should be out of sync again ", lsManager.isInSync(milestoneTracker));

// 1999 and 2000 -> out of sync still
assertFalse("Previous out of sync and not equal index should not be in sync", lsManager.isInSync(milestoneTracker));

// 2000 and 2000 -> in sync again
assertTrue("Equal index should be in sync", lsManager.isInSync(milestoneTracker));
}

@Test
public void getDelayTest() {
assertEquals("Out of sync should return the config value at getLocalSnapshotsIntervalUnsynced",
DELAY_UNSYNC, lsManager.getSnapshotInterval(false));

assertEquals("In sync should return the config value at getLocalSnapshotsIntervalSynced",
DELAY_SYNC, lsManager.getSnapshotInterval(true));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.iota.iri.service.snapshot.impl;

import static org.junit.Assert.*;

import java.util.HashMap;

import org.junit.Before;
import org.junit.Test;

import com.iota.iri.TransactionTestUtils;
import com.iota.iri.model.Hash;
import com.iota.iri.service.snapshot.Snapshot;
import com.iota.iri.service.snapshot.SnapshotMetaData;
import com.iota.iri.service.snapshot.SnapshotState;

public class SnapshotImplTest {

private static SnapshotState state;

private static SnapshotMetaData metaData;

@Before
public void setUp() throws Exception {
state = new SnapshotStateImpl(new HashMap<>());
metaData = new SnapshotMetaDataImpl(Hash.NULL_HASH, 1, 1l, new HashMap<>(), new HashMap<>());
}

@Test
public void skippedMilestoneTest() {
Snapshot snapshot = new SnapshotImpl(state, metaData);
assertTrue("Not previously seen milestone should be accepted", snapshot.addSkippedMilestone(1));

assertFalse("Previously seen milestone should not be accepted", snapshot.addSkippedMilestone(1));
assertTrue("Skipped milestone should be removed correctly", snapshot.removeSkippedMilestone(1));
assertFalse("Not skipped milestone should fail to get removed", snapshot.removeSkippedMilestone(1));
}

@Test
public void updateTest() {
Snapshot snapshot = new SnapshotImpl(state, metaData);
snapshot.setIndex(0);
snapshot.setHash(Hash.NULL_HASH);
snapshot.setInitialTimestamp(1l);

Snapshot newSnapshot = snapshot.clone();
newSnapshot.setIndex(1);
snapshot.setHash(TransactionTestUtils.getTransactionHash());
snapshot.setInitialTimestamp(5l);

assertNotEquals("Modified snapshot clone should not be equal to its original", snapshot, newSnapshot);
snapshot.update(newSnapshot);
assertEquals("Updating a snapshot with another snapshot should make them equal", snapshot, newSnapshot);
}

@Test
public void cloneTest() {
Snapshot oldSnapshot = new SnapshotImpl(state, metaData);
Snapshot newSnapshot = oldSnapshot.clone();

assertEquals("A clone of a snapshot is equal to its original", oldSnapshot, newSnapshot);

oldSnapshot.addSkippedMilestone(1);

// Clone shouldnt have the skipped milestone
assertFalse("Adding a value to a clone should be reflected on the original", newSnapshot.removeSkippedMilestone(1));
assertNotEquals("A clone should not be equal to its original after modification", oldSnapshot, newSnapshot);
}
}
Loading

0 comments on commit 2b40917

Please sign in to comment.