TimeUtils: migrate mock clock to java.time API

* Migrate `setMockClock()` parameters to Instant
* Migrate `rollMockClock()` parameters to Duration
* Rename to `clearMockClock()` from `resetMocking()`
* Use `Clock` internally to provide the time
This commit is contained in:
Andreas Schildbach 2023-03-07 14:09:21 +01:00
parent 7a1c71c319
commit 16c90d2891
15 changed files with 76 additions and 69 deletions

View file

@ -18,6 +18,7 @@ package org.bitcoinj.base.internal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@ -29,75 +30,68 @@ import java.util.TimeZone;
*/
public class TimeUtils {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
/**
* If non-null, overrides the return value of now().
*/
private static volatile Date mockTime;
// Clock to be used for the return value of now() and currentTime() variants
private static volatile Clock clock = Clock.systemUTC();
/**
* Advances (or rewinds) the mock clock by the given number of seconds.
*/
public static Date rollMockClock(int seconds) {
return rollMockClockMillis(seconds * 1000);
}
/**
* Advances (or rewinds) the mock clock by the given number of milliseconds.
*/
public static Date rollMockClockMillis(long millis) {
if (mockTime == null)
throw new IllegalStateException("You need to use setMockClock() first.");
mockTime = new Date(mockTime.getTime() + millis);
return mockTime;
}
/**
* Sets the mock clock to the current time.
* Sets the mock clock to the current time as a fixed instant.
*/
public static void setMockClock() {
mockTime = new Date();
setMockClock(Instant.now());
}
/**
* Sets the mock clock to the given time (in seconds).
* Sets the mock clock to a fixed instant.
* @param fixedInstant a fixed instant
*/
public static void setMockClock(long mockClockSeconds) {
mockTime = new Date(mockClockSeconds * 1000);
public static void setMockClock(Instant fixedInstant) {
clock = Clock.fixed(fixedInstant, UTC.toZoneId());
}
/**
* Clears the mock clock and sleep
* Rolls an already set mock clock by the given duration.
* @param delta amount to roll the mock clock, can be negative
* @throws IllegalStateException if the mock clock isn't set
*/
public static void resetMocking() {
mockTime = null;
public static void rollMockClock(Duration delta) {
if (clock.equals(Clock.systemUTC()))
throw new IllegalStateException("You need to use setMockClock() first.");
setMockClock(clock.instant().plus(delta));
}
/**
* Clears the mock clock and causes time to tick again.
*/
public static void clearMockClock() {
clock = Clock.systemUTC();
}
/**
* Returns the current time, or a mocked out equivalent.
*/
public static Date now() {
return mockTime != null ? mockTime : new Date();
return Date.from(currentTime());
}
/**
* Returns the current time in milliseconds since the epoch, or a mocked out equivalent.
*/
public static long currentTimeMillis() {
return mockTime != null ? mockTime.getTime() : System.currentTimeMillis();
return currentTime().toEpochMilli();
}
/**
* Returns the current time in seconds since the epoch, or a mocked out equivalent.
*/
public static long currentTimeSeconds() {
return currentTimeMillis() / 1000;
return currentTime().getEpochSecond();
}
/**
* Returns the current time as an Instant, or a mocked out equivalent.
*/
public static Instant currentTime() {
return Instant.ofEpochMilli(currentTimeMillis());
return Instant.now(clock);
}
/**

View file

@ -20,6 +20,8 @@ package org.bitcoinj.base.internal;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import static org.junit.Assert.assertEquals;
@ -35,9 +37,15 @@ public class TimeUtilsTest {
}
@Test
public void testRollMockClock() {
TimeUtils.setMockClock(25200);
assertEquals(new Date("Thu Jan 01 07:00:08 GMT 1970"), TimeUtils.rollMockClock(8));
TimeUtils.resetMocking();
public void setAndRollMockClock() {
TimeUtils.setMockClock(Instant.ofEpochSecond(25200));
assertEquals(new Date("Thu Jan 01 07:00:00 GMT 1970"), TimeUtils.now());
TimeUtils.rollMockClock(Duration.ofSeconds(8));
assertEquals(new Date("Thu Jan 01 07:00:08 GMT 1970"), TimeUtils.now());
}
@Test(expected = IllegalStateException.class)
public void rollMockClock_uninitialized() {
TimeUtils.rollMockClock(Duration.ofMinutes(1));
}
}

View file

@ -69,7 +69,7 @@ public abstract class AbstractFullPrunedBlockChainTest {
@BeforeClass
public static void setUpClass() {
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
PARAMS = new UnitTestParams() {
@Override public int getInterval() {
return 10000;

View file

@ -40,6 +40,7 @@ import org.junit.rules.ExpectedException;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.Locale;
@ -178,7 +179,7 @@ public class BlockChainTest {
assertTrue(chain.add(newBlock));
prev = newBlock;
// The fake chain should seem to be "fast" for the purposes of difficulty calculations.
TimeUtils.rollMockClock(2);
TimeUtils.rollMockClock(Duration.ofSeconds(2));
}
// Now add another block that has no difficulty adjustment, it should be rejected.
try {

View file

@ -42,6 +42,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
@ -147,7 +148,7 @@ public class TransactionTest {
@Test
public void testEstimatedLockTime_WhenParameterSignifiesBlockHeight() {
int TEST_LOCK_TIME = 20;
Instant now = TimeUtils.currentTime();
Instant now = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS);
BlockChain mockBlockChain = createMock(BlockChain.class);
EasyMock.expect(mockBlockChain.estimateBlockTimeInstant(TEST_LOCK_TIME)).andReturn(now);

View file

@ -39,7 +39,6 @@ import java.math.BigInteger;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@ -51,7 +50,7 @@ public class SPVBlockStoreTest {
@BeforeClass
public static void setUpClass() {
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
}
@Before

View file

@ -97,7 +97,7 @@ public class WalletProtobufSerializerTest {
@BeforeClass
public static void setUpClass() {
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
Context.propagate(new Context());
}

View file

@ -62,7 +62,7 @@ public class TestWithWallet {
@BeforeClass
public static void setUpClass() throws Exception {
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
}
public void setUp() throws Exception {

View file

@ -42,7 +42,7 @@ public class VersionTallyTest {
@BeforeClass
public static void setUpClass() {
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
}
@Before

View file

@ -28,6 +28,7 @@ import org.bitcoinj.wallet.listeners.AbstractKeyChainEventListener;
import org.junit.Before;
import org.junit.Test;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -69,7 +70,7 @@ public class BasicKeyChainTest {
TimeUtils.setMockClock();
long now = TimeUtils.currentTimeSeconds();
final ECKey key1 = new ECKey();
TimeUtils.rollMockClock(86400);
TimeUtils.rollMockClock(Duration.ofDays(1));
final ECKey key2 = new ECKey();
final ArrayList<ECKey> keys = Lists.newArrayList(key1, key2);
@ -198,7 +199,7 @@ public class BasicKeyChainTest {
TimeUtils.setMockClock();
Date now = TimeUtils.now();
final ECKey key1 = new ECKey();
TimeUtils.rollMockClock(5000);
TimeUtils.rollMockClock(Duration.ofSeconds(5000));
final ECKey key2 = new ECKey();
chain.importKeys(Arrays.asList(key1, key2));
List<Protos.Key> keys = chain.serializeToProtobuf();
@ -278,7 +279,7 @@ public class BasicKeyChainTest {
TimeUtils.setMockClock();
long now = TimeUtils.currentTimeSeconds();
final ECKey key1 = new ECKey();
TimeUtils.rollMockClock(86400);
TimeUtils.rollMockClock(Duration.ofDays(1));
final ECKey key2 = new ECKey();
final List<ECKey> keys = Lists.newArrayList(key1, key2);
assertEquals(2, chain.importKeys(keys));

View file

@ -38,6 +38,7 @@ import org.junit.Before;
import org.junit.Test;
import java.math.BigInteger;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@ -256,11 +257,11 @@ public class KeyChainGroupTest {
}
private void encryption(boolean withImported) {
TimeUtils.rollMockClock(0);
TimeUtils.setMockClock();
long now = TimeUtils.currentTimeSeconds();
ECKey a = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(now, group.getEarliestKeyCreationTime());
TimeUtils.rollMockClock(-86400);
TimeUtils.rollMockClock(Duration.ofDays(-1));
long yesterday = TimeUtils.currentTimeSeconds();
ECKey b = new ECKey();
@ -403,9 +404,9 @@ public class KeyChainGroupTest {
long now = TimeUtils.currentTimeSeconds(); // mock
long yesterday = now - 86400;
assertEquals(now, group.getEarliestKeyCreationTime());
TimeUtils.rollMockClock(10000);
TimeUtils.rollMockClock(Duration.ofSeconds(10000));
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
TimeUtils.rollMockClock(10000);
TimeUtils.rollMockClock(Duration.ofSeconds(10000));
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
// Check that all keys are assumed to be created at the same instant the seed is.
assertEquals(now, group.getEarliestKeyCreationTime());

View file

@ -81,6 +81,7 @@ import java.io.File;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@ -1444,7 +1445,7 @@ public class WalletTest extends TestWithWallet {
// Check the wallet can give us an ordered list of all received transactions.
TimeUtils.setMockClock();
Transaction tx1 = sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, COIN);
TimeUtils.rollMockClock(60 * 10);
TimeUtils.rollMockClock(Duration.ofMinutes(10));
Transaction tx2 = sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, valueOf(0, 5));
// Check we got them back in order.
List<Transaction> transactions = wallet.getTransactionsByTime();
@ -1457,7 +1458,7 @@ public class WalletTest extends TestWithWallet {
assertEquals(tx2, transactions.get(0));
// Create a spend five minutes later.
TimeUtils.rollMockClock(60 * 5);
TimeUtils.rollMockClock(Duration.ofMinutes(5));
Transaction tx3 = wallet.createSend(OTHER_ADDRESS, valueOf(0, 5));
// Does not appear in list yet.
assertEquals(2, wallet.getTransactionsByTime().size());
@ -1483,7 +1484,7 @@ public class WalletTest extends TestWithWallet {
long now = TimeUtils.currentTimeSeconds();
wallet = Wallet.createDeterministic(TESTNET, ScriptType.P2PKH);
assertEquals(now, wallet.getEarliestKeyCreationTime());
TimeUtils.rollMockClock(60);
TimeUtils.rollMockClock(Duration.ofMinutes(1));
wallet.freshReceiveKey();
assertEquals(now, wallet.getEarliestKeyCreationTime());
}
@ -1494,7 +1495,7 @@ public class WalletTest extends TestWithWallet {
long now = TimeUtils.currentTimeSeconds();
wallet = Wallet.createDeterministic(TESTNET, ScriptType.P2PKH);
assertEquals(now, wallet.getEarliestKeyCreationTime());
TimeUtils.rollMockClock(-120);
TimeUtils.rollMockClock(Duration.ofMinutes(-2));
wallet.addWatchedAddress(OTHER_ADDRESS);
wallet.freshReceiveKey();
assertEquals(now - 120, wallet.getEarliestKeyCreationTime());
@ -2916,7 +2917,7 @@ public class WalletTest extends TestWithWallet {
assertFalse(wallet.isKeyRotating(key1));
// We got compromised!
TimeUtils.rollMockClock(1);
TimeUtils.rollMockClock(Duration.ofSeconds(1));
wallet.setKeyRotationTime(compromiseTime);
assertTrue(wallet.isKeyRotating(key1));
wallet.doMaintenance(null, true);
@ -3010,7 +3011,7 @@ public class WalletTest extends TestWithWallet {
DeterministicKey watchKey1 = wallet.getWatchingKey();
// A day later, we get compromised.
TimeUtils.rollMockClock(86400);
TimeUtils.rollMockClock(Duration.ofDays(1));
wallet.setKeyRotationTime(TimeUtils.currentTime());
List<Transaction> txns = wallet.doMaintenance(null, false).get();
@ -3030,7 +3031,7 @@ public class WalletTest extends TestWithWallet {
ECKey key = wallet.freshReceiveKey();
Address address = key.toAddress(ScriptType.P2PKH, BitcoinNetwork.TESTNET);
TimeUtils.setMockClock();
TimeUtils.rollMockClock(86400);
TimeUtils.rollMockClock(Duration.ofDays(1));
for (int i = 0; i < 800; i++) {
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, CENT, address);
}
@ -3038,7 +3039,7 @@ public class WalletTest extends TestWithWallet {
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
Instant compromise = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS);
TimeUtils.rollMockClock(86400);
TimeUtils.rollMockClock(Duration.ofDays(1));
wallet.freshReceiveKey();
wallet.setKeyRotationTime(compromise);
wallet.doMaintenance(null, true);

View file

@ -452,15 +452,15 @@ public class PeerTest extends TestWithNetworkConnections {
// This test is INCOMPLETE because it does not check we handle >2000 blocks correctly.
Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block;
blockChain.add(b1);
TimeUtils.rollMockClock(60 * 10); // 10 minutes later.
TimeUtils.rollMockClock(Duration.ofMinutes(10)); // 10 minutes later.
Block b2 = makeSolvedTestBlock(b1);
b2.setTime(TimeUtils.currentTime());
b2.solve();
TimeUtils.rollMockClock(60 * 10); // 10 minutes later.
TimeUtils.rollMockClock(Duration.ofMinutes(10)); // 10 minutes later.
Block b3 = makeSolvedTestBlock(b2);
b3.setTime(TimeUtils.currentTime());
b3.solve();
TimeUtils.rollMockClock(60 * 10);
TimeUtils.rollMockClock(Duration.ofMinutes(10));
Block b4 = makeSolvedTestBlock(b3);
b4.setTime(TimeUtils.currentTime());
b4.solve();
@ -498,7 +498,7 @@ public class PeerTest extends TestWithNetworkConnections {
inbound(writeTarget, b3);
pingAndWait(writeTarget);
closePeer(peer);
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
}
@Test
@ -513,7 +513,7 @@ public class PeerTest extends TestWithNetworkConnections {
assertEquals(Long.MAX_VALUE, peer.getPingTime());
assertFalse(future.isDone());
Ping pingMsg = (Ping) outbound(writeTarget);
TimeUtils.rollMockClock(5);
TimeUtils.rollMockClock(Duration.ofSeconds(5));
// The pong is returned.
inbound(writeTarget, new Pong(pingMsg.getNonce()));
pingAndWait(writeTarget);
@ -525,12 +525,12 @@ public class PeerTest extends TestWithNetworkConnections {
// Do it again and make sure it affects the average.
CompletableFuture<Duration> future2 = peer.sendPing();
pingMsg = (Ping) outbound(writeTarget);
TimeUtils.rollMockClock(50);
TimeUtils.rollMockClock(Duration.ofSeconds(50));
inbound(writeTarget, new Pong(pingMsg.getNonce()));
Duration elapsed2 = future2.get();
assertEquals(elapsed2.toMillis(), peer.getLastPingTime());
assertEquals(7250, peer.getPingTime());
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
}
@Test

View file

@ -80,7 +80,7 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
@After
public void tearDown() {
super.tearDown();
TimeUtils.resetMocking();
TimeUtils.clearMockClock();
}
@Test

View file

@ -39,6 +39,7 @@ import org.junit.rules.Timeout;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.Semaphore;
@ -121,7 +122,7 @@ public class TestWithPeerGroup extends TestWithNetworkConnections {
if (!blockJobs)
return super.schedule(command, delay, unit);
return super.schedule(() -> {
TimeUtils.rollMockClockMillis(unit.toMillis(delay));
TimeUtils.rollMockClock(Duration.ofMillis(unit.toMillis(delay)));
command.run();
jobBlocks.acquireUninterruptibly();
}, 0 /* immediate */, unit);